pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
To: pgsql-hackers@postgresql.org
Subject: Re: Declarative partitioning
Date: Mon, 15 Feb 2016 10:55:57 +0900
Message-ID: <56C1302D.6070403@lab.ntt.co.jp> (raw)
In-Reply-To: <56A9D51E.2000508@lab.ntt.co.jp>
References: <55D3093C.5010800@lab.ntt.co.jp>
	<56A23484.4000504@2ndquadrant.com>
	<56A9D51E.2000508@lab.ntt.co.jp>
List-Unsubscribe: <mailto:majordomo@postgresql.org?body=unsub%20pgsql-hackers>

Hi,

Attached find a patch series to implement the following syntax:

1. Syntax for defining the partition key as a new clause in CREATE TABLE:

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

2. Syntax to create partitions and sub-partitions (as ALTER TABLE commands
on a partitioned table):

ADD PARTITION name FOR VALUES partition_bound_spec
  [ WITH ( storage_parameter [= value] [, ... ] )]
  [ TABLESPACE tablespace_name ]

MODIFY PARTITION partition_name ADD SUBPARTITION name FOR VALUES
partition_bound_spec
  [ WITH ( storage_parameter [= value] [, ... ] )]
  [ TABLESPACE tablespace_name ]

DROP PARTITION name
DROP SUBPARTITION name

ATTACH PARTITION name FOR VALUES partition_bound_spec
  USING [ TABLE ] table_name
  [ WITH ( storage_parameter [= value] [, ... ] )]
  [ TABLESPACE tablespace_name ]


MODIFY PARTITION partition_name ATTACH SUBPARTITION name FOR VALUES
partition_bound_spec
  USING [ TABLE ] table_name
  [ WITH ( storage_parameter [= value] [, ... ] )]
  [ TABLESPACE tablespace_name ]

Where partition_bound_spec is:

  FOR VALUES [ IN ] ( expression [, ...] )
  FOR VALUES LESS THAN ( expression [, ...] )

DETACH PARTITION name [ USING [ TABLE ] table_name]
DETACH SUBPARTITION name [ USING [ TABLE ] table_name]

Please note that ATTACH PARTITION ... USING TABLE command shown above is
not yet implemented.

As mentioned previously, this uses two system catalogs pg_partitioned_rel
and pg_partition.

This is complete with tuple routing so that no triggers or rules are
required. There is also basic planner support but no support yet to enable
constraint exclusion on partitions (which will be fulfilled shortly by
installing equivalent check constraints on partitions).

There is documentation for the new syntax and catalogs. I feel more
detailed documentation is required so I will keep on improving it in
subsequent versions as we build consensus about the syntax and general
design. There are no regression tests at all in the attached patches which
I will add in the next version along with constraint exclusion support.
pg_dump support will also be added shortly.

The individual patches have commit messages that describe code changes.
This is registered in the upcoming CF. Feedback and review is greatly
welcomed!

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

Attachments:

  [text/x-diff] 0001-Add-relkind-RELKIND_PARTITIONED_REL-to-pg_class.h.patch (938B, ../56C1302D.6070403@lab.ntt.co.jp/2-0001-Add-relkind-RELKIND_PARTITIONED_REL-to-pg_class.h.patch)
  download | inline diff:
From 5463d65691bd5037d42280a59120dee008eb0822 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 8 Jan 2016 09:23:12 +0900
Subject: [PATCH 01/10] Add relkind RELKIND_PARTITIONED_REL to pg_class.h

Partitioned tables to be introduced in subsequent commits will be
marked with this relkind.
---
 src/include/catalog/pg_class.h |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fdc67c2..149f619 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -153,6 +153,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 */
-- 
1.7.1

  [text/x-diff] 0002-Add-partitioned-table-system-catalog.patch (11.0K, ../56C1302D.6070403@lab.ntt.co.jp/3-0002-Add-partitioned-table-system-catalog.patch)
  download | inline diff:
From 4bb5a3edee1eaf33b3a6df6892650fc744d50827 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 8 Feb 2016 15:08:49 +0900
Subject: [PATCH 02/10] Add partitioned table system catalog.

Named pg_partitioned_rel, it contains the following information:

 1. Partitioned table oid (pg_class.oid)
 2. Partition key level
 3. Partition strategy (list, range, etc.)
 4. Number of partition attributes
 5. Partition key (array of attnum of partition attributes)
 6. Partition key opclass info (array of opclass oids, one per key attribute)
 7. Partition key expressions (pg_node_tree corresponding to list of expressions)

Add a unique index on (partrelid, partlevel) and associated syscache. There is
also a non-unique index on just partrelid.
---
 doc/src/sgml/catalogs.sgml                 |  109 ++++++++++++++++++++++++++++
 src/backend/catalog/Makefile               |    2 +-
 src/backend/utils/cache/syscache.c         |   12 +++
 src/include/catalog/indexing.h             |    6 ++
 src/include/catalog/pg_partitioned_rel.h   |   69 ++++++++++++++++++
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/sanity_check.out |    1 +
 7 files changed, 199 insertions(+), 1 deletions(-)
 create mode 100644 src/include/catalog/pg_partitioned_rel.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 412c845..db8d0ac 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -219,6 +219,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-rel"><structname>pg_partitioned_rel</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>
@@ -4537,6 +4542,110 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-rel">
+  <title><structname>pg_partitioned_rel</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-rel">
+   <primary>pg_partitioned_rel</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned</structname> stores
+   information about the partition key of partitioned tables.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_rel</> 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>partlevel</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>
+       Partition key level; It's value is 0 for the top-level key and 1 for
+       the sub-partition key, if any
+      </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>partkey</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>indkey</>.  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/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 25130ec..df30415 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_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_rel.h\
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..f703668 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_rel.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
 	},
+	{PartitionedRelRelationId,		/* PARTEDRELIDLEVEL */
+		PartitionedRelRelidLevelIndexId,
+		2,
+		{
+			Anum_pg_partitioned_rel_partrelid,
+			Anum_pg_partitioned_rel_partlevel,
+			0,
+			0
+		},
+		128
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ab2c1a8..343204b 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -316,6 +316,12 @@ 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_rel_partrelid_level_index, 3319, on pg_partitioned_rel using btree(partrelid oid_ops, partlevel int2_ops));
+#define PartitionedRelRelidLevelIndexId			3319
+
+DECLARE_INDEX(pg_partitioned_rel_partrelid_index, 3320, on pg_partitioned_rel using btree(partrelid oid_ops));
+#define PartitionedRelRelidIndexId          3320
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_partitioned_rel.h b/src/include/catalog/pg_partitioned_rel.h
new file mode 100644
index 0000000..3f17451
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_rel.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_rel.h
+ *	  definition of the system "partitioned" relation (pg_partitioned_rel)
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_rel.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_rel definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_rel
+ * ----------------
+ */
+#define PartitionedRelRelationId 3318
+
+CATALOG(pg_partitioned_rel,3318) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	int16			partlevel;		/* partition key level*/
+	char			partstrat;		/* partition key strategy */
+	int16			partnatts;		/* number of partition key columns */
+
+	/* variable-length fields start here, but we allow direct access to indkey */
+	int2vector		partkey;		/* 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_rel;
+
+/* ----------------
+ *      Form_pg_partitioned_rel corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_rel relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_rel *Form_pg_partitioned_rel;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_rel
+ * ----------------
+ */
+#define Natts_pg_partitioned_rel				7
+#define Anum_pg_partitioned_rel_partrelid		1
+#define Anum_pg_partitioned_rel_partlevel		2
+#define Anum_pg_partitioned_rel_partstrat		3
+#define Anum_pg_partitioned_rel_partnatts		4
+#define Anum_pg_partitioned_rel_partkey			5
+#define Anum_pg_partitioned_rel_partclass		6
+#define Anum_pg_partitioned_rel_partexprs		7
+
+#endif   /* PG_PARTITIONED_REL_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..1c961c7 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTEDRELIDLEVEL,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index eb0bc88..19e4e46 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -117,6 +117,7 @@ pg_namespace|t
 pg_opclass|t
 pg_operator|t
 pg_opfamily|t
+pg_partitioned_rel|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
-- 
1.7.1

  [text/x-diff] 0003-Introduce-partition-key-clause-in-CREATE-TABLE.patch (22.0K, ../56C1302D.6070403@lab.ntt.co.jp/4-0003-Introduce-partition-key-clause-in-CREATE-TABLE.patch)
  download | inline diff:
From ad90ca32803d6f9b23e0262e4c558526c5574735 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 8 Feb 2016 15:21:20 +0900
Subject: [PATCH 03/10] Introduce partition key clause in CREATE TABLE.

This adds the following clause:

  PARTITION BY method ON (col [USING opclass] [, ...])
  SUBPARTITION BY method ON (col [USING opclass] [, ...])

where 'method' is either RANGE or LIST. 'col' could be either a simple
column reference or a parenthesized expression . 'opclass' is name
of btree operator class to use with the column (compatible with the
type of the column).

Syntax allows specifycing multiple SUBPARTITION BY clauses each of which
specifies the partition key for successively lower level of partition
hierarchy. However, only 2 levels of partitioning will be supported
in upcoming commits that will add support for creating and managing
partitions. So for now, higher level code emits an error if more than one
SUBPARTITION BY clauses are specified. Note that if we find an aggreable
syntax for creating and managing partitions at arbitrary number of levels,
we can remove the aforementioned restriction.

This adds 2 parser nodes viz. PartitionElem and PartitionBy. The former
is similar to IndexElem and the latter encapsulates details of partition
key such as the method and list of PartitionElems. The new member
partitionBy of CreateStmt is simply a list of PartitionBy nodes. The
first member always corresponds to PARTITION BY clause and subsequent
members (if any) to SUBPARTITION BY clause(s).
---
 doc/src/sgml/ref/create_table.sgml |   58 ++++++++++++++++
 src/backend/nodes/copyfuncs.c      |   33 +++++++++
 src/backend/nodes/equalfuncs.c     |   28 ++++++++
 src/backend/nodes/outfuncs.c       |   27 +++++++
 src/backend/parser/gram.y          |  132 ++++++++++++++++++++++++++++++++---
 src/backend/parser/parse_utilcmd.c |   35 ++++++++++
 src/include/nodes/nodes.h          |    2 +
 src/include/nodes/parsenodes.h     |   35 ++++++++++
 src/include/parser/kwlist.h        |    2 +
 src/include/pg_config_manual.h     |    5 ++
 10 files changed, 345 insertions(+), 12 deletions(-)

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index cd234db..43725e0 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,8 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARTITION BY {RANGE | LIST} ON ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+    [ SUBPARTITION BY {RANGE | LIST} ON ( { <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> ]
@@ -126,6 +128,21 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   </para>
 
   <para>
+   The optional partition clause specifies the partition key and sub-partition
+   keys (if any). If specified, an empty partitioned table is created. Data
+   rows added to the table are mapped to and stored in partitions (or sub-
+   partitions thereof) which are created using separate commands. The rows
+   are mapped based on values of the columns specified in the key and partition
+   rules associated with individual partitions. Partitions can be specified
+   to be further divided into sub-partitions by specifying the sub-partition
+   key. In fact, there can be multiple sub-partition keys each of which
+   corresponds to division of partitions on successive levels. When partitions
+   are so sub-partitioned, any rows mapped to a given partition are further
+   mapped to one of its own partitions based on values of the columns in
+   the sub-partition key corrsponding to the partition's level.
+  </para>
+
+  <para>
    To be able to create a table, you must have <literal>USAGE</literal>
    privilege on all column types or the type in the <literal>OF</literal>
    clause, respectively.
@@ -314,6 +331,47 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY {RANGE | LIST} ON ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+    [ SUBPARTITION BY {RANGE | LIST} ON ( { <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 the partition key
+      of the table. Table thus created is called a partitioned table. The key
+      consists of an ordered list of column names and/or parenthesized
+      expressions when using the <literal>RANGE</> method, whereas only a
+      single column or parenthesized 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>
+      Partitioned table is divided into sub-tables (called partitions), which
+      in turn, are created using separate commands. The table itself is empty
+      and does not have any storage files. A data row inserted into the table
+      is mapped to and stored in one of its partitions based on the values of
+      columns and/or expressions in the partition key and partition rules
+      associated with the partitions.
+     </para>
+
+     <para>
+      If <literal>SUBPARTITION BY</> clause(s) are specified, any partitions
+      of the table are themselves further divided into partitions using
+      similar rules and so on. Thus a multi-level partition hierarchy can
+      be created. In such a multi-level partitioned table, data is stored
+      in the leaf-level 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>
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a9e9cc3..6b8eed8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2995,6 +2995,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partitionBy);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4124,6 +4125,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
  * ****************************************************************
@@ -5011,6 +5038,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;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index b9c3959..befff43 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1153,6 +1153,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partitionBy);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2594,6 +2595,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
  */
@@ -3338,6 +3360,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 28d983c..9baf7cd 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2163,6 +2163,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partitionBy);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3011,6 +3012,26 @@ _outConstraint(StringInfo str, const Constraint *node)
 	}
 }
 
+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 -
@@ -3542,6 +3563,12 @@ _outNode(StringInfo str, const void *obj)
 			case T_XmlSerialize:
 				_outXmlSerialize(str, obj);
 				break;
+			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 b307b48..6e3471d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -226,6 +226,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*pelem;
+	PartitionBy			*partby;
 }
 
 %type <node>	stmt schema_stmt
@@ -537,6 +539,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 <list>	OptPartitionKey PartitionKey
+%type <partby>	PartitionBy SubPartitionBy
+%type <pelem>	part_elem
+%type <list>	part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -601,7 +607,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 MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -627,8 +633,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START
-	STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
-	SYMMETRIC SYSID SYSTEM_P
+	STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBPARTITION
+	SUBSTRING SYMMETRIC SYSID SYSTEM_P
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM TREAT TRIGGER TRIM TRUE_P
@@ -2804,35 +2810,37 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionKey OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partitionBy = $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 OptPartitionKey OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partitionBy = $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;
 				}
@@ -3411,6 +3419,104 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key definition */
+OptPartitionKey: PartitionKey	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionKey: PartitionBy
+				{
+					List *n = list_make1($1);
+
+					$$ = n;
+				}
+			| PartitionKey SubPartitionBy
+				{
+					List *n = lappend($1, $2);
+
+					$$ = n;
+				}
+		;
+
+PartitionBy: PARTITION BY RANGE ON '(' part_params ')'
+				{
+					PartitionBy *n = makeNode(PartitionBy);
+
+					n->strategy = PARTITION_STRAT_RANGE;
+					n->partParams = $6;
+					n->location = @1;
+
+					$$ = n;
+				}
+			| PARTITION BY LIST ON '(' part_params ')'
+				{
+					PartitionBy *n = makeNode(PartitionBy);
+
+					n->strategy = PARTITION_STRAT_LIST;
+					n->partParams = $6;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+SubPartitionBy: SUBPARTITION BY RANGE ON '(' part_params ')'
+				{
+					PartitionBy *n = makeNode(PartitionBy);
+
+					n->strategy = PARTITION_STRAT_RANGE;
+					n->partParams = $6;
+					n->location = @1;
+
+					$$ = n;
+				}
+			| SUBPARTITION BY LIST ON '(' part_params ')'
+				{
+					PartitionBy *n = makeNode(PartitionBy);
+
+					n->strategy = PARTITION_STRAT_LIST;
+					n->partParams = $6;
+					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; }
@@ -13767,6 +13873,7 @@ unreserved_keyword:
 			| LAST_P
 			| LEAKPROOF
 			| LEVEL
+			| LIST
 			| LISTEN
 			| LOAD
 			| LOCAL
@@ -13873,6 +13980,7 @@ unreserved_keyword:
 			| STORAGE
 			| STRICT_P
 			| STRIP_P
+			| SUBPARTITION
 			| SYSID
 			| SYSTEM_P
 			| TABLES
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index dc431c7..54efa35 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -205,6 +205,41 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
 		stmt->relation->schemaname = get_namespace_name(namespaceid);
 
+	if (stmt->partitionBy)
+	{
+		ListCell   *cell;
+		int			keyidx = 0;
+
+		if (list_length(stmt->partitionBy) > 2)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("specifying more than one SUBPARTITION BY clause"
+							" is not supported")));
+
+		foreach(cell, stmt->partitionBy)
+		{
+			PartitionBy *partby = (PartitionBy *) lfirst(cell);
+			int		partnatts = list_length(partby->partParams);
+
+			if (partnatts > PARTITION_MAX_KEYS)
+				ereport(ERROR,
+					(errcode(ERRCODE_TOO_MANY_COLUMNS),
+					 errmsg("cannot use more than %d columns in the %s key",
+							PARTITION_MAX_KEYS,
+							keyidx > 0 ? "sub-partition" : "partition")));
+
+			if (partby->strategy == PARTITION_STRAT_LIST &&
+				partnatts > 1)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot use more than one column in the %s key",
+							keyidx > 0 ? "sub-partition" : "partition"),
+					 errdetail("Only one column allowed with list partitioning.")));
+
+			keyidx++;
+		}
+	}
+
 	/* Set up CreateStmtContext */
 	cxt.pstate = pstate;
 	if (IsA(stmt, CreateForeignTableStmt))
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c407fa2..825f7d9 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -433,6 +433,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 2fd0629..130410c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -696,6 +696,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
@@ -1749,6 +1783,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	List	   *partitionBy;
 	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 6e1e820..38c7afe 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -226,6 +226,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)
@@ -367,6 +368,7 @@ PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD)
 PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD)
 PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("subpartition", SUBPARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD)
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index ef89521..ac40571 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 (XXX - arbitrary)
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
-- 
1.7.1

  [text/x-diff] 0004-Infrastructure-for-creation-of-partitioned-tables.patch (79.9K, ../56C1302D.6070403@lab.ntt.co.jp/5-0004-Infrastructure-for-creation-of-partitioned-tables.patch)
  download | inline diff:
From d16c73fe9df84e8fd5c83ac1c31df2976d29dc97 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 8 Feb 2016 17:06:39 +0900
Subject: [PATCH 04/10] Infrastructure for creation of partitioned tables.

Primary changes include the code to store and retrieve partition key info
to and from the catalog. Other changes are centered around handling special
case behaviors for RELKIND_PARTITIONED_REL relations based on the fact that
no storage objects are allocated for them. Certain SQL features like unique
primary key, exclusion or foreign key constraints cannot be supported right
away.

There are a few special cases around ALTER TABLE, viz. the following:

 * Changing tablespace is handled by simply updating the pg_class.reltablespace
 * Changing logging is handled by simply updating pg_class.relpersistence
 * Dropping or altering type of columns that are part of the partition key
   is prevented
 * Inheritance and OF type_name for partitioned tables is prevented

Creating indexes on partitioned tables is disallowed. Also, running commands
reindex, vacuum, analyze, cluster essentially ignores them. These two things
follow from the fact that partitioned tables themselves are just logical
entities without any storage.

Partition key(s) are built during the first relcache build of a partitioned
relation and stored in rd_partkey. They are built in the CacheMemoryContext.
Each partition key is represented as a PartitionKey struct and rd_partkey
points to an ordered list of these structs. Partition key expressions (if any)
are built on demand and cached in appropriate PartitionKey struct.
---
 src/backend/access/common/reloptions.c |    2 +
 src/backend/catalog/Makefile           |    2 +-
 src/backend/catalog/aclchk.c           |    2 +
 src/backend/catalog/heap.c             |   37 ++-
 src/backend/catalog/index.c            |    4 +
 src/backend/catalog/objectaddress.c    |   10 +-
 src/backend/catalog/partition.c        |  450 ++++++++++++++++++++++++++
 src/backend/commands/analyze.c         |   13 +-
 src/backend/commands/cluster.c         |    4 +
 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       |  544 ++++++++++++++++++++++++++++++--
 src/backend/commands/trigger.c         |    7 +-
 src/backend/commands/vacuum.c          |   11 +-
 src/backend/parser/parse_agg.c         |    6 +
 src/backend/parser/parse_expr.c        |    5 +
 src/backend/parser/parse_utilcmd.c     |   22 ++
 src/backend/rewrite/rewriteDefine.c    |    1 +
 src/backend/tcop/utility.c             |   53 ++--
 src/backend/utils/cache/relcache.c     |   95 ++++++
 src/bin/psql/describe.c                |   42 ++-
 src/include/catalog/partition.h        |   59 ++++
 src/include/commands/defrem.h          |    2 +
 src/include/parser/parse_node.h        |    3 +-
 src/include/utils/rel.h                |    3 +
 src/include/utils/relcache.h           |    2 +
 30 files changed, 1313 insertions(+), 87 deletions(-)
 create mode 100644 src/backend/catalog/partition.c
 create mode 100644 src/include/catalog/partition.h

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 86b9ae1..d6388fe 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -915,6 +915,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:
@@ -1365,6 +1366,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 df30415..ffec071 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/aclchk.c b/src/backend/catalog/aclchk.c
index 0f3bc07..a405205 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -751,6 +751,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/heap.c b/src/backend/catalog/heap.c
index 6a4a9d9..1194611 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"
@@ -295,6 +296,15 @@ heap_create(const char *relname,
 			 */
 			reltablespace = InvalidOid;
 			break;
+		case RELKIND_PARTITIONED_REL:
+			/*
+			 * A partitioned relation itself does not have storage, but still
+			 * needs to have a valid tablespace ID stored in pg_class. It will
+			 * be used as the default tablespace for its individual partitions,
+			 * which can be overridden.
+			 */
+			create_storage = false;
+			break;
 		case RELKIND_SEQUENCE:
 			create_storage = true;
 
@@ -1101,9 +1111,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 +1145,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 +1190,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,8 +1366,13 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
-		heap_create_init_fork(new_rel_desc);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_REL);
+
+		/*
+		 * Do not bother creating the init fork for a partitioned relation.
+		 */
+		if (relkind != RELKIND_PARTITIONED_REL)
+			heap_create_init_fork(new_rel_desc);
 	}
 
 	/*
@@ -1800,11 +1818,18 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_rel tuples.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		RemovePartitionKeysByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
-		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
 	{
 		RelationDropStorage(rel);
 	}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index a309c44..e93aad4 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3498,6 +3498,10 @@ reindex_relation(Oid relid, int flags, int options)
 	 */
 	rel = heap_open(relid, ShareLock);
 
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		ereport(ERROR,
+					 (errmsg("cannot reindex partitioned tables")));
+
 	toast_relid = rel->rd_rel->reltoastrelid;
 
 	/*
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d2aaa6d..fd3db14 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1154,7 +1154,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",
@@ -3186,6 +3187,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);
@@ -3638,6 +3643,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..125f0a1
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,450 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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_rel.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.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"
+
+/*
+ * StorePartitionKey
+ *		Store the partition keys of rel into pg_partitioned_rel catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  int16 partlevel,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprs,
+				  Oid *partOpClassOids,
+				  char strategy)
+{
+	int			i;
+	int2vector *partkey;
+	oidvector  *partopclass;
+	Datum		partexprsDatum;
+	Relation	pg_partitioned_rel;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_rel];
+	bool		nulls[Natts_pg_partitioned_rel];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL);
+
+	tuple = SearchSysCache2(PARTEDRELIDLEVEL,
+							ObjectIdGetDatum(RelationGetRelid(rel)),
+							partlevel);
+	/* 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?)
+	 */
+	partkey = buildint2vector(partattrs, partnatts);
+	partopclass = buildoidvector(partOpClassOids, 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_rel = heap_open(PartitionedRelRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprsDatum)
+		nulls[Anum_pg_partitioned_rel_partexprs - 1] = true;
+
+	values[Anum_pg_partitioned_rel_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_rel_partlevel - 1] = Int16GetDatum(partlevel);
+	values[Anum_pg_partitioned_rel_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_rel_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_rel_partkey - 1] =  PointerGetDatum(partkey);
+	values[Anum_pg_partitioned_rel_partclass - 1] = PointerGetDatum(partopclass);
+	values[Anum_pg_partitioned_rel_partexprs - 1] = partexprsDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_rel), values, nulls);
+
+	simple_heap_insert(pg_partitioned_rel, tuple);
+
+	/* Update the indexes on pg_partitioned_rel */
+	CatalogUpdateIndexes(pg_partitioned_rel, 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 = partOpClassOids[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Things inside key expressions
+	 *
+	 * An ugliness: normal dependencies also created on attribute references
+	 * as part of the following.
+	 */
+	if (partexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprs,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_NORMAL);
+	/* Tell world about the key */
+	CacheInvalidateRelcache(rel);
+
+	heap_close(pg_partitioned_rel, RowExclusiveLock);
+	heap_freetuple(tuple);
+}
+
+/*
+ *  RemovePartitionKeysByRelId
+ *		Remove pg_partitioned_rel entries for a relation
+ */
+void
+RemovePartitionKeysByRelId(Oid relid)
+{
+	Relation	pkeyrel;
+	SysScanDesc	scan;
+	ScanKeyData	key;
+	HeapTuple	tuple;
+
+	pkeyrel = heap_open(PartitionedRelRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&key,
+				Anum_pg_partitioned_rel_partrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+
+	scan = systable_beginscan(pkeyrel, PartitionedRelRelidIndexId, true,
+							  NULL, 1, &key);
+
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+		simple_heap_delete(pkeyrel, &tuple->t_self);
+
+	systable_endscan(scan);
+	heap_close(pkeyrel, RowExclusiveLock);
+}
+
+/*
+ * relid_is_partitioned
+ *		Returns whether relation with OID relid is partitioned
+ */
+bool
+relid_is_partitioned(Oid relid)
+{
+	HeapTuple	tuple;
+	char		relkind;
+
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", relid);
+
+	relkind = ((Form_pg_class) GETSTRUCT(tuple))->relkind;
+	ReleaseSysCache(tuple);
+
+	return relkind == RELKIND_PARTITIONED_REL;
+}
+
+/*
+ * RelationBuildPartitionKeys
+ *		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
+RelationBuildPartitionKeys(Relation relation)
+{
+	List	   *keys = NIL;
+	HeapTuple	tuple;
+	bool		isnull;
+	int			level, i;
+	MemoryContext oldcxt = CurrentMemoryContext;
+
+	/*
+	 * Loop through the partition keys for this relation, if any.
+	 */
+	for (level = 1; ; ++level)
+	{
+		Relation		catalog;
+		Form_pg_partitioned_rel	form;
+		PartitionKey	key;
+		int2vector	   *partattrs;
+		oidvector	   *opclass;
+		Datum			datum;
+
+		tuple = SearchSysCache2(PARTEDRELIDLEVEL,
+							ObjectIdGetDatum(RelationGetRelid(relation)),
+							Int16GetDatum(level));
+
+		if (!HeapTupleIsValid(tuple))
+			break;
+
+		form = (Form_pg_partitioned_rel) GETSTRUCT(tuple);
+
+		/* Allocate in the hopefully short-lived working context */
+		key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+		key->strategy = form->partstrat;
+		key->level = form->partlevel;
+		key->partnatts = form->partnatts;
+
+		/* Open the catalog for its tuple descriptor */
+		catalog = heap_open(PartitionedRelRelationId, AccessShareLock);
+		datum = fastgetattr(tuple, Anum_pg_partitioned_rel_partkey,
+							RelationGetDescr(catalog),
+							&isnull);
+		Assert(!isnull);
+		partattrs = (int2vector *) DatumGetPointer(datum);
+
+		datum = fastgetattr(tuple, Anum_pg_partitioned_rel_partclass,
+							RelationGetDescr(catalog),
+							&isnull);
+		Assert(!isnull);
+		opclass = (oidvector *) DatumGetPointer(datum);
+
+		key->partattrs = (AttrNumber *)
+							palloc0(key->partnatts * sizeof(AttrNumber));
+		key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+		key->partsupfunc = (FmgrInfo *)
+							palloc0(key->partnatts * sizeof(FmgrInfo));
+
+		/*
+		 * Copy partattrs. Further, determine and store the opfamily and
+		 * support function per attribute.
+		 */
+		for (i = 0; i < key->partnatts; i++)
+		{
+			HeapTuple			tuple;
+			Form_pg_opclass 	form;
+			Oid					funcid;
+
+			key->partattrs[i] = partattrs->values[i];
+
+			tuple = SearchSysCache(CLAOID,
+							ObjectIdGetDatum(opclass->values[i]),
+							0, 0, 0);
+			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;
+
+			/*
+			 * Btree support function is good to cover the list and range
+			 * partitionig related needs.
+			 */
+			funcid = get_opfamily_proc(form->opcfamily,
+							form->opcintype, form->opcintype,
+							BTORDER_PROC);
+
+			fmgr_info(funcid, &key->partsupfunc[i]);
+			ReleaseSysCache(tuple);
+		}
+
+		/* partexprs are built and copied here as and when necessary */
+		key->partexprs = NIL;
+
+		keys = lappend(keys, key);
+
+		ReleaseSysCache(tuple);
+		heap_close(catalog, AccessShareLock);
+	}
+
+	/* Success --- now copy to the cache memory */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	relation->rd_partkeys = CopyPartitionKeys(keys);
+	MemoryContextSwitchTo(oldcxt);
+
+	FreePartitionKeys(keys);
+}
+
+/*
+ * CopyPartitionKeys
+ *
+ * The copy is allocated in the current memory context.
+ */
+List *
+CopyPartitionKeys(List *keys)
+{
+	ListCell   *cell;
+	List	   *result = NIL;
+
+	foreach (cell, keys)
+	{
+		PartitionKey	fromkey = lfirst(cell);
+		PartitionKey	newkey = (PartitionKey)
+									palloc0(sizeof(PartitionKeyData));
+
+		newkey->strategy = fromkey->strategy;
+		newkey->level = fromkey->level;
+		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->partsupfunc = (FmgrInfo *)
+							palloc(newkey->partnatts * sizeof(FmgrInfo));
+		memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+							newkey->partnatts * sizeof(FmgrInfo));
+
+		result = lappend(result, newkey);
+	}
+
+	return result;
+}
+
+/*
+ * FreePartitionKeys
+ */
+void
+FreePartitionKeys(List *keys)
+{
+	ListCell   *cell;
+
+	if (keys == NIL)
+		return;
+
+	foreach (cell, keys)
+	{
+		PartitionKey	key = lfirst(cell);
+
+		pfree(key->partattrs);
+		pfree(key->partopfamily);
+		pfree(key->partsupfunc);
+		pfree(key);
+	}
+	pfree(keys);
+}
+
+/*
+ * get_partition_key_type_info
+ *
+ * Returns a PartitionKeyTypeInfo object that includes type info
+ * for individual partition key attributes.
+ *
+ * Separated out to avoid repeating the code.
+ */
+PartitionKeyTypeInfo *
+get_partition_key_type_info(Relation rel, PartitionKey key)
+{
+	int			i;
+	List	   *partexprs;
+	ListCell   *partexprs_item;
+	PartitionKeyTypeInfo *result;
+
+	partexprs = RelationGetPartitionExpr(rel, key->level);
+
+	result = (PartitionKeyTypeInfo *) palloc0(sizeof(PartitionKeyTypeInfo));
+	result->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	result->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	result->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	result->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	result->typalign = (char *) palloc0(key->partnatts * sizeof(bool));
+
+	partexprs_item = list_head(partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber attno = key->partattrs[i];
+
+		if (attno != InvalidAttrNumber)
+		{
+			result->typid[i] = rel->rd_att->attrs[attno - 1]->atttypid;
+			result->typmod[i] = rel->rd_att->attrs[attno - 1]->atttypmod;
+		}
+		else
+		{
+			result->typid[i] = exprType(lfirst(partexprs_item));
+			result->typmod[i] = exprTypmod(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+		get_typlenbyvalalign(result->typid[i],
+								&result->typlen[i],
+								&result->typbyval[i],
+								&result->typalign[i]);
+	}
+
+	if (partexprs)
+		pfree(partexprs);
+
+	return result;
+}
+
+/*
+ * free_key_type_info
+ */
+void
+free_partition_key_type_info(PartitionKeyTypeInfo *typinfo)
+{
+	Assert(typinfo != NULL);
+
+	pfree(typinfo->typid);
+	pfree(typinfo->typmod);
+	pfree(typinfo->typlen);
+	pfree(typinfo->typbyval);
+	pfree(typinfo->typalign);
+	pfree(typinfo);
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 070df29..e412c68 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -237,9 +237,16 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	{
 		/* No need for a WARNING if we already complained during VACUUM */
 		if (!(options & VACOPT_VACUUM))
-			ereport(WARNING,
-					(errmsg("skipping \"%s\" --- cannot analyze non-tables or special system tables",
-							RelationGetRelationName(onerel))));
+		{
+			if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+				ereport(WARNING,
+						(errmsg("skipping \"%s\" --- cannot analyze partitioned tables",
+								RelationGetRelationName(onerel))));
+			else
+				ereport(WARNING,
+						(errmsg("skipping \"%s\" --- cannot analyze non-tables or special system tables",
+								RelationGetRelationName(onerel))));
+		}
 		relation_close(onerel, ShareUpdateExclusiveLock);
 		return;
 	}
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 5cb28cf..25398ae 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -128,6 +128,10 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 			   errmsg("cannot cluster temporary tables of other sessions")));
 
+		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+			ereport(ERROR,
+					(errmsg("cannot cluster partitioned tables")));
+
 		if (stmt->indexname == NULL)
 		{
 			ListCell   *index;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3201476..403738d 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 13b04e6..ca02c91 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -67,8 +67,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);
@@ -381,6 +379,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_REL)
+			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),
@@ -1219,7 +1222,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 bb735ac..3f77694 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 96dc923..674fbe5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -262,6 +262,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 +439,20 @@ 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 PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Oid relid,
+								List *partParams,
+								AttrNumber *partattrs,
+								List **partexprs,
+								Oid *partOpClassOids);
+static bool find_attr_reference_walker(Node *node,
+								find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static void SetRelationSpcFileNode(Relation rel, Oid newTableSpace,
+								Oid newFileNode);
+static void ATExecChangePersistence(List **wqueue, AlteredTableInfo *tab,
+						Relation rel, char persistence, LOCKMODE lockmode);
+static void SetRelationPersistence(Oid relationId, char persistence);
 
 
 /* ----------------------------------------------------------------
@@ -476,6 +496,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
 	Oid			ofTypeId;
 	ObjectAddress address;
+	int16		keyindex;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -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_REL));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -710,6 +732,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
 								  true, true, false);
 
+	/* Store partition key information into the catalog */
+	keyindex = 1;
+	foreach(listptr, stmt->partitionBy)
+	{
+		PartitionBy	   *partby = lfirst(listptr);
+		int16			partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		List		   *partexprs = NIL;
+		Oid				partOpClassOids[PARTITION_MAX_KEYS];
+
+		partnatts = list_length(partby->partParams);
+		partby = transformPartitionBy(rel, partby);
+
+		ComputePartitionAttrs(relationId, partby->partParams,
+							  partattrs,
+							  &partexprs,
+							  partOpClassOids);
+
+		StorePartitionKey(rel, keyindex++, partnatts, partattrs,
+							partexprs, partOpClassOids, partby->strategy);
+	}
+
 	ObjectAddressSet(address, RelationRelationId, relationId);
 
 	/*
@@ -955,7 +999,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 */
@@ -1521,6 +1573,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_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)
 			ereport(ERROR,
@@ -2162,6 +2221,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 &&
@@ -3577,7 +3637,11 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			ATExecDropCluster(rel, lockmode);
 			break;
 		case AT_SetLogged:		/* SET LOGGED */
+			ATExecChangePersistence(wqueue, tab, rel,
+									RELPERSISTENCE_PERMANENT, lockmode);
 		case AT_SetUnLogged:	/* SET UNLOGGED */
+			ATExecChangePersistence(wqueue, tab, rel,
+									RELPERSISTENCE_UNLOGGED, lockmode);
 			break;
 		case AT_AddOids:		/* SET WITH OIDS */
 			/* Use the ADD COLUMN code, unless prep decided to do nothing */
@@ -3724,8 +3788,10 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		/* Foreign tables have no storage. */
-		if (tab->relkind == RELKIND_FOREIGN_TABLE)
+		/* Foreign tables and partitioned tables have no storage. */
+		if (tab->relkind == RELKIND_FOREIGN_TABLE ||
+			(tab->relkind == RELKIND_PARTITIONED_REL &&
+				tab->newTableSpace == InvalidOid))
 			continue;
 
 		/*
@@ -4291,6 +4357,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 +4594,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 +5485,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 +5760,84 @@ 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)
+{
+	List	   *keys = rel->rd_partkeys;
+	ListCell   *cell;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+	{
+		*is_expr = false;
+		return false;
+	}
+
+	foreach(cell, keys)
+	{
+		PartitionKey	key = lfirst(cell);
+		int16		level = key->level;
+		int			partnatts = key->partnatts;
+		AttrNumber *partattrs = key->partattrs;
+		List	   *partexprs = RelationGetPartitionExpr(rel, level);
+		ListCell   *partexpr_item;
+		int			i;
+
+		partexpr_item = list_head(partexprs);
+		for (i = 0; i < partnatts; i++)
+		{
+			AttrNumber partatt = partattrs[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);
+			}
+		}
+
+		if (partexprs)
+			pfree(partexprs);
+	}
+
+	return false;
+}
+
 /*
  * Return value is the address of the dropped column.
  */
@@ -5705,6 +5852,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 +5897,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 expressions")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6428,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),
@@ -7319,8 +7486,12 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	bool		isnull;
 	Snapshot	snapshot;
 
-	/* VALIDATE CONSTRAINT is a no-op for foreign tables */
-	if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	/*
+	 * VALIDATE CONSTRAINT is a no-op for foreign tables and partitioned
+	 * tables.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
 		return;
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrtup);
@@ -7861,6 +8032,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 +8063,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 drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expressions")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7906,7 +8091,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 +9119,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 +9582,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);
@@ -9554,9 +9742,6 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	Oid			newrelfilenode;
 	RelFileNode newrnode;
 	SMgrRelation dstrel;
-	Relation	pg_class;
-	HeapTuple	tuple;
-	Form_pg_class rd_rel;
 	ForkNumber	forkNum;
 	List	   *reltoastidxids = NIL;
 	ListCell   *lc;
@@ -9605,6 +9790,18 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot move temporary tables of other sessions")));
 
+	/* For partitioned tables, just set the new tablespace in pg_class */
+	if(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+	{
+		SetRelationSpcFileNode(rel, newTableSpace, rel->rd_rel->relfilenode);
+		relation_close(rel, lockmode);
+
+		/* Make sure the reltablespace change is visible */
+		CommandCounterIncrement();
+
+		return;
+	}
+
 	reltoastrelid = rel->rd_rel->reltoastrelid;
 	/* Fetch the list of indexes on toast relation if necessary */
 	if (OidIsValid(reltoastrelid))
@@ -9615,14 +9812,6 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 		relation_close(toastRel, lockmode);
 	}
 
-	/* Get a modifiable copy of the relation's pg_class row */
-	pg_class = heap_open(RelationRelationId, RowExclusiveLock);
-
-	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(tableOid));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "cache lookup failed for relation %u", tableOid);
-	rd_rel = (Form_pg_class) GETSTRUCT(tuple);
-
 	/*
 	 * Since we copy the file directly without looking at the shared buffers,
 	 * we'd better first flush out any pages of the source relation that are
@@ -9683,18 +9872,11 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	RelationDropStorage(rel);
 	smgrclose(dstrel);
 
-	/* update the pg_class row */
-	rd_rel->reltablespace = (newTableSpace == MyDatabaseTableSpace) ? InvalidOid : newTableSpace;
-	rd_rel->relfilenode = newrelfilenode;
-	simple_heap_update(pg_class, &tuple->t_self, tuple);
-	CatalogUpdateIndexes(pg_class, tuple);
+	/* Now update pg_class.reltablespace */
+	SetRelationSpcFileNode(rel, newTableSpace, newrelfilenode);
 
 	InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
 
-	heap_freetuple(tuple);
-
-	heap_close(pg_class, RowExclusiveLock);
-
 	relation_close(rel, NoLock);
 
 	/* Make sure the reltablespace change is visible */
@@ -9711,6 +9893,37 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 }
 
 /*
+ * SetRelationTablespace - updates rel's reltablespace.
+ */
+static void
+SetRelationSpcFileNode(Relation rel, Oid newTableSpace, Oid newFileNode)
+{
+	Relation	pg_class;
+	HeapTuple	tuple;
+	Form_pg_class rd_rel;
+
+	/* Get a modifiable copy of the relation's pg_class row */
+	pg_class = heap_open(RelationRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(RELOID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+											RelationGetRelid(rel));
+	rd_rel = (Form_pg_class) GETSTRUCT(tuple);
+
+	/* update the pg_class row */
+	rd_rel->reltablespace = (newTableSpace == MyDatabaseTableSpace)
+										? InvalidOid : newTableSpace;
+	rd_rel->relfilenode = newFileNode;
+	simple_heap_update(pg_class, &tuple->t_self, tuple);
+	CatalogUpdateIndexes(pg_class, tuple);
+
+	heap_freetuple(tuple);
+	heap_close(pg_class, RowExclusiveLock);
+}
+
+/*
  * Alter Table ALL ... SET TABLESPACE
  *
  * Allows a user to move all objects of some type in a given tablespace in the
@@ -9817,7 +10030,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 +10230,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 +10286,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.
@@ -10702,6 +10927,11 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 				typeobj;
 	HeapTuple	classtuple;
 
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a partitioned table to composite type")));
+
 	/* Validate the type. */
 	typetuple = typenameType(NULL, ofTypename, NULL);
 	check_of_type(typetuple);
@@ -10971,6 +11201,11 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode
 	Relation	indexRel;
 	int			key;
 
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("cannot set replica identity for partitioned tables")));
+
 	if (stmt->identity_type == REPLICA_IDENTITY_DEFAULT)
 	{
 		relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
@@ -11343,6 +11578,55 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 }
 
 /*
+ * ATExecChangePersistence - change pg_class.relpersistence of rel
+ */
+static void
+ATExecChangePersistence(List **wqueue, AlteredTableInfo *tab, Relation rel,
+						char persistence, LOCKMODE lockmode)
+{
+	/* Phase 3 does not handle partitioned tables, so do this here. */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		SetRelationPersistence(RelationGetRelid(rel), persistence);
+}
+
+/*
+ * SetRelationPersistence - set relpersistence
+ */
+static void
+SetRelationPersistence(Oid relationId, char persistence)
+{
+	Relation	relationRelation;
+	HeapTuple	tuple;
+	Form_pg_class classtuple;
+
+	/*
+	 * Fetch a modifiable copy of the tuple, modify it, update pg_class.
+	 */
+	relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relationId));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", relationId);
+	classtuple = (Form_pg_class) GETSTRUCT(tuple);
+
+	if (classtuple->relpersistence != persistence)
+	{
+		classtuple->relpersistence = persistence;
+		simple_heap_update(relationRelation, &tuple->t_self, tuple);
+
+		/* keep the catalog indexes up to date */
+		CatalogUpdateIndexes(relationRelation, tuple);
+	}
+	else
+	{
+		/* no need to change tuple, but force relcache rebuild anyway */
+		CacheInvalidateRelcacheByTuple(tuple);
+	}
+
+	heap_freetuple(tuple);
+	heap_close(relationRelation, RowExclusiveLock);
+}
+
+/*
  * Execute ALTER TABLE SET SCHEMA
  */
 ObjectAddress
@@ -11435,6 +11719,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);
@@ -11884,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_REL)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12038,6 +12323,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 &&
@@ -12049,3 +12335,203 @@ 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)
+{
+	ParseState 		*pstate = NULL;
+	RangeTblEntry	*rte;
+	ListCell		*l;
+	PartitionBy		*partby = NULL;
+
+	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, false, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partitionby->partParams)
+	{
+		ListCell	   *column;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... ON (foo, foo) */
+		foreach(column, partby->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(column);
+			if (pparam->name && strcmp(pelem->name, pparam->name) == 0)
+				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_PARTKEY_EXPRESSION);
+
+			/* 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 *partOpClassOids)
+{
+	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))
+			{
+				/* difference in error message spellings is historical */
+				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);
+
+		partOpClassOids[attn++] = opclassOid;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index cca2e43..26dbac3 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 &&
@@ -1114,6 +1115,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,
@@ -1220,7 +1222,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 4cb4acf..cc21951 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1300,9 +1300,14 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
-		ereport(WARNING,
-				(errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
-						RelationGetRelationName(onerel))));
+		if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+			ereport(WARNING,
+					(errmsg("skipping \"%s\" --- cannot vacuum partitioned tables",
+							RelationGetRelationName(onerel))));
+		else
+			ereport(WARNING,
+					(errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
+							RelationGetRelationName(onerel))));
 		relation_close(onerel, lmode);
 		PopActiveSnapshot();
 		CommitTransactionCommand();
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b109ea5..3d465c0 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -457,6 +457,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in index expressions");
 
 			break;
+		case EXPR_KIND_PARTKEY_EXPRESSION:
+			err = _("aggregate functions are not allowed in partition key expressions");
+			break;
 		case EXPR_KIND_INDEX_PREDICATE:
 			if (isAgg)
 				err = _("aggregate functions are not allowed in index predicates");
@@ -830,6 +833,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_INDEX_EXPRESSION:
 			err = _("window functions are not allowed in index expressions");
 			break;
+		case EXPR_KIND_PARTKEY_EXPRESSION:
+			err = _("window functions are not allowed in partition key expressions");
+			break;
 		case EXPR_KIND_INDEX_PREDICATE:
 			err = _("window functions are not allowed in index predicates");
 			break;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index ebda55d..b115f66 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1703,6 +1703,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_INDEX_EXPRESSION:
 			err = _("cannot use subquery in index expression");
 			break;
+		case EXPR_KIND_PARTKEY_EXPRESSION:
+			err = _("cannot use subquery in partition key expressions");
+			break;
 		case EXPR_KIND_INDEX_PREDICATE:
 			err = _("cannot use subquery in index predicate");
 			break;
@@ -3225,6 +3228,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTKEY_EXPRESSION:
+			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 54efa35..02655da 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -75,6 +75,7 @@ typedef struct
 	Relation	rel;			/* opened/locked rel, if ALTER */
 	List	   *inhRelations;	/* relations to inherit from */
 	bool		isforeign;		/* true if CREATE/ALTER FOREIGN TABLE */
+	bool		ispartitioned;	/* true if has partition key */
 	bool		isalter;		/* true if altering existing table */
 	bool		hasoids;		/* does relation have an OID column? */
 	List	   *columns;		/* ColumnDef items */
@@ -264,6 +265,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partitionBy != NIL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -617,6 +619,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:
@@ -626,6 +634,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 partititoned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -643,6 +657,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
@@ -794,6 +814,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 &&
@@ -2550,6 +2571,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 1434a03..b7cda53 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/tcop/utility.c b/src/backend/tcop/utility.c
index 045f7f0..34da4e2 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -961,37 +961,48 @@ ProcessUtilitySlow(Node *parsetree,
 						{
 							Datum		toast_options;
 							static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+							char	relkind = ((CreateStmt *) stmt)->partitionBy != NIL
+													? RELKIND_PARTITIONED_REL
+													: RELKIND_RELATION;
 
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
-													 RELKIND_RELATION,
+													 relkind,
 													 InvalidOid, NULL);
 							EventTriggerCollectSimpleCommand(address,
 															 secondaryObject,
 															 stmt);
 
 							/*
-							 * Let NewRelationCreateToastTable decide if this
-							 * one needs a secondary relation too.
+							 * It's not clear whether this is the right place
+							 * to determine using the relkind, whether or not
+							 * TOAST relation is to be created.
 							 */
-							CommandCounterIncrement();
-
-							/*
-							 * parse and validate reloptions for the toast
-							 * table
-							 */
-							toast_options = transformRelOptions((Datum) 0,
-											  ((CreateStmt *) stmt)->options,
-																"toast",
-																validnsps,
-																true,
-																false);
-							(void) heap_reloptions(RELKIND_TOASTVALUE,
-												   toast_options,
-												   true);
+							if (relkind != RELKIND_PARTITIONED_REL)
+							{
+								/*
+								 * Let NewRelationCreateToastTable decide if this
+								 * one needs a secondary relation too.
+								 */
+								CommandCounterIncrement();
 
-							NewRelationCreateToastTable(address.objectId,
-														toast_options);
+								/*
+								 * parse and validate reloptions for the toast
+								 * table
+								 */
+								toast_options = transformRelOptions((Datum) 0,
+												  ((CreateStmt *) stmt)->options,
+																	"toast",
+																	validnsps,
+																	true,
+																	false);
+								(void) heap_reloptions(RELKIND_TOASTVALUE,
+													   toast_options,
+													   true);
+
+								NewRelationCreateToastTable(address.objectId,
+															toast_options);
+							}
 						}
 						else if (IsA(stmt, CreateForeignTableStmt))
 						{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 130c06d..ddb0da2 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_rel.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.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:
@@ -1049,6 +1051,10 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->rd_rsdesc = NULL;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		RelationBuildPartitionKeys(relation);
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2045,6 +2051,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	FreePartitionKeys(relation->rd_partkeys);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -4506,6 +4513,93 @@ RelationGetExclusionInfo(Relation indexRelation,
 	MemoryContextSwitchTo(oldcxt);
 }
 
+/*
+ * RelationGetPartitionExpr -- get the partition key expressions
+ *
+ * The returned node tree is copied into the caller's memory context. (We
+ * don't want to return a pointer to the relcache copy,since it could
+ * disappear due to relcache invalidation.)
+ */
+List *
+RelationGetPartitionExpr(Relation relation, int16 level)
+{
+	ListCell   *cell;
+	Relation	catalog;
+	HeapTuple	tuple;
+	List	   *result = NIL;
+	Datum		exprsDatum;
+	bool		isnull;
+	char	   *exprsString;
+	MemoryContext oldcxt;
+
+	/* Shouldn't happen but play safe! */
+	if (relation->rd_partkeys == NIL)
+		return NIL;
+
+	/* Find the proper key in relcache */
+	foreach(cell, relation->rd_partkeys)
+	{
+		PartitionKey	key = lfirst(cell);
+
+		if (key->level != level)
+			continue;
+
+		/* Quick copy if we already computed the result */
+		if (key->partexprs)
+			return (List *) copyObject(key->partexprs);
+
+		/* Need to compute from the tuple in the syscache */
+		tuple = SearchSysCache2(PARTEDRELIDLEVEL,
+								RelationGetRelid(relation),
+								level);
+		Assert(HeapTupleIsValid(tuple));
+
+		/*
+		 * We build the tree we intend to return in the caller's context.
+		 * After successfully completing the work, we copy it into the
+		 * relcache entry. This avoids problems if we get some sort of
+		 * error partway through.
+		 */
+		catalog = heap_open(PartitionedRelRelationId, AccessShareLock);
+		exprsDatum = heap_getattr(tuple,
+								Anum_pg_partitioned_rel_partexprs,
+								RelationGetDescr(catalog),
+								&isnull);
+
+		/* No expressions in the key at this level */
+		if (isnull)
+		{
+			ReleaseSysCache(tuple);
+			heap_close(catalog, AccessShareLock);
+			return NIL;
+		}
+
+		exprsString = TextDatumGetCString(exprsDatum);
+		result = (List *) stringToNode(exprsString);
+		pfree(exprsString);
+
+		/*
+		 * Run the expressions through eval_const_expressions. This is not just an
+		 * optimization, but is necessary, because 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.
+		 */
+		result = (List *) eval_const_expressions(NULL, (Node *) result);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) result);
+
+		/* Now save a copy of the completed tree in the relcache entry. */
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+		key->partexprs = (List *) copyObject(result);
+		MemoryContextSwitchTo(oldcxt);
+
+		ReleaseSysCache(tuple);
+		heap_close(catalog, AccessShareLock);
+	}
+
+	return result;
+}
 
 /*
  * Routines to support ereport() reports of relation-related errors
@@ -4898,6 +4992,7 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeys = NIL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index fd8dc91..aa8c694 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -764,6 +764,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"),
@@ -773,6 +774,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");
@@ -819,7 +821,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
@@ -1463,8 +1465,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)");
 	}
 
@@ -1529,6 +1531,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\"",
@@ -1542,8 +1552,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");
@@ -1563,12 +1573,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");
 	}
 
@@ -1668,7 +1678,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);
@@ -1676,8 +1686,8 @@ 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);
 		}
@@ -1822,7 +1832,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;
@@ -2381,7 +2391,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;
@@ -2592,7 +2602,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
@@ -2923,6 +2933,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"),
@@ -2934,6 +2945,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"));
 
@@ -2972,7 +2984,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/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..44b4c06
--- /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/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+/* Partition key information for one level */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partition strategy */
+	int16		level;			/* partition level */
+	int16		partnatts;		/* number of partition attributes */
+	AttrNumber *partattrs;		/* partition attnums */
+	Oid		   *partopfamily;	/* OIDs of opfamily per col */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+	List	   *partexprs;		/* partition key expressions, if any */
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
+
+/* Type information for partition key columns */
+typedef struct PartitionKeyTypeInfo
+{
+	Oid		*typid;
+	int32	*typmod;
+	int16	*typlen;
+	bool	*typbyval;
+	char	*typalign;
+} PartitionKeyTypeInfo;
+
+extern void StorePartitionKey(Relation rel,
+					int16 partlevel,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprs,
+					Oid *partOpClassOids,
+					char strategy);
+extern void RemovePartitionKeysByRelId(Oid relid);
+extern void RelationBuildPartitionKeys(Relation relation);
+extern List *CopyPartitionKeys(List *keys);
+extern void FreePartitionKeys(List *keys);
+extern PartitionKeyTypeInfo *get_partition_key_type_info(Relation rel,
+					PartitionKey key);
+extern void free_partition_key_type_info(PartitionKeyTypeInfo *typinfo);
+extern bool relid_is_partitioned(Oid relid);
+
+#endif   /* PARTITION_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 54f67e9..1b6270c 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/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..f6f3f52 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_PARTKEY_EXPRESSION	/* partition key expression */
 } ParseExprKind;
 
 
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index f2bebf2..4242e84 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -15,6 +15,7 @@
 #define REL_H
 
 #include "access/tupdesc.h"
+#include "catalog/partition.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_index.h"
 #include "fmgr.h"
@@ -89,6 +90,8 @@ typedef struct RelationData
 	/* use "struct" here to avoid needing to include rowsecurity.h: */
 	struct RowSecurityDesc *rd_rsdesc;	/* row security policies, or NULL */
 
+	List	*rd_partkeys;		/* partition keys, or NIL */
+
 	/* 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 */
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 1b48304..0aa2104 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -42,6 +42,8 @@ extern Oid	RelationGetOidIndex(Relation relation);
 extern Oid	RelationGetReplicaIndex(Relation relation);
 extern List *RelationGetIndexExpressions(Relation relation);
 extern List *RelationGetIndexPredicate(Relation relation);
+extern List *RelationGetPartitionExpr(Relation relation,
+									int16 level);
 
 typedef enum IndexAttrBitmapKind
 {
-- 
1.7.1

  [text/x-diff] 0005-Add-partition-system-catalog.patch (8.3K, ../56C1302D.6070403@lab.ntt.co.jp/6-0005-Add-partition-system-catalog.patch)
  download | inline diff:
From ba39817ac4ccc6182eae720a6f54df0f66584574 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 22 Jan 2016 18:31:39 +0900
Subject: [PATCH 05/10] Add partition system catalog.

Named pg_partition, it contains the following information:

 1. Partition relation oid (pg_class.oid)
 2. Partition parent oid
 3. Whether partition is valid
 4. List partition literal values
 5. Range partition maximum value per key column

It has a unique index on partrelid and the related syscache. Also,
an index on partparent.
---
 doc/src/sgml/catalogs.sgml         |   75 +++++++++++++++++++++++++++++++++++-
 src/backend/catalog/Makefile       |    2 +-
 src/backend/utils/cache/syscache.c |   12 ++++++
 src/include/catalog/indexing.h     |    5 ++
 src/include/catalog/pg_partition.h |   62 +++++++++++++++++++++++++++++
 src/include/utils/syscache.h       |    1 +
 6 files changed, 155 insertions(+), 2 deletions(-)
 create mode 100644 src/include/catalog/pg_partition.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index db8d0ac..a5fe8a6 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -219,8 +219,13 @@
      </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-rel"><structname>pg_partitioned_rel</structname></link></entry>
-      <entry>information about partitioned tables, including the partition key</entry>
+      <entry>information about partitioned tables</entry>
      </row>
 
      <row>
@@ -4542,6 +4547,74 @@
 
  </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
+   information about partitions of partitioned tables.
+  </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>partparent</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 partitioned table this partition is part of</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partvalid</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>Partition is valid</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partlistvals</structfield></entry>
+      <entry><type>anyarray</type></entry>
+      <entry></entry>
+      <entry>For partitions of list partitioned table, list of values assigned to this partition</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partrangemaxs</structfield></entry>
+      <entry><type>anyarray</type></entry>
+      <entry></entry>
+      <entry>For partitions of range partitioned table, array of maximum values per partition key column</entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+
+ </sect1>
+
  <sect1 id="catalog-pg-partitioned-rel">
   <title><structname>pg_partitioned_rel</structname></title>
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index ffec071..fc85518 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_seclabel.h pg_shseclabel.h \
-	pg_collation.h pg_range.h pg_transform.h pg_partitioned_rel.h\
+	pg_collation.h pg_range.h pg_transform.h pg_partitioned_rel.h pg_partition.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index f703668..fe194d5 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_partition.h"
 #include "catalog/pg_partitioned_rel.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
@@ -569,6 +570,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionRelationId,		/* PARTRELID */
+		PartitionRelidIndexId,
+		1,
+		{
+			Anum_pg_partition_partrelid,
+			0,
+			0,
+			0
+		},
+		128
+	},
 	{PartitionedRelRelationId,		/* PARTEDRELIDLEVEL */
 		PartitionedRelRelidLevelIndexId,
 		2,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 343204b..a2a583d 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -322,6 +322,11 @@ DECLARE_UNIQUE_INDEX(pg_partitioned_rel_partrelid_level_index, 3319, on pg_parti
 DECLARE_INDEX(pg_partitioned_rel_partrelid_index, 3320, on pg_partitioned_rel using btree(partrelid oid_ops));
 #define PartitionedRelRelidIndexId          3320
 
+DECLARE_UNIQUE_INDEX(pg_partition_partrelid_index, 3322, on pg_partition using btree(partrelid oid_ops));
+#define PartitionRelidIndexId		3322
+DECLARE_INDEX(pg_partition_parent_index, 3323, on pg_partition using btree(partparent oid_ops));
+#define PartitionParentIndexId		3323
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_partition.h b/src/include/catalog/pg_partition.h
new file mode 100644
index 0000000..aa89338
--- /dev/null
+++ b/src/include/catalog/pg_partition.h
@@ -0,0 +1,62 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 3321
+
+CATALOG(pg_partition,3321) BKI_WITHOUT_OIDS
+{
+	Oid			partrelid;		/* partition oid */
+	Oid			partparent;		/* parent oid */
+	bool		partvalid;		/* is partition valid */
+
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+	anyarray	partlistvals;	/* array of allowed values for the only
+								 * partition column */
+	anyarray	partrangemaxs;	/* array of rangemax values, one per
+								 * key column */
+#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					5
+#define Anum_pg_partition_partrelid			1
+#define Anum_pg_partition_partparent		2
+#define Anum_pg_partition_partvalid			3
+#define Anum_pg_partition_partlistvals		4
+#define Anum_pg_partition_partrangemaxs		5
+
+#endif   /* PG_PARTITION_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 1c961c7..600047b 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTRELID,
 	PARTEDRELIDLEVEL,
 	PROCNAMEARGSNSP,
 	PROCOID,
-- 
1.7.1

  [text/x-diff] 0006-Add-partition-creation-management-syntax.patch (39.6K, ../56C1302D.6070403@lab.ntt.co.jp/7-0006-Add-partition-creation-management-syntax.patch)
  download | inline diff:
From a136d87b072a40c045d16b74453ec30387d8c783 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 9 Feb 2016 10:55:48 +0900
Subject: [PATCH 06/10] Add partition creation/management syntax.

This adds following ALTER TABLE commands for adding/dropping/attaching/
detaching partitions and sub-partitions to partitioned tables:

  ADD PARTITION name FOR VALUES .. [ WITH (..) ] [ TABLESPACE .. ]
  MODIFY PARTITION partname ADD SUBPARTITION FOR VALUES ..
    [ WITH (..) ] [ TABLESPACE .. ]
  DROP PARTITION partname
  DROP SUBPARTITION subpartname
  ATTACH PARTITION name FOR VALUES .. USING [ TABLE ] table_name
    [ WITH (..) ] [ TABLESPACE .. ]
  MODIFY PARTITION partname ATTACH SUBPARTITION name FOR VALUES ..
    USING [ TABLE ] table_name [ WITH (..) ] [ TABLESPACE .. ]
  DETACH PARTITION partname [ USING [ TABLE ] table_name ]
  DROP SUBPARTITION subpartname [ USING [ TABLE ] table_name ]

Two of these commands (ATTACH PARTITION/SUBPARTITION) will not be
implemented right away and will output an "feature not supproted"
error message.

The commands don't yet actually create partitions/sub-partitions; that
will have to wait the next commit which will add infrastructure to add
partition info to the catalog.

Adds LESS, THAN, MODIFY as unreserved keywords and changes PARTITION
and SUBPARTITION from unreserved to reserved keywords.
---
 doc/src/sgml/ref/alter_table.sgml  |  107 +++++++++++++++++++
 src/backend/commands/tablecmds.c   |   32 ++++++
 src/backend/nodes/copyfuncs.c      |   34 ++++++
 src/backend/nodes/equalfuncs.c     |   30 +++++
 src/backend/nodes/outfuncs.c       |    1 +
 src/backend/parser/gram.y          |  188 +++++++++++++++++++++++++++++++--
 src/backend/parser/parse_agg.c     |   16 +++-
 src/backend/parser/parse_expr.c    |   11 ++
 src/backend/parser/parse_utilcmd.c |  206 ++++++++++++++++++++++++++++++++++++
 src/include/nodes/nodes.h          |    2 +
 src/include/nodes/parsenodes.h     |   37 ++++++-
 src/include/parser/kwlist.h        |    9 ++-
 src/include/parser/parse_node.h    |    3 +-
 13 files changed, 661 insertions(+), 15 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index aca40f5..e8067cb 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,12 +77,25 @@ 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 }
+    ADD PARTITION <replaceable class="PARAMETER">name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] )] [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+    MODIFY PARTITION <replaceable class="PARAMETER">partition_name</replaceable> ADD SUBPARTITION <replaceable class="PARAMETER">name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] )] [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+    DROP PARTITION <replaceable class="PARAMETER">name</replaceable>
+    DROP SUBPARTITION <replaceable class="PARAMETER">name</replaceable>
+    ATTACH PARTITION <replaceable class="PARAMETER">name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> USING [ TABLE ] <replaceable class="PARAMETER">table_name</replaceable> [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] )] [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+    MODIFY PARTITION <replaceable class="PARAMETER">partition_name</replaceable> ATTACH SUBPARTITION <replaceable class="PARAMETER">name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> USING [ TABLE ] <replaceable class="PARAMETER">table_name</replaceable> [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] )] [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+    DETACH PARTITION <replaceable class="PARAMETER">name</replaceable> [ USING [ TABLE ] <replaceable class="PARAMETER">table_name</replaceable>]
+    DETACH SUBPARTITION <replaceable class="PARAMETER">name</replaceable> [ USING [ TABLE ] <replaceable class="PARAMETER">table_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 ]
+
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+    FOR VALUES [ IN ] ( <replaceable class="PARAMETER">expression</replaceable> [, ...] )
+    FOR VALUES LESS THAN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] )
+
 </synopsis>
  </refsynopsisdiv>
 
@@ -704,6 +717,100 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ADD PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form adds named partition to a partitioned table. The <literal>
+      FOR VALUES </literal> specification must match the partition key
+      of the partitioned table. 
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>ADD SUBPARTITION</literal></term>
+    <listitem>
+     <para>
+      This form adds named sub-partition to specified partition of a
+      partitioned table. The <literal> FOR VALUES </literal> specification
+      must match the corresponding sub-partition key of the partitioned table.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>DROP PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form drops specified partition of a partitioned table.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>DROP SUBPARTITION</literal></term>
+    <listitem>
+     <para>
+      This form drops specified sub-partition of a partitioned table.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>ATTACH PARTITION USING TABLE</literal></term>
+    <listitem>
+     <para>
+      This form attaches a specified non-partitioned table as named partition
+      of a partitioned table provided it is not sub-partitioned. The <literal>
+      FOR VALUES </literal> specifiation must match the partition key of the
+      partitioned table.
+     </para>
+     <para>
+      There are a number of restrictions on the composition of source table
+      for it to be a candidate for attaching as a partition.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>ATTACH SUBPARTITION USING TABLE</literal></term>
+    <listitem>
+     <para>
+      This form attaches a specified non-partitioned table as named sub-partition
+      of specified partition of a partitioned table provided it is sub-partitioned.
+      The <literal> FOR VALUES </literal> specifiation must match the partition
+      key of the partitioned table.
+     </para>
+     <para>
+      There are a number of restrictions on the composition of source table
+      for it to be a candidate for attaching as a partition.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>DETACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form detaches specified partition of a partitioned table.
+      Optionally, one can specifiy target_name to rename the no longer
+      partition to.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>DETACH SUBPARTITION</literal></term>
+    <listitem>
+     <para>
+      This form detaches specified sub-partition of a partitioned table.
+      Optionally, one can specifiy target_name to rename the no longer
+      sub-partition to.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 674fbe5..7bdbdf0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3117,6 +3117,17 @@ AlterTableGetLockLevel(List *cmds)
 				cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
 				break;
 
+			case AT_AddPartition:
+			case AT_AddSubPartition:
+			case AT_DropPartition:
+			case AT_DropSubPartition:
+			case AT_AttachPartition:
+			case AT_AttachSubPartition:
+			case AT_DetachPartition:
+			case AT_DetachSubPartition:
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
 			default:			/* oops */
 				elog(ERROR, "unrecognized alter table type: %d",
 					 (int) cmd->subtype);
@@ -3434,6 +3445,18 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
+		case AT_AddPartition:
+		case AT_AddSubPartition:
+		case AT_DropPartition:
+		case AT_DropSubPartition:
+		case AT_AttachPartition:
+		case AT_AttachSubPartition:
+		case AT_DetachPartition:
+		case AT_DetachSubPartition:
+			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);
@@ -3757,6 +3780,15 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
+		case AT_AddPartition:
+		case AT_AddSubPartition:
+		case AT_DropPartition:
+		case AT_DropSubPartition:
+		case AT_AttachPartition:
+		case AT_AttachSubPartition:
+		case AT_DetachPartition:
+		case AT_DetachSubPartition:
+			break;
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 6b8eed8..aabe108 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3002,6 +3002,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_SCALAR_FIELD(oncommit);
 	COPY_STRING_FIELD(tablespacename);
 	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(is_internal_partition);
 }
 
 static CreateStmt *
@@ -4151,6 +4152,33 @@ _copyPartitionElem(const PartitionElem *from)
 	return newnode;
 }
 
+static PartitionStmt *
+_copyPartitionStmt(const PartitionStmt *from)
+{
+	PartitionStmt *newnode = makeNode(PartitionStmt);
+
+	COPY_NODE_FIELD(name);
+	COPY_NODE_FIELD(parent);
+	COPY_NODE_FIELD(using_table);
+	COPY_NODE_FIELD(values);
+	COPY_NODE_FIELD(options);
+	COPY_STRING_FIELD(tablespacename);
+
+	return newnode;
+}
+
+static PartitionValues *
+_copyPartitionValues(const PartitionValues *from)
+{
+	PartitionValues *newnode = makeNode(PartitionValues);
+
+	COPY_NODE_FIELD(listvalues);
+	COPY_NODE_FIELD(rangemaxs);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5044,6 +5072,12 @@ copyObject(const void *from)
 		case T_PartitionElem:
 			retval = _copyPartitionElem(from);
 			break;
+		case T_PartitionStmt:
+			retval = _copyPartitionStmt(from);
+			break;
+		case T_PartitionValues:
+			retval = _copyPartitionValues(from);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index befff43..cfb5db6 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1160,6 +1160,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_SCALAR_FIELD(oncommit);
 	COMPARE_STRING_FIELD(tablespacename);
 	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(is_internal_partition);
 
 	return true;
 }
@@ -2616,6 +2617,29 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
 	return true;
 }
 
+static bool
+_equalPartitionStmt(const PartitionStmt *a, const PartitionStmt *b)
+{
+	COMPARE_NODE_FIELD(name);
+	COMPARE_NODE_FIELD(parent);
+	COMPARE_NODE_FIELD(using_table);
+	COMPARE_NODE_FIELD(values);
+	COMPARE_NODE_FIELD(options);
+	COMPARE_STRING_FIELD(tablespacename);
+
+	return true;
+}
+
+static bool
+_equalPartitionValues(const PartitionValues *a, const PartitionValues *b)
+{
+	COMPARE_NODE_FIELD(listvalues);
+	COMPARE_NODE_FIELD(rangemaxs);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3366,6 +3390,12 @@ equal(const void *a, const void *b)
 		case T_PartitionElem:
 			retval = _equalPartitionElem(a, b);
 			break;
+		case T_PartitionStmt:
+			retval = _equalPartitionStmt(a, b);
+			break;
+		case T_PartitionValues:
+			retval = _equalPartitionValues(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 9baf7cd..00b449d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2170,6 +2170,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_ENUM_FIELD(oncommit, OnCommitAction);
 	WRITE_STRING_FIELD(tablespacename);
 	WRITE_BOOL_FIELD(if_not_exists);
+	WRITE_BOOL_FIELD(is_internal_partition);
 }
 
 static void
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6e3471d..902bd40 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -226,8 +226,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
-	PartitionElem		*pelem;
+	PartitionElem		*partelem;
 	PartitionBy			*partby;
+	PartitionValues		*partvalues;
 }
 
 %type <node>	stmt schema_stmt
@@ -541,8 +542,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean> opt_if_not_exists
 %type <list>	OptPartitionKey PartitionKey
 %type <partby>	PartitionBy SubPartitionBy
-%type <pelem>	part_elem
-%type <list>	part_params
+%type <partelem>	part_elem
+%type <list>		part_params
+%type <partvalues>	ForValues
+%type <list>		ValuesList
+%type <range>		using_table opt_using_table
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -568,7 +572,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
@@ -583,7 +587,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
 	DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
-	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
+	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC 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
@@ -607,10 +611,11 @@ 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 LIST LISTEN LOAD LOCAL
+	LEADING LEAKPROOF LEAST LEFT LESS LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
-	MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
+	MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODIFY MODE MONTH_P
+	MOVE
 
 	NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NONE
 	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
@@ -636,7 +641,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBPARTITION
 	SUBSTRING SYMMETRIC SYSID SYSTEM_P
 
-	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
+	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THAN THEN
 	TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM TREAT TRIGGER TRIM TRUE_P
 	TRUNCATE TRUSTED TYPE_P TYPES_P
 
@@ -2370,6 +2375,124 @@ alter_table_cmd:
 					n->def = (Node *)$1;
 					$$ = (Node *) n;
 				}
+			/* ALTER TABLE <name> ADD PARTITION */
+			| ADD_P PARTITION qualified_name ForValues OptWithPartition
+			  OptTableSpace
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionStmt *stmt = makeNode(PartitionStmt);
+
+					n->subtype = AT_AddPartition;
+					stmt->name = $3;
+					stmt->values = $4;
+					stmt->options = $5;
+					stmt->tablespacename = $6;
+					n->def = (Node *) stmt;
+					$$ = (Node *) n;
+				}
+			/* ALTER TABLE <name> MODIFY PARTITION ADD SUBPARTITION */
+			| MODIFY PARTITION qualified_name ADD_P SUBPARTITION qualified_name
+			  ForValues OptWithPartition OptTableSpace
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionStmt *stmt = makeNode(PartitionStmt);
+
+					n->subtype = AT_AddSubPartition;
+					stmt->name = $6;
+					stmt->parent = $3;
+					stmt->values = $7;
+					stmt->options = $8;
+					stmt->tablespacename = $9;
+					n->def = (Node *) stmt;
+
+					$$ = (Node *) n;
+				}
+			/* ALTER TABLE <name> DROP PARTITION */
+			| DROP PARTITION qualified_name
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropPartition;
+					n->def = (Node *) $3;
+					$$ = (Node *) n;
+				}
+			/* ALTER TABLE <name> DROP SUBPARTITION */
+			| DROP SUBPARTITION qualified_name
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropSubPartition;
+					n->def = (Node *) $3;
+					$$ = (Node *) n;
+				}
+			/* ALTER TABLE <name> ATTACH PARTITION USING TABLE */
+			| ATTACH PARTITION qualified_name ForValues using_table OptWithPartition
+			  OptTableSpace
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionStmt *stmt = makeNode(PartitionStmt);
+
+					n->subtype = AT_AttachPartition;
+					stmt->name = $3;
+					stmt->values = $4;
+					stmt->using_table = $5;
+					stmt->options = $6;
+					stmt->tablespacename = $7;
+					n->def = (Node *) stmt;
+
+					$$ = (Node *) n;
+				}
+			/* ALTER TABLE <name> MODIFY PARTITION ATTACH SUBPARTITION USING TABLE */
+			| MODIFY PARTITION qualified_name ATTACH SUBPARTITION qualified_name
+			  ForValues using_table OptWithPartition OptTableSpace
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionStmt *stmt = makeNode(PartitionStmt);
+
+					n->subtype = AT_AttachSubPartition;
+					stmt->name = $6;
+					stmt->parent = $3;
+					stmt->values = $7;
+					stmt->using_table = $8;
+					stmt->options = $9;
+					stmt->tablespacename = $10;
+					n->def = (Node *) stmt;
+
+					$$ = (Node *) n;
+				}
+			/* ALTER TABLE <name> DETACH PARTITION [ USING TABLE ] */
+			| DETACH PARTITION qualified_name opt_using_table
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionStmt *stmt = makeNode(PartitionStmt);
+
+					n->subtype = AT_DetachPartition;
+					stmt->name = $3;
+					stmt->using_table = $4;
+					n->def = (Node *) stmt;
+
+					$$ = (Node *) n;
+				}
+			/* ALTER TABLE <name> DETACH SUBPARTITION [ USING TABLE ] */
+			| DETACH SUBPARTITION qualified_name opt_using_table
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionStmt *stmt = makeNode(PartitionStmt);
+
+					n->subtype = AT_DetachSubPartition;
+					stmt->name = $3;
+					stmt->using_table = $4;
+					n->def = (Node *) stmt;
+
+					$$ = (Node *) n;
+				}
+		;
+
+opt_using_table:
+			using_table							{ $$ = $1; }
+			| /* EMPTY */						{ $$ = NULL; }
+		;
+using_table:
+			/* USING [ TABLE ] qualified_name */
+			USING opt_table qualified_name		{ $$ = $3; }
 		;
 
 alter_column_default:
@@ -2465,6 +2588,40 @@ reloption_elem:
 				}
 		;
 
+ForValues:
+			/* LIST partition */
+			FOR VALUES opt_in '(' ValuesList ')'
+				{
+					PartitionValues *n = makeNode(PartitionValues);
+
+					n->listvalues = $5;
+					n->rangemaxs = NIL;
+					n->location = @1;
+
+					$$ = n;
+				}
+
+			/* RANGE partition */
+			| FOR VALUES LESS THAN '(' ValuesList ')'
+				{
+					PartitionValues *n = makeNode(PartitionValues);
+
+					n->listvalues = NIL;
+					n->rangemaxs = $6;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+opt_in:		IN_P				{}
+			|   /* EMPTY */		{}
+		;
+
+ValuesList:
+			a_expr						{ $$ = list_make1($1); }
+			| ValuesList ',' a_expr		{ $$ = lappend($1, $3); }
+		;
 
 /*****************************************************************************
  *
@@ -3525,6 +3682,12 @@ OptWith:
 			| /*EMPTY*/					{ $$ = NIL; }
 		;
 
+/* WITH (options) only for partitions */
+OptWithPartition:
+			WITH reloptions				{ $$ = $2; }
+			| /*EMPTY*/					{ $$ = NIL; }
+		;
+
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
 			| ON COMMIT DELETE_P ROWS		{ $$ = ONCOMMIT_DELETE_ROWS; }
 			| ON COMMIT PRESERVE ROWS		{ $$ = ONCOMMIT_PRESERVE_ROWS; }
@@ -13767,6 +13930,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13812,6 +13976,7 @@ unreserved_keyword:
 			| DELETE_P
 			| DELIMITER
 			| DELIMITERS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
@@ -13872,6 +14037,7 @@ unreserved_keyword:
 			| LARGE_P
 			| LAST_P
 			| LEAKPROOF
+			| LESS
 			| LEVEL
 			| LIST
 			| LISTEN
@@ -13888,6 +14054,7 @@ unreserved_keyword:
 			| MINUTE_P
 			| MINVALUE
 			| MODE
+			| MODIFY
 			| MONTH_P
 			| MOVE
 			| NAME_P
@@ -13912,7 +14079,6 @@ unreserved_keyword:
 			| PARALLEL
 			| PARSER
 			| PARTIAL
-			| PARTITION
 			| PASSING
 			| PASSWORD
 			| PLANS
@@ -13980,7 +14146,6 @@ unreserved_keyword:
 			| STORAGE
 			| STRICT_P
 			| STRIP_P
-			| SUBPARTITION
 			| SYSID
 			| SYSTEM_P
 			| TABLES
@@ -13989,6 +14154,7 @@ unreserved_keyword:
 			| TEMPLATE
 			| TEMPORARY
 			| TEXT_P
+			| THAN
 			| TRANSACTION
 			| TRANSFORM
 			| TRIGGER
@@ -14185,6 +14351,7 @@ reserved_keyword:
 			| ONLY
 			| OR
 			| ORDER
+			| PARTITION
 			| PLACING
 			| PRIMARY
 			| REFERENCES
@@ -14192,6 +14359,7 @@ reserved_keyword:
 			| SELECT
 			| SESSION_USER
 			| SOME
+			| SUBPARTITION
 			| SYMMETRIC
 			| TABLE
 			| THEN
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 3d465c0..5420b20 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -458,7 +458,11 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 
 			break;
 		case EXPR_KIND_PARTKEY_EXPRESSION:
-			err = _("aggregate functions are not allowed in partition key expressions");
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expressions");
+			else
+				err = _("grouping operations are not allowed in partition key expressions");
+
 			break;
 		case EXPR_KIND_INDEX_PREDICATE:
 			if (isAgg)
@@ -488,6 +492,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_VALUE:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition value expressions");
+			else
+				err = _("grouping operations are not allowed in partition value expressions");
+
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -848,6 +859,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_VALUE:
+			err = _("window functions are not allowed in partition value expressions");
+			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 b115f66..49fca98 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -514,6 +514,12 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 			return node;
 	}
 
+	if (pstate->p_expr_kind == EXPR_KIND_PARTITION_VALUE)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot use column reference in partition value expression"),
+				 parser_errposition(pstate, cref->location)));
+
 	/*----------
 	 * The allowed syntaxes are:
 	 *
@@ -1718,6 +1724,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_VALUE:
+			err = _("cannot use subquery in partition value expressions");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3230,6 +3239,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "WHEN";
 		case EXPR_KIND_PARTKEY_EXPRESSION:
 			return "partition key expression";
+		case EXPR_KIND_PARTITION_VALUE:
+			return "partition value 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 02655da..ce8ed33 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -49,6 +49,7 @@
 #include "nodes/nodeFuncs.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"
@@ -130,6 +131,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 transformAddPartition(CreateStmtContext *cxt, PartitionStmt *stmt);
+static PartitionValues *transformPartitionValues(CreateStmtContext *cxt,
+						PartitionKey key,
+						PartitionValues *values);
 
 
 /*
@@ -2660,6 +2665,41 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					break;
 				}
 
+			case AT_AddPartition:
+			case AT_AddSubPartition:
+				transformAddPartition(&cxt, (PartitionStmt *) cmd->def);
+				newcmds = lappend(newcmds, cmd);
+				break;
+			case AT_DropPartition:
+			case AT_DropSubPartition:
+				break;
+			case AT_AttachPartition:
+			case AT_AttachSubPartition:
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("ATTACH PARTITION/SUBPARTITION USING TABLE not implemented")));
+				break;
+			case AT_DetachPartition:
+			case AT_DetachSubPartition:
+				{
+					PartitionStmt *partstmt = (PartitionStmt *) cmd->def;
+
+					if (partstmt->using_table)
+					{
+						RenameStmt *stmt = makeNode(RenameStmt);
+
+						stmt->renameType = OBJECT_TABLE;
+						stmt->relation = partstmt->name;
+						stmt->newname = partstmt->using_table->relname;
+						stmt->missing_ok = false;
+
+						cxt.alist = lappend(cxt.alist, stmt);
+					}
+
+					newcmds = lappend(newcmds, cmd);
+					break;
+				}
+
 			default:
 				newcmds = lappend(newcmds, cmd);
 				break;
@@ -3024,3 +3064,169 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
 						"different from the one being created (%s)",
 						*stmt_schema_name, context_schema)));
 }
+
+/*
+ * transformAddPartition
+ *		Transforms ADD PARTITION / SUBPARTITION
+ *
+ * Note that cxt->relation corresponds to the root partitioned table.
+ */
+static void
+transformAddPartition(CreateStmtContext *cxt, PartitionStmt *stmt)
+{
+	Relation		parentRel;
+	bool			is_subpartition = stmt->parent != NULL;
+	PartitionKey	key;
+
+	/* Check if the target table is partitioned at all */
+	if (cxt->rel->rd_partkeys == NIL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+
+	/* If adding a sub-partition, check if we can do so */
+	if (is_subpartition && list_length(cxt->rel->rd_partkeys) < 2)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot add sub-partition"),
+				 errdetail("\"%s\" is not sub-partitioned", cxt->relation->relname)));
+
+	if (stmt->parent)
+	{
+		parentRel = heap_openrv_extended(stmt->parent, NoLock, true);
+
+		if (parentRel == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("partition \"%s\" of \"%s\" does not exist",
+							stmt->parent->relname, cxt->relation->relname),
+						parser_errposition(cxt->pstate, stmt->parent->location)));
+	}
+	else
+		parentRel = heap_openrv(cxt->relation, NoLock);
+
+	/* tranform FOR VALUES clause */
+	key = !is_subpartition ? linitial(cxt->rel->rd_partkeys)
+									: lsecond(cxt->rel->rd_partkeys);
+	stmt->values = transformPartitionValues(cxt, key, stmt->values);
+
+	heap_close(parentRel, NoLock);
+}
+
+/*
+ * transformPartitionValues
+ *
+ * Transform partition value as returned by the grammar into something
+ * we can evaluate to store into pg_partition.
+ */
+static PartitionValues *
+transformPartitionValues(CreateStmtContext *cxt, PartitionKey key,
+						 PartitionValues *values)
+{
+	int			i;
+	ListCell   *cell;
+	PartitionKeyTypeInfo *typinfo;
+	PartitionValues		*result = (PartitionValues *)
+											makeNode(PartitionValues);
+
+	typinfo = get_partition_key_type_info(cxt->rel, key);
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			if (!values->listvalues)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("invalid FOR VALUES specification for a list partition"),
+						 parser_errposition(cxt->pstate, values->location)));
+
+			foreach(cell, values->listvalues)
+			{
+				Node   *value = (Node *) lfirst(cell);
+				Node   *orig_value = value;
+				Oid		valuetype;
+
+				value = transformExpr(cxt->pstate, value,
+										EXPR_KIND_PARTITION_VALUE);
+				valuetype = exprType(value);
+				value = coerce_to_target_type(cxt->pstate,
+											value, valuetype,
+											typinfo->typid[0],
+											typinfo->typmod[0],
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+				if (value == NULL)
+					ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("partition key column is of type %s"
+								" but expression is of type %s",
+									format_type_be(typinfo->typid[0]),
+									format_type_be(valuetype)),
+						 errhint("You will need to rewrite or cast the expression."),
+						 parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+				result->listvalues = lappend(result->listvalues, value);
+			}
+			break;
+
+		case PARTITION_STRAT_RANGE:
+			if (!values->rangemaxs)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid FOR VALUES specification for a range partition"),
+					 parser_errposition(cxt->pstate, values->location)));
+
+			if (list_length(values->rangemaxs) > key->partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("range partition FOR VALUES specification has more"
+							" expressions than columns in the partition key"),
+							parser_errposition(cxt->pstate,
+									exprLocation(list_nth(values->rangemaxs,
+												 key->partnatts)))));
+			else if (list_length(values->rangemaxs) < key->partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("range partition FOR VALUES specification has fewer"
+							" expressions than columns in the partition key"),
+							parser_errposition(cxt->pstate,
+									exprLocation(list_nth(values->rangemaxs,
+												 list_length(values->rangemaxs))))));
+			i = 0;
+			foreach(cell, values->rangemaxs)
+			{
+				Node   *value = (Node *) lfirst(cell);
+				Node   *orig_value = value;
+				Oid		valuetype;
+
+				value = transformExpr(cxt->pstate, value,
+										EXPR_KIND_PARTITION_VALUE);
+				valuetype = exprType(value);
+				value = coerce_to_target_type(NULL,
+											value, valuetype,
+											typinfo->typid[i],
+											typinfo->typmod[i],
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+				if (value == NULL)
+					ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("partition key column is of type %s"
+								" but expression is of type %s",
+									format_type_be(typinfo->typid[0]),
+									format_type_be(valuetype)),
+						 errhint("You will need to rewrite or cast the expression."),
+						 parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+				result->rangemaxs = lappend(result->rangemaxs, value);
+				++i;
+			}
+			break;
+	}
+
+	result->location = values->location;
+
+	return result;
+}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 825f7d9..8032f0d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -435,6 +435,8 @@ typedef enum NodeTag
 	T_RoleSpec,
 	T_PartitionElem,
 	T_PartitionBy,
+	T_PartitionValues,
+	T_PartitionStmt,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 130410c..8128226 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -731,6 +731,31 @@ typedef struct PartitionBy
 	int			location;	/* token location, or -1 if unknown */
 } PartitionBy;
 
+/*
+ * PartitionValues - partition FOR VALUES descriptor
+ */
+typedef struct PartitionValues
+{
+    NodeTag     type;
+    List       *listvalues;
+    List       *rangemaxs;
+    int         location;   /* token location, or -1 if unknown */
+} PartitionValues;
+
+/*
+ * PartitionStmt -	create new partition
+ */
+typedef struct PartitionStmt
+{
+    NodeTag          type;
+    RangeVar        *name;				/* partition name */
+    RangeVar		*parent;     		/* RangeVar node for the parent */
+	RangeVar		*using_table;		/* USING TABLE <name> */
+    PartitionValues *values;            /* FOR VALUES clause descriptor */
+    List            *options;           /* options from WITH clause */
+    char            *tablespacename;    /* table space to use, or NULL */
+} PartitionStmt;
+
 /****************************************************************************
  *	Nodes for a Query tree
  ****************************************************************************/
@@ -1557,7 +1582,16 @@ 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_AddPartition,			/* ADD PARTITION ... */
+	AT_AddSubPartition,			/* MODIFY PARTITION ... ADD SUBPARTITION */
+	AT_DropPartition,			/* DROP PARTITION */
+	AT_DropSubPartition,		/* DROP SUBPARTITION */
+	AT_AttachPartition,			/* ATTACH PARTITION .. USING TABLE .. */
+	AT_AttachSubPartition,		/* MODIFY PARTITION ..
+								 * ATTACH SUBPARTITION .. USING TABLE .. */
+	AT_DetachPartition,			/* DETACH PARTITION .. USING TABLE .. */
+	AT_DetachSubPartition		/* DETACH SUBPARTITION .. USING TABLE .. */
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -1790,6 +1824,7 @@ typedef struct CreateStmt
 	OnCommitAction oncommit;	/* what do we do at COMMIT? */
 	char	   *tablespacename; /* table space to use, or NULL */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		is_internal_partition;	/* relation is internal partition? */
 } CreateStmt;
 
 /* ----------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 38c7afe..f56eaee 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)
@@ -126,6 +127,7 @@ PG_KEYWORD("delete", DELETE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
 PG_KEYWORD("delimiters", DELIMITERS, 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)
@@ -223,6 +225,7 @@ PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD)
 PG_KEYWORD("leakproof", LEAKPROOF, UNRESERVED_KEYWORD)
 PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD)
 PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
+PG_KEYWORD("less", LESS, UNRESERVED_KEYWORD)
 PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
 PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
@@ -243,6 +246,7 @@ PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
 PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
 PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD)
+PG_KEYWORD("modify", MODIFY, UNRESERVED_KEYWORD)
 PG_KEYWORD("month", MONTH_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("move", MOVE, UNRESERVED_KEYWORD)
 PG_KEYWORD("name", NAME_P, UNRESERVED_KEYWORD)
@@ -285,7 +289,7 @@ PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD)
 PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD)
 PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD)
 PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
-PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
+PG_KEYWORD("partition", PARTITION, RESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
@@ -368,7 +372,7 @@ PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD)
 PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD)
 PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD)
-PG_KEYWORD("subpartition", SUBPARTITION, UNRESERVED_KEYWORD)
+PG_KEYWORD("subpartition", SUBPARTITION, RESERVED_KEYWORD)
 PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD)
@@ -381,6 +385,7 @@ PG_KEYWORD("temp", TEMP, UNRESERVED_KEYWORD)
 PG_KEYWORD("template", TEMPLATE, UNRESERVED_KEYWORD)
 PG_KEYWORD("temporary", TEMPORARY, UNRESERVED_KEYWORD)
 PG_KEYWORD("text", TEXT_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("than", THAN, UNRESERVED_KEYWORD)
 PG_KEYWORD("then", THEN, RESERVED_KEYWORD)
 PG_KEYWORD("time", TIME, COL_NAME_KEYWORD)
 PG_KEYWORD("timestamp", TIMESTAMP, COL_NAME_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f6f3f52..054cd37 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_PARTKEY_EXPRESSION	/* partition key expression */
+	EXPR_KIND_PARTKEY_EXPRESSION,	/* partition key expression */
+	EXPR_KIND_PARTITION_VALUE
 } ParseExprKind;
 
 
-- 
1.7.1

  [text/x-diff] 0007-Add-infrastructure-to-actually-create-and-manage-par.patch (81.0K, ../56C1302D.6070403@lab.ntt.co.jp/8-0007-Add-infrastructure-to-actually-create-and-manage-par.patch)
  download | inline diff:
From 6c56eb3caf49ac07795809e2e169753cd1388a41 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 10 Feb 2016 16:36:16 +0900
Subject: [PATCH 07/10] Add infrastructure to actually create and manage partitions.

This primarily extends catalog/partition.h API to include functions for
storing and removing pg_partition entries. It further includes inquiry
functions for perusing information stored in pg_partition.

catalog/heap.c now knows a couple of new things: to drop partitions along
with a partitioned heap, and to distinguish RELKIND_RELATION relations
that are "internal partitions" from those that are not.

tablecmds.c now implements AT commands for which syntax was added in the
last commit like adding, dropping, detaching partitions/sub-partitions. When
adding a partition, a pg_class relation with matching tuple descriptor
(including dropped columns) is first created followed by creating necessary
pg_partition entry. Dropping has a similar story. Detach simply drops the
pg_partition entry so that it is no longer returned as a partition for
its parent. Attaching table as a partition is not implemented.

Certain AT commands are outright disallowed for partitions. Those include
add/drop/rename column, set/drop not null, alter column type, add check
constraint. Further, all of these commands, except the last, when perfomed
on a partitioned table are recursively applied to all of its partitions.
OTOH, when a check constraint is added or a not valid constraint validated,
it is "checked" for rows in its leaf partitions.

COPY FROM, INSERT into and UPDATE on partitions is disallowed.

Finally, one can request a PartitionDesc on a partitioned table (top-level
or internal) which consists of C array of values of its partitions.
If list partitioned, values are pointers to C arrays of literal values per
partition, and if range partitioned, values are individual rangemax values
themselves (sorted in ascending order). In the range partitioned case, there
are as many arrays as number of key columns. It also contains an array of
partition OIDs (laid out in order of values). The resulting structure is
cached in rd_partdesc. Currently, the only user of this data structure is
add partition command which uses it to verify that new partition does not
overlap with existing partitions.
---
 src/backend/access/common/tupdesc.c        |    6 +
 src/backend/bootstrap/bootparse.y          |    6 +-
 src/backend/catalog/heap.c                 |   28 +-
 src/backend/catalog/index.c                |    5 +-
 src/backend/catalog/partition.c            |  999 +++++++++++++++++++++++++++-
 src/backend/catalog/toasting.c             |    3 +-
 src/backend/commands/cluster.c             |    5 +-
 src/backend/commands/copy.c                |   16 +-
 src/backend/commands/tablecmds.c           |  694 +++++++++++++++++++-
 src/backend/executor/execMain.c            |   25 +
 src/backend/nodes/copyfuncs.c              |    2 +
 src/backend/nodes/equalfuncs.c             |    2 +
 src/backend/nodes/outfuncs.c               |    2 +
 src/backend/parser/gram.y                  |    1 +
 src/backend/parser/parse_utilcmd.c         |  109 +++-
 src/include/catalog/heap.h                 |    6 +-
 src/include/catalog/partition.h            |   55 ++
 src/include/nodes/parsenodes.h             |    5 +
 src/include/utils/rel.h                    |    2 +
 src/test/regress/expected/sanity_check.out |    1 +
 20 files changed, 1919 insertions(+), 53 deletions(-)

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index b56d0e3..af79a5a 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -648,6 +648,12 @@ BuildDescForRelation(List *schema)
 		has_not_null |= entry->is_not_null;
 		desc->attrs[attnum - 1]->attislocal = entry->is_local;
 		desc->attrs[attnum - 1]->attinhcount = entry->inhcount;
+
+		if (entry->is_dropped_copy)
+		{
+			Assert(entry->is_for_partition);
+			desc->attrs[attnum - 1]->attisdropped = true;
+		}
 	}
 
 	if (has_not_null)
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..f2c2ebb 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -224,7 +224,8 @@ Boot_CreateStmt:
 												   RELPERSISTENCE_PERMANENT,
 												   shared_relation,
 												   mapped_relation,
-												   true);
+												   true,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
@@ -251,7 +252,8 @@ Boot_CreateStmt:
 													  false,
 													  true,
 													  false,
-													  NULL);
+													  NULL,
+													  false);
 						elog(DEBUG4, "relation created with OID %u", id);
 					}
 					do_end();
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 1194611..e4e69f0 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -252,7 +252,8 @@ heap_create(const char *relname,
 			char relpersistence,
 			bool shared_relation,
 			bool mapped_relation,
-			bool allow_system_table_mods)
+			bool allow_system_table_mods,
+			bool is_internal_partition)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -285,6 +286,9 @@ heap_create(const char *relname,
 	 */
 	switch (relkind)
 	{
+		case RELKIND_RELATION:
+			create_storage = !is_internal_partition;
+			break;
 		case RELKIND_VIEW:
 		case RELKIND_COMPOSITE_TYPE:
 		case RELKIND_FOREIGN_TABLE:
@@ -1045,7 +1049,8 @@ heap_create_with_catalog(const char *relname,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
-						 ObjectAddress *typaddress)
+						 ObjectAddress *typaddress,
+						 bool is_internal_partition)
 {
 	Relation	pg_class_desc;
 	Relation	new_rel_desc;
@@ -1179,7 +1184,8 @@ heap_create_with_catalog(const char *relname,
 							   relpersistence,
 							   shared_relation,
 							   mapped_relation,
-							   allow_system_table_mods);
+							   allow_system_table_mods,
+							   is_internal_partition);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1824,12 +1830,26 @@ heap_drop_with_catalog(Oid relid)
 		RemovePartitionKeysByRelId(relid);
 
 	/*
+	 * If a partition, delete the pg_partition tuple.
+	 */
+	if (relid_is_partition(relid))
+		RemovePartitionEntryByRelId(relid);
+
+	/*
+	 * If partitioned (either a RELKIND_PARTITIONED_REL relation or an internal
+	 * partition), drop partitions.
+	 */
+	if (relid_is_partitioned(relid))
+		RelationDropPartitions(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
-		rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
+		!relid_is_internal_partition(relid))
 	{
 		RelationDropStorage(rel);
 	}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index e93aad4..e21a7a4 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -844,7 +844,8 @@ index_create(Relation heapRelation,
 								relpersistence,
 								shared_relation,
 								mapped_relation,
-								allow_system_table_mods);
+								allow_system_table_mods,
+								false);
 
 	Assert(indexRelationId == RelationGetRelid(indexRelation));
 
@@ -3498,7 +3499,7 @@ reindex_relation(Oid relid, int flags, int options)
 	 */
 	rel = heap_open(relid, ShareLock);
 
-	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+	if (relid_is_partitioned(relid))
 		ereport(ERROR,
 					 (errmsg("cannot reindex partitioned tables")));
 
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 125f0a1..33a16eb 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -24,6 +24,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partition.h"
 #include "catalog/pg_partitioned_rel.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
@@ -39,6 +40,42 @@
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
+#define compare_using_func(func, val1, val2)\
+							DatumGetInt32(FunctionCall2Coll(&(func),\
+										  DEFAULT_COLLATION_OID,\
+										  (val1), (val2)))
+
+/*
+ * Information about a single partition
+ */
+typedef struct PartitionInfo
+{
+	Oid		oid;			/* Partition relation OID */
+	bool	valid;			/* is partition valid? */
+	int		partnatts;		/* qsort_arg's callback function depends on this */
+	int		listnvalues;	/* How many values does listvalues below hold */
+	Datum  *listvalues;		/* List partition values */
+	Datum  *rangemaxs;		/* Range partition max bound - one datum per
+							   key column */
+} PartitionInfo;
+
+static int32 range_partition_cmp_max(const void *a, const void *b,
+					void *arg);
+static PartitionDesc CopyPartitionDesc(PartitionDesc src, int partnatts,
+					char strategy, PartitionKeyTypeInfo *typinfo);
+static PartitionInfo **GetRelPartitions(Relation rel, PartitionKey key,
+					PartitionKeyTypeInfo *typinfo, bool only_valid,
+					int *numparts);
+static void free_partitions(PartitionInfo **p, int num);
+static int	oid_cmp(const void *p1, const void *p2);
+static List *get_leaf_partitions_recurse(Oid relid, int lockmode,
+					int nlevels, int levels_down);
+static int get_partition_level_internal(Oid relid, int level);
+static int32 rangemaxs_cmp_datum(const Datum *vals,
+					const Datum *rangemaxs,
+					int partnatts,
+					FmgrInfo *partsupfunc);
+
 /*
  * StorePartitionKey
  *		Store the partition keys of rel into pg_partitioned_rel catalog
@@ -193,7 +230,8 @@ relid_is_partitioned(Oid relid)
 	relkind = ((Form_pg_class) GETSTRUCT(tuple))->relkind;
 	ReleaseSysCache(tuple);
 
-	return relkind == RELKIND_PARTITIONED_REL;
+	return relkind == RELKIND_PARTITIONED_REL ||
+			relid_is_internal_partition(relid);
 }
 
 /*
@@ -382,11 +420,11 @@ FreePartitionKeys(List *keys)
 
 /*
  * get_partition_key_type_info
+ *		Returns a PartitionKeyTypeInfo object that includes type info
+ *		for individual partition key attributes.
  *
- * Returns a PartitionKeyTypeInfo object that includes type info
- * for individual partition key attributes.
- *
- * Separated out to avoid repeating the code.
+ * Note to callers: make sure to call this on root parents (that is,
+ * RELKIND_PARTITIONED_REL relations)
  */
 PartitionKeyTypeInfo *
 get_partition_key_type_info(Relation rel, PartitionKey key)
@@ -396,6 +434,8 @@ get_partition_key_type_info(Relation rel, PartitionKey key)
 	ListCell   *partexprs_item;
 	PartitionKeyTypeInfo *result;
 
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL);
+
 	partexprs = RelationGetPartitionExpr(rel, key->level);
 
 	result = (PartitionKeyTypeInfo *) palloc0(sizeof(PartitionKeyTypeInfo));
@@ -448,3 +488,952 @@ free_partition_key_type_info(PartitionKeyTypeInfo *typinfo)
 	pfree(typinfo->typalign);
 	pfree(typinfo);
 }
+
+/*
+ * StorePartition
+ *		Store information of new partition of rel into pg_partition
+ *
+ * Note that 'rel' always corresponds to the root partitioned table while
+ * 'parentOid' is OID of the actual parent of 'partition'.
+ */
+void
+StorePartition(Relation rel, PartitionKey key, PartitionKeyTypeInfo *typinfo,
+				Oid partitionOid,
+				Oid parentOid,
+				bool valid,
+				int listnvalues,
+				Datum *boundDatums)
+{
+	Relation		pg_partition;
+	HeapTuple		tuple;
+	ArrayType	   *arr_listvalues = NULL;
+	ArrayType	   *arr_rangemaxs = NULL;
+	Datum			values[Natts_pg_partition];
+	bool			nulls[Natts_pg_partition];
+
+	pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);
+
+	/* Build the tuple */
+	MemSet(nulls, false, sizeof(nulls));
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			Assert(listnvalues > 0);
+			arr_listvalues = construct_array(boundDatums,
+										 listnvalues,
+										 typinfo->typid[0],
+										 typinfo->typlen[0],
+										 typinfo->typbyval[0],
+										 typinfo->typalign[0]);
+			/* No range bounds */
+			nulls[Anum_pg_partition_partrangemaxs - 1] = true;
+			break;
+
+		case PARTITION_STRAT_RANGE:
+			arr_rangemaxs = construct_array(boundDatums, key->partnatts,
+										 ANYARRAYOID, -1, false, 'd');
+
+			/* No list values */
+			nulls[Anum_pg_partition_partlistvals - 1] = true;
+			break;
+	}
+
+	values[Anum_pg_partition_partrelid - 1] = ObjectIdGetDatum(partitionOid);
+	values[Anum_pg_partition_partparent - 1] = ObjectIdGetDatum(parentOid);
+	values[Anum_pg_partition_partvalid - 1] = BoolGetDatum(valid);
+	values[Anum_pg_partition_partlistvals - 1] = PointerGetDatum(arr_listvalues);
+	values[Anum_pg_partition_partrangemaxs - 1] = PointerGetDatum(arr_rangemaxs);
+
+	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);
+
+	heap_close(pg_partition, RowExclusiveLock);
+}
+
+/*
+ *  RemovePartitionByRelId
+ *		Remove a pg_partition entry for a partition.
+ */
+void
+RemovePartitionEntryByRelId(Oid relid)
+{
+	Relation		partrel;
+	HeapTuple		tuple;
+
+	partrel = heap_open(PartitionRelationId, RowExclusiveLock);
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition %u", relid);
+
+	simple_heap_delete(partrel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+	heap_close(partrel, RowExclusiveLock);
+}
+
+/*
+ * RelationDropPartitions
+ *		Find and drop all partitions of relation with OID 'relid'
+ */
+void
+RelationDropPartitions(Oid relid)
+{
+	List	   *partitionOids;
+	ListCell   *cell;
+
+	partitionOids = get_partitions(relid, AccessExclusiveLock);
+
+	foreach (cell, partitionOids)
+	{
+		Oid		partitionOid = lfirst_oid(cell);
+		ObjectAddress	obj;
+
+		obj.classId = RelationRelationId;
+		obj.objectId = partitionOid;
+		obj.objectSubId = 0;
+
+		performDeletion(&obj, DROP_CASCADE, 0);
+	}
+}
+
+/*
+ * RelationGetPartitionDesc
+ *		Get the PartitionDesc of a partitioned table
+ *
+ * The returned struct consists of array of OIDs of partitions, array of
+ * datums corresponding to the values of partition bounds and related info.
+ * Memory for the struct is allocated in the caller's memory context while
+ * a copy is cached in rd_partdesc so that we don't compute the result
+ * unnecessarily (We don't want to return a pointer to the relcache copy
+ * though, since it could disappear due to relcache invalidation.)
+ */
+PartitionDesc
+RelationGetPartitionDesc(Relation relation, PartitionKey key,
+						 PartitionKeyTypeInfo *typinfo, bool include_all)
+{
+	int		i,
+			j;
+	int		numparts = 0;
+	MemoryContext	oldcxt;
+	PartitionDesc	result;
+	PartitionInfo **partitions;
+
+	/* Quick copy and exit if already computed the descriptor */
+	if (relation->rd_partdesc != NULL)
+		return CopyPartitionDesc(relation->rd_partdesc,
+								 key->partnatts,
+								 key->strategy,
+								 typinfo);
+
+	result = (PartitionDesc) palloc0(sizeof(PartitionDescData));
+
+	partitions = GetRelPartitions(relation, key, typinfo, !include_all,
+								  &numparts);
+	result->numparts = numparts;
+
+	if (partitions)
+	{
+		result->oids = (Oid *) palloc0(numparts * sizeof(Oid));
+		switch (key->strategy)
+		{
+			case PARTITION_STRAT_LIST:
+				result->listnvalues = (int *) palloc0(numparts * sizeof(int));
+				result->listvalues = (Datum **)
+										palloc0(numparts * sizeof(Datum *));
+				break;
+			case PARTITION_STRAT_RANGE:
+				for (i = 0; i < key->partnatts; i++)
+					result->rangemaxs[i] = (Datum *)
+										palloc0(numparts * sizeof(Datum));
+				break;
+		}
+	}
+
+	for (i = 0; i < numparts; i++)
+	{
+		result->oids[i] = partitions[i]->oid;
+
+		switch (key->strategy)
+		{
+			case PARTITION_STRAT_LIST:
+				/* Simply copy the list value datums for each partition */
+				result->listnvalues[i] = partitions[i]->listnvalues;
+				result->listvalues[i] = (Datum *)
+							palloc0(partitions[i]->listnvalues * sizeof(Datum));
+				for (j = 0; j < result->listnvalues[i]; j++)
+				{
+					Datum   *from = &partitions[i]->listvalues[j];
+					Datum   *into = &result->listvalues[i][j];
+
+					if (!typinfo->typbyval[0])
+					{
+						if (typinfo->typlen[0] == -1)
+							*into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
+						else
+							*into = datumCopy(*from, false, typinfo->typlen[0]);
+					}
+					else
+						*into = *from;
+				}
+				break;
+
+			case PARTITION_STRAT_RANGE:
+				/* Sort on rangemaxs before copying */
+				qsort_arg(partitions, numparts,	sizeof(PartitionInfo *),
+												range_partition_cmp_max,
+												key->partsupfunc);
+
+				for (j = 0; j < key->partnatts; j++)
+				{
+					Datum   *from = &partitions[i]->rangemaxs[j];
+					Datum   *into = &result->rangemaxs[j][i];
+
+					if (!typinfo->typbyval[j])
+					{
+						if (typinfo->typlen[j] == -1)
+							*into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
+						else
+							*into = datumCopy(*from, false, typinfo->typlen[j]);
+					}
+					else
+						*into = *from;
+				}
+				break;
+		}
+	}
+
+	/* Clean up after yourself */
+	if (partitions)
+		free_partitions(partitions, numparts);
+
+	/* Save a copy in the relcache */
+	relation->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(relation),
+										ALLOCSET_DEFAULT_MINSIZE,
+										ALLOCSET_DEFAULT_INITSIZE,
+										ALLOCSET_DEFAULT_MAXSIZE);
+
+	relation->rd_partdesc = MemoryContextAllocZero(relation->rd_pdcxt,
+													sizeof(PartitionDescData));
+	oldcxt = MemoryContextSwitchTo(relation->rd_pdcxt);
+	relation->rd_partdesc = CopyPartitionDesc(result,
+											  key->partnatts,
+											  key->strategy,
+											  typinfo);
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+}
+
+/*
+ * range_partition_cmp_max
+ *		Compare two PartitionBoundInfos based on rangemaxs
+ *
+ * Used as a callback to qsort_arg for sorting range partitions in
+ * ascending order of their rangemaxs values.
+ */
+static int32
+range_partition_cmp_max(const void *a, const void *b, void *arg)
+{
+	int         i;
+	int32       result;
+	FmgrInfo   *cmpfn = (FmgrInfo *) arg;
+	int         natts = (*(const PartitionInfo **) a)->partnatts;
+	const PartitionInfo *arg1 = *(const PartitionInfo **) a;
+	const PartitionInfo *arg2 = *(const PartitionInfo **) b;
+
+	for (i = 0; i < natts; i++)
+	{
+		result = DatumGetInt32(FunctionCall2Coll(&cmpfn[i],
+						DEFAULT_COLLATION_OID,
+						arg1->rangemaxs[i], arg2->rangemaxs[i]));
+
+		/* consider multicolumn range partitions */
+		if (!result)
+			continue;
+		else
+			return result;
+	}
+
+	return 0;
+}
+
+/*
+ * CopyPartitionDesc
+ *		Copy PartitionDesc of a partitioned relation into caller's context
+ */
+static PartitionDesc
+CopyPartitionDesc(PartitionDesc src, int partnatts, char strategy,
+				  PartitionKeyTypeInfo *typinfo)
+{
+	PartitionDesc	result;
+	int				i, j;
+
+	result = (PartitionDesc) palloc0(sizeof(PartitionDescData));
+	result->numparts = src->numparts;
+
+	/* If there are partitions, prepare to copy bound arrays */
+	if (result->numparts > 0)
+	{
+		result->oids = (Oid *) palloc0(result->numparts * sizeof(Oid));
+		switch (strategy)
+		{
+			case PARTITION_STRAT_LIST:
+				result->listnvalues = (int *)
+								palloc0(result->numparts * sizeof(int));
+				result->listvalues = (Datum **)
+								palloc0(result->numparts * sizeof(Datum *));
+				break;
+			case PARTITION_STRAT_RANGE:
+				for (i = 0; i < partnatts; i++)
+					result->rangemaxs[i] = (Datum *)
+								palloc0(result->numparts * sizeof(Datum));
+				break;
+		}
+	}
+
+	for (i = 0; i < result->numparts; i++)
+	{
+		result->oids[i] = src->oids[i];
+
+		switch (strategy)
+		{
+			case PARTITION_STRAT_LIST:
+				result->listnvalues[i] = src->listnvalues[i];
+				result->listvalues[i] = (Datum *)
+							palloc0(result->listnvalues[i] * sizeof(Datum));
+
+				for (j = 0; j < result->listnvalues[i]; j++)
+				{
+					Datum   *from = &src->listvalues[i][j];
+					Datum   *into = &result->listvalues[i][j];
+
+					if (!typinfo->typbyval[0])
+					{
+						if (typinfo->typlen[0] == -1)
+							*into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
+						else
+							*into = datumCopy(*from, false, typinfo->typlen[0]);
+					}
+					else
+						*into = *from;
+				}
+				break;
+
+			case PARTITION_STRAT_RANGE:
+				for (j = 0; j < partnatts; j++)
+				{
+					Datum   *from = &src->rangemaxs[j][i];
+					Datum   *into = &result->rangemaxs[j][i];
+
+					if (!typinfo->typbyval[j])
+					{
+						if (typinfo->typlen[j] == -1)
+							*into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
+						else
+							*into = datumCopy(*from, false, typinfo->typlen[j]);
+					}
+					else
+						*into = *from;
+				}
+				break;
+		}
+	}
+
+	return result;
+}
+
+/*
+ * GetRelPartitions
+ *		Return information of partitions of rel
+ *
+ * If only_valid is 'true', only return partitions that are marked valid
+ * in pg_partition.
+ *
+ * Returns number of partitions in *numparts.
+ */
+static PartitionInfo **
+GetRelPartitions(Relation rel, PartitionKey key, PartitionKeyTypeInfo *typinfo,
+				 bool only_valid, int *numparts)
+{
+	List	   *partoids;
+	int			i;
+	ListCell   *cell;
+	PartitionInfo **partitions;
+
+	partoids = get_partitions(RelationGetRelid(rel), NoLock);
+
+	if ((*numparts = list_length(partoids)) == 0)
+		return NULL;
+
+	partitions = (PartitionInfo **) palloc0(*numparts *
+										sizeof(PartitionInfo *));
+	i = 0;
+	foreach(cell, partoids)
+	{
+		HeapTuple			tuple;
+		Form_pg_partition	form;
+		int					j;
+		int					rangenmaxs;
+		Datum			   *rangemaxs;
+		Datum				datum;
+		bool				isnull;
+		Oid 				partrelid;
+		PartitionInfo	   *result = NULL;
+
+		partrelid = lfirst_oid(cell);
+
+		tuple = SearchSysCache1(PARTRELID, partrelid);
+		Assert(HeapTupleIsValid(tuple));
+
+		form = (Form_pg_partition) GETSTRUCT(tuple);
+
+		if (only_valid && !form->partvalid)
+			continue;
+
+		result = (PartitionInfo *) palloc0(sizeof(PartitionInfo));
+		result->oid = partrelid;
+		result->partnatts = key->partnatts;
+
+		datum = SysCacheGetAttr(PARTRELID, tuple,
+									Anum_pg_partition_partlistvals, &isnull);
+		if (!isnull)
+		{
+			deconstruct_array(DatumGetArrayTypeP(datum),
+							  typinfo->typid[0],
+							  typinfo->typlen[0],
+							  typinfo->typbyval[0],
+							  typinfo->typalign[0],
+							  &result->listvalues, NULL, &result->listnvalues);
+
+			ReleaseSysCache(tuple);
+			partitions[i++] = result;
+			continue;
+		}
+
+		/* Must be a range partition */
+		datum = SysCacheGetAttr(PARTRELID, tuple,
+								Anum_pg_partition_partrangemaxs, &isnull);
+		Assert(!isnull);
+
+		/*
+		 * Each element of rangemaxs (any) array is itself an array
+		 * containing rangemax value for a given key column.
+		 */
+		deconstruct_array(DatumGetArrayTypeP(datum),
+						  ANYARRAYOID, -1, false, 'd',
+						  &rangemaxs, NULL, &rangenmaxs);
+		/* Paranoia */
+		Assert(rangenmaxs = result->partnatts);
+
+		result->rangemaxs = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+		for (j = 0; j < result->partnatts; j++)
+		{
+			ArrayType  *arr = DatumGetArrayTypeP(rangemaxs[j]);
+			Datum	   *datum;
+			bool	   *nulls;
+			int			dummy;
+
+			deconstruct_array(arr,
+							  typinfo->typid[j],
+							  typinfo->typlen[j],
+							  typinfo->typbyval[j],
+							  typinfo->typalign[j],
+							  &datum, &nulls, &dummy);
+			Assert(!nulls[0]);
+
+			result->rangemaxs[j] = datum[0];
+		}
+		ReleaseSysCache(tuple);
+
+		partitions[i++] = result;
+	}
+
+	return partitions;
+}
+
+/*
+ * free_partitions
+ *		Free the PartitionInfo array (presumably the one that was built
+ *		using GetRelPartitions)
+ */
+static void
+free_partitions(PartitionInfo **p, int num)
+{
+	int	i;
+
+	for (i = 0; i < num; i++)
+	{
+		if (p[i]->rangemaxs)
+			pfree(p[i]->rangemaxs);
+		if (p[i]->listvalues)
+			pfree(p[i]->listvalues);
+		pfree(p[i]);
+	}
+
+	pfree(p);
+}
+
+/*
+ * get_partitions
+ *		Returns a list of OIDs of partitions of relation with OID relid
+ *
+ * Partitions are locked in an order that follows the sorted order of
+ * their OIDs.
+ */
+List *
+get_partitions(Oid relid, int lockmode)
+{
+	List		   *list = NIL;
+	Relation		partitionRel;
+	SysScanDesc		scan;
+	ScanKeyData		skey[1];
+	HeapTuple		tuple;
+	Oid				partrelid;
+	Oid			   *oidarr;
+	int				maxoids,
+					numoids,
+					i;
+
+	/* Scan pg_partition and build a working array of partition OIDs */
+	maxoids = 64;
+	oidarr = (Oid *) palloc(maxoids * sizeof(Oid));
+	numoids = 0;
+
+	partitionRel = heap_open(PartitionRelationId, AccessShareLock);
+	ScanKeyInit(&skey[0],
+				Anum_pg_partition_partparent,
+				BTEqualStrategyNumber, F_OIDEQ,
+				relid);
+
+	scan = systable_beginscan(partitionRel, PartitionParentIndexId, true,
+								NULL, 1, skey);
+
+	while ((tuple = systable_getnext(scan)) != NULL)
+	{
+		partrelid = ((Form_pg_partition) GETSTRUCT(tuple))->partrelid;
+		if (numoids >= maxoids)
+		{
+			maxoids *= 2;
+			oidarr = (Oid *) repalloc(oidarr, maxoids * sizeof(Oid));
+		}
+		oidarr[numoids++] = partrelid;
+	}
+
+	systable_endscan(scan);
+
+	heap_close(partitionRel, AccessShareLock);
+
+	/*
+	 * If we found more than one partition, sort them by OID. This is important
+	 * since we need to be sure all backends lock partitions in the same order
+	 * to avoid needless deadlocks.
+	 */
+	if (numoids > 1)
+		qsort(oidarr, numoids, sizeof(Oid), oid_cmp);
+
+	/* Acquire locks and build the result list. */
+	for (i = 0; i < numoids; i++)
+	{
+		partrelid = oidarr[i];
+
+		if (lockmode != NoLock)
+		{
+			/* Get the lock to synchronize against concurrent drop */
+			LockRelationOid(partrelid, lockmode);
+
+			/*
+			 * Now that we have the lock, double-check to see if the relation
+			 * really exists or not.  If not, assume it was dropped while we
+			 * waited to acquire lock, and ignore it.
+			 */
+			if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(partrelid)))
+			{
+				/* Release useless lock */
+				UnlockRelationOid(partrelid, lockmode);
+				/* And ignore this relation */
+				continue;
+			}
+		}
+
+		list = lappend_oid(list, partrelid);
+	}
+
+	pfree(oidarr);
+
+	return list;
+}
+
+/* qsort comparison function */
+static int
+oid_cmp(const void *p1, const void *p2)
+{
+	Oid			v1 = *((const Oid *) p1);
+	Oid			v2 = *((const Oid *) p2);
+
+	if (v1 < v2)
+		return -1;
+	if (v1 > v2)
+		return 1;
+	return 0;
+}
+
+/*
+ * get_all_partitions
+ *		Returns a list of OIDs of partitions of relation with OID relid
+ *		including any partitions of their own and so on
+ *
+ * Note: should be called only on RELKIND_PARTITIONED_REL relations.
+ */
+List *
+get_all_partitions(Oid relid, int lockmode)
+{
+	List	   *list;
+	ListCell   *cell;
+
+	/* Initialize the list with all partitions at level 1 */
+	list = get_partitions(relid, lockmode);
+
+	/*
+	 * Recursively add partitions of further levels in what is effectively
+	 * a breadth-first manner.
+	 */
+	foreach (cell, list)
+	{
+		Oid		partOid = lfirst_oid(cell);
+
+		list = list_concat(list, get_partitions(partOid, lockmode));
+	}
+
+	return list;
+}
+
+/*
+ * get_leaf_partitions
+ *		Returns a list of OIDs of leaf level partitions of relation with
+ *		OID 'relid'
+ *
+ * Note to callers: make sure to call this on RELKIND_PARTITIONED_REL
+ * relations or internal partitions;
+ */
+List *
+get_leaf_partitions(Oid relid, int lockmode)
+{
+	Relation	rel;
+	int			nlevels;
+
+	rel = heap_open(relid, AccessShareLock);
+
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		nlevels = list_length(rel->rd_partkeys);
+	else
+	{
+		Oid		rootParent;
+		Relation	rootParentRel;
+
+		Assert(relid_is_internal_partition(relid));
+
+		rootParent = get_partition_root_parent(relid);
+		rootParentRel = heap_open(rootParent, AccessShareLock);
+
+		nlevels = list_length(rootParentRel->rd_partkeys) -
+								get_partition_level(relid);
+
+		heap_close(rootParentRel, AccessShareLock);
+	}
+	heap_close(rel, AccessShareLock);
+
+	return get_leaf_partitions_recurse(relid, lockmode, nlevels, 0);
+}
+
+/*
+ * get_leaf_partitions_recurse
+ *		Returns leaf level partitions of relation with OID 'relid'
+ */
+static List *
+get_leaf_partitions_recurse(Oid relid, int lockmode, int nlevels,
+							int levels_down)
+{
+	List	   *partitions,
+			   *result = NIL;
+	ListCell   *cell;
+
+	/*
+	 * If we got to level just 1 above the leaf level, return our partitions
+	 * as leaf partitions.
+	 */
+	if (levels_down == nlevels - 1)
+		return get_partitions(relid, lockmode);
+
+	/* Not there yet, so move one-level down */
+	partitions = get_partitions(relid, lockmode);
+	foreach (cell, partitions)
+	{
+		Oid		partOid = lfirst_oid(cell);
+
+		result = list_concat(result, get_leaf_partitions_recurse(partOid,
+														lockmode, nlevels,
+														levels_down + 1));
+	}
+
+	return result;
+}
+
+/*
+ * get_partition_parent
+ *		Get OID of the parent of partition with OID 'relid'
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	HeapTuple	tuple;
+	Oid			result;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+
+	if (!HeapTupleIsValid(tuple))
+		return InvalidOid;
+
+	result = ((Form_pg_partition) GETSTRUCT(tuple))->partparent;
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
+/*
+ * get_partition_root_parent
+ *		Get OID of the root parent of partition with OID 'relid'
+ */
+Oid
+get_partition_root_parent(Oid relid)
+{
+	Oid		parentOid;
+
+	parentOid = get_partition_parent(relid);
+
+	if (!OidIsValid(parentOid))
+		return relid;
+
+	return get_partition_root_parent(parentOid);
+}
+
+/*
+ * get_partition_level
+ *		Returns the level of partition with OID 'relid'
+ *
+ * Special case: returns 0 for the root of a partitioning tree.
+ */
+int get_partition_level(Oid relid)
+{
+	return get_partition_level_internal(relid, 0);
+}
+
+/*
+ * get_partition_level_internal
+ *		Workhorse for public function get_partition_level
+ */
+static int
+get_partition_level_internal(Oid relid, int level)
+{
+	Oid		parentOid;
+
+	parentOid = get_partition_parent(relid);
+
+	if (!OidIsValid(parentOid))
+		return level;
+
+	return get_partition_level_internal(parentOid, level + 1);
+}
+
+/*
+ * relid_is_partition
+ *		Returns whether relation with OID relid is a partition
+ */
+bool
+relid_is_partition(Oid relid)
+{
+	return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * relid_is_internal_partition
+ *		Returns whether relation with OID relid is an internal partition
+ */
+bool
+relid_is_internal_partition(Oid relid)
+{
+	Relation	rel;
+	Relation	partitionRel;
+	Relation	parentRel;
+	SysScanDesc	scan;
+	ScanKeyData	key[1];
+
+	/*
+	 * Cannot be an internal partition if not a partition in the first
+	 * place
+	 */
+	if (!relid_is_partition(relid))
+		return false;
+
+	/*
+	 * A non-NULL rd_partdesc means it has partitions, so it's not a
+	 * leaf partiion.
+	 */
+	rel = heap_open(relid, AccessShareLock);
+	if (rel->rd_partdesc != NULL)
+	{
+		heap_close(rel, AccessShareLock);
+		return true;
+	}
+	heap_close(rel, AccessShareLock);
+
+	/*
+	 * rd_partdesc is NULL but if there exists at least one pg_partition
+	 * tuple with 'relid' in its partparent column, then it's not a leaf
+	 * partition.
+	 */
+	ScanKeyInit(&key[0],
+				Anum_pg_partition_partparent,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+
+	partitionRel = heap_open(PartitionRelationId, AccessShareLock);
+	scan = systable_beginscan(partitionRel,
+							  PartitionParentIndexId,
+							  true, NULL, 1, key);
+
+	if (HeapTupleIsValid(systable_getnext(scan)))
+	{
+		systable_endscan(scan);
+		heap_close(partitionRel, AccessShareLock);
+		return true;
+	}
+	systable_endscan(scan);
+	heap_close(partitionRel, AccessShareLock);
+
+	/*
+	 * Finally, there are yet no (sub-) partitions created but there
+	 * is a partition key for partitions of its level
+	 */
+	parentRel = heap_open(get_partition_root_parent(relid), AccessShareLock);
+	if (list_length(parentRel->rd_partkeys) > get_partition_level(relid))
+	{
+		heap_close(parentRel, AccessShareLock);
+		return true;
+	}
+	heap_close(parentRel, AccessShareLock);
+
+	return false;
+}
+
+/*
+ * list_partition_overlaps
+ *		Check whether list partition overlaps with existing partitions of rel
+ *
+ * If it does overlap, return the oid of the partition it overlaps with.
+ */
+bool
+list_partition_overlaps(PartitionDesc pdesc, PartitionKey key,
+						int listnvalues, Datum *listvalues,
+						Oid *overlapsWith)
+{
+	int			i, j, k;
+	int32		result;
+	FmgrInfo		*partsupfunc = key->partsupfunc;
+
+	for (i = 0; i < pdesc->numparts; i++)
+	{
+		int		ilistnvalues = pdesc->listnvalues[i];
+		Datum  *ilistvalues = pdesc->listvalues[i];
+		Oid		iOid = pdesc->oids[i];
+
+		for (j = 0; j < listnvalues; j++)
+		{
+			Datum	newDatum = listvalues[j];
+
+			for (k = 0; k < ilistnvalues; k++)
+			{
+				result = compare_using_func(partsupfunc[0],
+											newDatum,
+											ilistvalues[k]);
+				if (!result)
+				{
+					*overlapsWith = iOid;
+					return true;
+				}
+			}
+		}
+	}
+
+	return false;
+}
+
+/*
+ * range_partition_overlaps
+ *		Check whether range partition overlaps with some existing partition
+ *		of rel
+ *
+ * This amounts to comparing the new rangemaxs with that of the last partition.
+ * A new range partition should always be defined to have a rangemax that is
+ * greater than that of the last partition.
+ */
+bool
+range_partition_overlaps(PartitionDesc pdesc, PartitionKey key, Datum *rangemaxs)
+{
+	int			i;
+	int32		cmpval;
+	Datum	   *rel_rangemaxs;
+	int			partnatts = key->partnatts;
+	FmgrInfo   *partsupfunc = key->partsupfunc;
+
+	if (pdesc->numparts == 0)
+		return false;
+
+	/* Get ahold of rangemaxs of the last partition of rel */
+	rel_rangemaxs = (Datum *) palloc0(partnatts * sizeof(Datum));
+
+	for (i = 0; i < partnatts; i++)
+		rel_rangemaxs[i] = pdesc->rangemaxs[i][pdesc->numparts - 1];
+
+	cmpval = rangemaxs_cmp_datum(rangemaxs,
+								 rel_rangemaxs,
+								 partnatts,
+								 partsupfunc);
+
+	pfree(rel_rangemaxs);
+	return cmpval <= 0;
+}
+
+/*
+ * rangemaxs_cmp_datum
+ *		Compare datum against rangemaxs of a range partition
+ *
+ * vals and rangemaxs are each 'natts' datums long. partsupfunc is an array
+ * of pointers to FmgrInfo structs, one per partition key column.
+ */
+static int32
+rangemaxs_cmp_datum(const Datum *vals, const Datum *rangemaxs,
+							  int partnatts, FmgrInfo *partsupfunc)
+{
+	int 	i;
+	int32	result;
+
+	for (i = 0; i < partnatts; i++)
+	{
+		result = compare_using_func(partsupfunc[i], vals[i], rangemaxs[i]);
+
+		/* consider a multi-column key */
+		if (!result)
+			continue;
+		else
+			return result;
+	}
+
+	return 0;
+}
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index f40a005..91e08de 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -291,7 +291,8 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 										   false,
 										   true,
 										   true,
-										   NULL);
+										   NULL,
+										   false);
 	Assert(toast_relid != InvalidOid);
 
 	/* make the toast relation visible, else heap_open will fail */
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 25398ae..4c47d54 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -128,7 +128,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 			   errmsg("cannot cluster temporary tables of other sessions")));
 
-		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		if (relid_is_partitioned(tableOid))
 			ereport(ERROR,
 					(errmsg("cannot cluster partitioned tables")));
 
@@ -684,7 +684,8 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
 										  false,
 										  true,
 										  true,
-										  NULL);
+										  NULL,
+										  false);
 	Assert(OIDNewHeap != InvalidOid);
 
 	ReleaseSysCache(tuple);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 403738d..62b650f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1731,6 +1731,13 @@ BeginCopyTo(Relation rel,
 							RelationGetRelationName(rel))));
 	}
 
+	if (rel != NULL && relid_is_internal_partition(RelationGetRelid(rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot copy from internal partition \"%s\"",
+						RelationGetRelationName(rel)),
+				 errhint("Try the COPY (SELECT ...) TO variant.")));
+
 	cstate = BeginCopy(false, rel, query, queryString, queryRelId, attnamelist,
 					   options);
 	oldcontext = MemoryContextSwitchTo(cstate->copycontext);
@@ -2236,7 +2243,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,
@@ -2265,6 +2273,12 @@ CopyFrom(CopyState cstate)
 							RelationGetRelationName(cstate->rel))));
 	}
 
+	if (relid_is_partition(RelationGetRelid(cstate->rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot copy to partition \"%s\"",
+						RelationGetRelationName(cstate->rel))));
+
 	tupDesc = RelationGetDescr(cstate->rel);
 
 	/*----------
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7bdbdf0..adfaa2c 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/partition.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -85,6 +86,7 @@
 #include "storage/smgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/fmgroids.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
@@ -345,9 +347,10 @@ 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 ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
-static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
-				 const char *colName, LOCKMODE lockmode);
+static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, bool recursing,
+				 LOCKMODE lockmode);
+static ObjectAddress ATExecSetNotNull(List **wqueue, AlteredTableInfo *tab, Relation rel,
+				 const char *colName, bool recursing, LOCKMODE lockmode);
 static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
 					Node *newDefault, LOCKMODE lockmode);
 static void ATPrepSetStatistics(Relation rel, const char *colName,
@@ -388,8 +391,9 @@ static void ATPrepAlterColumnType(List **wqueue,
 					  bool recurse, bool recursing,
 					  AlterTableCmd *cmd, LOCKMODE lockmode);
 static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
-static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
-					  AlterTableCmd *cmd, LOCKMODE lockmode);
+static ObjectAddress ATExecAlterColumnType(List **wqueue, AlteredTableInfo *tab,
+					  Relation rel, AlterTableCmd *cmd, bool recursing,
+					  LOCKMODE lockmode);
 static ObjectAddress ATExecAlterColumnGenericOptions(Relation rel, const char *colName,
 								List *options, LOCKMODE lockmode);
 static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab,
@@ -453,6 +457,17 @@ static void SetRelationSpcFileNode(Relation rel, Oid newTableSpace,
 static void ATExecChangePersistence(List **wqueue, AlteredTableInfo *tab,
 						Relation rel, char persistence, LOCKMODE lockmode);
 static void SetRelationPersistence(Oid relationId, char persistence);
+static Datum *EvalPartitionBound(Relation rootParent, Relation parent,
+						PartitionKey key, PartitionKeyTypeInfo *typinfo,
+						PartitionValues *values, int *listnvalues);
+static Datum *evalPartitionListBound(List *values, PartitionKeyTypeInfo *typinfo,
+						int nvalues);
+static Datum *evalPartitionRangeBound(List *values, PartitionKeyTypeInfo *typinfo,
+						int partnatts);
+static void ATExecAddPartition(Relation rootParent, RangeVar *name, RangeVar *parent,
+						PartitionValues *values);
+static void ATExecRemovePartition(Relation rootParent, RangeVar *name, bool is_detach,
+						bool is_subpart);
 
 
 /* ----------------------------------------------------------------
@@ -700,7 +715,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										  true,
 										  allowSystemTableMods,
 										  false,
-										  typaddress);
+										  typaddress,
+										  stmt->is_internal_partition);
 
 	/* Store inheritance information for new rel. */
 	StoreCatalogInheritance(relationId, inheritOids);
@@ -994,6 +1010,17 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 	if (!OidIsValid(relOid))
 		return;
 
+	if (relid_is_partition(relOid) && !relid_is_internal_partition(relOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is a sub-partition", rel->relname),
+				 errhint("Use ALTER TABLE DROP SUBPARTITION to drop it.")));
+	else if (relid_is_partition(relOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is a partition", rel->relname),
+				 errhint("Use ALTER TABLE DROP PARTITION to drop it.")));
+
 	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
 	if (!HeapTupleIsValid(tuple))
 		return;					/* concurrently dropped, so nothing to do */
@@ -1106,6 +1133,25 @@ ExecuteTruncate(TruncateStmt *stmt)
 				relids = lappend_oid(relids, childrelid);
 			}
 		}
+
+		if (relid_is_partitioned(myrelid))
+		{
+			ListCell   *partition;
+			List	   *partitions;
+
+			partitions = get_leaf_partitions(myrelid, AccessExclusiveLock);
+
+			foreach(partition, partitions)
+			{
+				Oid		partOid = lfirst_oid(partition);
+
+				/* get_leaf_partitions already got lock */
+				rel = heap_open(partOid, NoLock);
+				truncate_check_rel(rel);
+				rels = lappend(rels, rel);
+				relids = lappend_oid(relids, partOid);
+			}
+		}
 	}
 
 	/*
@@ -1234,6 +1280,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 	{
 		Relation	rel = (Relation) lfirst(cell);
 
+		if (relid_is_partitioned(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1345,7 +1394,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",
@@ -2213,6 +2263,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
@@ -2327,6 +2382,21 @@ renameatt_internal(Oid myrelid,
 							oldattname)));
 	}
 
+	/* Propagate to all partitions */
+	if (targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+	{
+		List	   *partitions;
+		ListCell   *partition;
+
+		partitions = get_all_partitions(RelationGetRelid(targetrelation), NoLock);
+		foreach(partition, partitions)
+		{
+			Oid			partrelid = lfirst_oid(partition);
+
+			renameatt_internal(partrelid, oldattname, newattname, false, true, 0, behavior);
+		}
+	}
+
 	/* rename attributes in typed tables of composite type */
 	if (targetrelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
 	{
@@ -3560,10 +3630,10 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
-			address = ATExecDropNotNull(rel, cmd->name, lockmode);
+			address = ATExecDropNotNull(rel, cmd->name, false, lockmode);
 			break;
 		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
-			address = ATExecSetNotNull(tab, rel, cmd->name, lockmode);
+			address = ATExecSetNotNull(wqueue, tab, rel, cmd->name, false, lockmode);
 			break;
 		case AT_SetStatistics:	/* ALTER COLUMN SET STATISTICS */
 			address = ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
@@ -3641,7 +3711,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 								 cmd->missing_ok, lockmode);
 			break;
 		case AT_AlterColumnType:		/* ALTER COLUMN TYPE */
-			address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
+			address = ATExecAlterColumnType(wqueue, tab, rel, cmd, false, lockmode);
 			break;
 		case AT_AlterColumnGenericOptions:		/* ALTER COLUMN OPTIONS */
 			address =
@@ -3781,13 +3851,25 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
 		case AT_AddPartition:
+			ATExecAddPartition(rel, ((PartitionStmt *) cmd->def)->name, NULL,
+										((PartitionStmt *) cmd->def)->values);
+			break;
 		case AT_AddSubPartition:
+			ATExecAddPartition(rel, ((PartitionStmt *) cmd->def)->name,
+										((PartitionStmt *) cmd->def)->parent,
+										((PartitionStmt *) cmd->def)->values);
+			break;
 		case AT_DropPartition:
+			ATExecRemovePartition(rel, (RangeVar *) cmd->def, false, false);
+			break;
 		case AT_DropSubPartition:
-		case AT_AttachPartition:
-		case AT_AttachSubPartition:
+			ATExecRemovePartition(rel, (RangeVar *) cmd->def, false, true);
+			break;
 		case AT_DetachPartition:
+			ATExecRemovePartition(rel, (RangeVar *) cmd->def, true, false);
+			break;
 		case AT_DetachSubPartition:
+			ATExecRemovePartition(rel, (RangeVar *) cmd->def, true, true);
 			break;
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
@@ -3822,8 +3904,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
 
 		/* Foreign tables and partitioned tables have no storage. */
 		if (tab->relkind == RELKIND_FOREIGN_TABLE ||
-			(tab->relkind == RELKIND_PARTITIONED_REL &&
-				tab->newTableSpace == InvalidOid))
+			(relid_is_partitioned(tab->relid) &&
+			 !OidIsValid(tab->newTableSpace) && tab->constraints == NIL))
 			continue;
 
 		/*
@@ -4065,6 +4147,43 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	oldTupDesc = tab->oldDesc;
 	newTupDesc = RelationGetDescr(oldrel);		/* includes all mods */
 
+	/*
+	 * Arrange to verify any new CHECK constraint on leaf partitions. Note
+	 * that NOT NULL constraint is handled by recursion in phase 2.
+	 */
+	if (oldrel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+	{
+		List	   *partitions;
+		ListCell   *partition;
+
+		partitions = get_leaf_partitions(tab->relid, NoLock);
+		foreach(partition, partitions)
+		{
+			Oid			partrelid = lfirst_oid(partition);
+			Relation	partrel;
+			AlteredTableInfo *parttab;
+
+			parttab = (AlteredTableInfo *)
+									palloc0(sizeof(AlteredTableInfo));
+
+			partrel = heap_open(partrelid, NoLock);
+
+			parttab->relid = partrelid;
+			parttab->relkind = partrel->rd_rel->relkind;
+			parttab->oldDesc = tab->oldDesc;
+			parttab->constraints = tab->constraints;
+
+			ATRewriteTable(parttab, InvalidOid, NoLock);
+
+			pfree(parttab);
+			heap_close(partrel, NoLock);
+		}
+
+		/* Nothing more to do here for the original tab */
+		heap_close(oldrel, NoLock);
+		return;
+	}
+
 	if (OidIsValid(OIDNewHeap))
 		newrel = heap_open(OIDNewHeap, lockmode);
 	else
@@ -4828,6 +4947,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (relid_is_partition(myrelid) && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5096,6 +5220,38 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	add_column_datatype_dependency(myrelid, newattnum, attribute.atttypid);
 	add_column_collation_dependency(myrelid, newattnum, attribute.attcollation);
 
+	/* Propagate to all partitions */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+	{
+		List	   *partitions;
+		ListCell   *partition;
+
+		partitions = get_all_partitions(RelationGetRelid(rel), lockmode);
+		foreach(partition, partitions)
+		{
+			Oid			partrelid = lfirst_oid(partition);
+			Relation	partrel;
+			AlteredTableInfo *parttab;
+			ColumnDef   *partColDef = copyObject(colDef);
+
+			/* find_partitions already got lock */
+			partrel = heap_open(partrelid, NoLock);
+			CheckTableNotInUse(partrel, "ALTER TABLE");
+
+			/* Find or create work queue entry for this table */
+			parttab = ATGetQueueEntry(wqueue, partrel);
+
+			partColDef->is_for_partition = true;
+
+			/* Recurse to partition; return value is ignored */
+			ATExecAddColumn(wqueue, parttab, partrel, partColDef, isOid,
+							false, true, if_not_exists, lockmode);
+
+			pfree(partColDef);
+			heap_close(partrel, NoLock);
+		}
+	}
+
 	/*
 	 * 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
@@ -5275,7 +5431,8 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
  * nullable, InvalidObjectAddress is returned.
  */
 static ObjectAddress
-ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
+ATExecDropNotNull(Relation rel, const char *colName, bool recursing,
+					LOCKMODE lockmode)
 {
 	HeapTuple	tuple;
 	AttrNumber	attnum;
@@ -5284,6 +5441,11 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	ListCell   *indexoidscan;
 	ObjectAddress address;
 
+	if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot alter column of a partition")));
+
 	/*
 	 * lookup the attribute
 	 */
@@ -5367,6 +5529,29 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	else
 		address = InvalidObjectAddress;
 
+	/* Propagate to partitions */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+	{
+		List	   *partitions;
+		ListCell   *partition;
+
+		partitions = get_all_partitions(RelationGetRelid(rel), lockmode);
+		foreach(partition, partitions)
+		{
+			Oid			partrelid = lfirst_oid(partition);
+			Relation	partrel;
+
+			/* find_partitions already got lock */
+			partrel = heap_open(partrelid, NoLock);
+			CheckTableNotInUse(partrel, "ALTER TABLE");
+
+			/* Recurse to partition; return value is ignored */
+			ATExecDropNotNull(partrel, colName, true, lockmode);
+
+			heap_close(partrel, NoLock);
+		}
+	}
+
 	InvokeObjectPostAlterHook(RelationRelationId,
 							  RelationGetRelid(rel), attnum);
 
@@ -5382,14 +5567,19 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
  * NULL, InvalidObjectAddress is returned.
  */
 static ObjectAddress
-ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
-				 const char *colName, LOCKMODE lockmode)
+ATExecSetNotNull(List **wqueue, AlteredTableInfo *tab, Relation rel,
+				 const char *colName, bool recursing, LOCKMODE lockmode)
 {
 	HeapTuple	tuple;
 	AttrNumber	attnum;
 	Relation	attr_rel;
 	ObjectAddress address;
 
+	if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot alter column of a partition")));
+
 	/*
 	 * lookup the attribute
 	 */
@@ -5433,6 +5623,34 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 	else
 		address = InvalidObjectAddress;
 
+	/* Propagate to partitions */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+	{
+		List	   *partitions;
+		ListCell   *partition;
+
+		partitions = get_all_partitions(RelationGetRelid(rel), lockmode);
+		foreach(partition, partitions)
+		{
+			Oid			partrelid = lfirst_oid(partition);
+			Relation	partrel;
+			AlteredTableInfo *parttab;
+
+			/* get_all_partitions already got lock */
+			partrel = heap_open(partrelid, NoLock);
+			CheckTableNotInUse(partrel, "ALTER TABLE");
+
+			/* Find or create work queue entry for this table */
+			parttab = ATGetQueueEntry(wqueue, partrel);
+
+			/* Recurse to partition; return value is ignored */
+			ATExecSetNotNull(wqueue, parttab, partrel, colName, true,
+							 lockmode);
+
+			heap_close(partrel, NoLock);
+		}
+	}
+
 	InvokeObjectPostAlterHook(RelationRelationId,
 							  RelationGetRelid(rel), attnum);
 
@@ -5890,6 +6108,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
 	 */
@@ -6076,6 +6299,30 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 		tab->rewrite |= AT_REWRITE_ALTER_OID;
 	}
 
+	/* Propagate to partitions */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+	{
+		List	   *partitions;
+		ListCell   *partition;
+
+		partitions = get_all_partitions(RelationGetRelid(rel), lockmode);
+		foreach(partition, partitions)
+		{
+			Oid			partrelid = lfirst_oid(partition);
+			Relation	partrel;
+
+			/* find_partitions already got lock */
+			partrel = heap_open(partrelid, NoLock);
+			CheckTableNotInUse(partrel, "ALTER TABLE");
+
+			/* Recurse to partition; return value is ignored */
+			ATExecDropColumn(wqueue, partrel, colName, behavior,
+								false, true, missing_ok, lockmode);
+
+			heap_close(partrel, NoLock);
+		}
+	}
+
 	return object;
 }
 
@@ -6308,6 +6555,11 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	ListCell   *child;
 	ObjectAddress address = InvalidObjectAddress;
 
+	if (relid_is_partition(RelationGetRelid(rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add CHECK constraints to a partition")));
+
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
@@ -7075,6 +7327,11 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 			 * Foreign keys do not inherit, so we purposely ignore the
 			 * recursion bit here
 			 */
+
+			/*
+			 * Foreign keys are not currently supported on partitioned tables,
+			 * so ignore recursion for now.
+			 */
 		}
 		else if (con->contype == CONSTRAINT_CHECK)
 		{
@@ -7122,6 +7379,26 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 				heap_close(childrel, NoLock);
 			}
 
+			/* Recurse to partitions */
+			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+			{
+				List	   *partitions;
+				ListCell   *partition;
+
+				partitions = get_leaf_partitions(RelationGetRelid(rel),
+												 lockmode);
+
+				foreach(partition, partitions)
+				{
+					Oid			partOID = lfirst_oid(partition);
+					Relation	partrel;
+
+					partrel = heap_open(partOID, lockmode);
+					validateCheckConstraint(partrel, tuple);
+					heap_close(partrel, NoLock);
+				}
+			}
+
 			validateCheckConstraint(rel, tuple);
 
 			/*
@@ -7523,7 +7800,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	 * tables.
 	 */
 	if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
-		rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		relid_is_partitioned(RelationGetRelid(rel)))
 		return;
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrtup);
@@ -8266,8 +8543,8 @@ ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
  * Return the address of the modified column.
  */
 static ObjectAddress
-ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
-					  AlterTableCmd *cmd, LOCKMODE lockmode)
+ATExecAlterColumnType(List **wqueue, AlteredTableInfo *tab, Relation rel,
+					  AlterTableCmd *cmd, bool recursing, LOCKMODE lockmode)
 {
 	char	   *colName = cmd->name;
 	ColumnDef  *def = (ColumnDef *) cmd->def;
@@ -8288,6 +8565,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 	HeapTuple	depTup;
 	ObjectAddress address;
 
+	if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot alter column of a partition")));
+
 	attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/* Look up the target column */
@@ -8658,6 +8940,37 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 		StoreAttrDefault(rel, attnum, defaultexpr, true);
 	}
 
+	/* Propagate to partitions */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+	{
+		List	   *partitions;
+		ListCell   *partition;
+
+		partitions = get_all_partitions(RelationGetRelid(rel), lockmode);
+		foreach(partition, partitions)
+		{
+			Oid			partrelid = lfirst_oid(partition);
+			Relation	partrel;
+			AlteredTableInfo *parttab;
+
+			/* find_partitions already got lock */
+			partrel = heap_open(partrelid, NoLock);
+			CheckTableNotInUse(partrel, "ALTER TABLE");
+
+			/* Find or create work queue entry for this table */
+			parttab = ATGetQueueEntry(wqueue, partrel);
+
+			parttab->rewrite |= tab->rewrite;
+			if (parttab->rewrite)
+				parttab->newvals = tab->newvals;
+
+			/* Recurse to partition; return value is ignored */
+			ATExecAlterColumnType(wqueue, parttab, partrel, cmd, true, lockmode);
+
+			heap_close(partrel, NoLock);
+		}
+	}
+
 	ObjectAddressSubSet(address, RelationRelationId,
 						RelationGetRelid(rel), attnum);
 
@@ -9343,6 +9656,29 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 		}
 	}
 
+	if (relid_is_partitioned(relationOid))
+	{
+		List	   *partitions;
+		ListCell   *partition;
+
+		/* Propagate to partitions */
+		partitions = get_partitions(relationOid, lockmode);
+		foreach(partition, partitions)
+		{
+			Oid			partrelid = lfirst_oid(partition);
+			Relation	partrel;
+
+			/* find_partitions already got lock */
+			partrel = heap_open(partrelid, NoLock);
+			CheckTableNotInUse(partrel, "ALTER TABLE");
+
+			/* Recurse to partition; return value is ignored */
+			ATExecChangeOwner(partrelid, newOwnerId, false, lockmode);
+
+			heap_close(partrel, NoLock);
+		}
+	}
+
 	InvokeObjectPostAlterHook(RelationRelationId, relationOid, 0);
 
 	ReleaseSysCache(tuple);
@@ -9823,7 +10159,7 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 				 errmsg("cannot move temporary tables of other sessions")));
 
 	/* For partitioned tables, just set the new tablespace in pg_class */
-	if(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+	if (relid_is_partitioned(tableOid))
 	{
 		SetRelationSpcFileNode(rel, newTableSpace, rel->rd_rel->relfilenode);
 		relation_close(rel, lockmode);
@@ -12567,3 +12903,319 @@ ComputePartitionAttrs(Oid relid,
 		partOpClassOids[attn++] = opclassOid;
 	}
 }
+
+/*
+ * ATExecAddPartition - ADD PARTITION
+ */
+static void
+ATExecAddPartition(Relation rootParent, RangeVar *name, RangeVar *parent,
+				   PartitionValues *values)
+{
+	bool			is_subpart;
+	PartitionKey	partKey;
+	Relation		partRel,
+					parentRel;
+	Datum		   *boundDatums;
+	int				listnvalues;
+	int				parent_level;
+	PartitionKeyTypeInfo *typinfo;
+
+	partRel = heap_openrv(name, NoLock);
+
+	if (parent != NULL)
+	{
+		parentRel = heap_openrv(parent, AccessExclusiveLock);
+		parent_level = get_partition_level(RelationGetRelid(parentRel));
+		is_subpart = true;
+	}
+	else
+		parentRel = rootParent;
+
+	partKey = is_subpart ? list_nth(rootParent->rd_partkeys, parent_level) :
+							linitial(rootParent->rd_partkeys);
+
+	typinfo = get_partition_key_type_info(rootParent, partKey);
+
+	/* Evaluate and validate new partition's bound */
+	boundDatums = EvalPartitionBound(rootParent, parentRel, partKey, typinfo,
+									 values, &listnvalues);
+
+	/* Update the catalog */
+	StorePartition(rootParent, partKey, typinfo,
+					RelationGetRelid(partRel),
+					RelationGetRelid(parentRel),
+					true,
+					listnvalues,
+					boundDatums);
+
+	/* Tell world about the new partition of parent. */
+	CacheInvalidateRelcache(parentRel);
+
+	free_partition_key_type_info(typinfo);
+	heap_close(partRel, NoLock);
+	if (parentRel != rootParent)
+		heap_close(parentRel, AccessExclusiveLock);
+}
+
+/*
+ * EvalPartitionBound
+ *		Evalulate FOR VALUES specification of a partition
+ *
+ * Returns partition bound value Datums to store in pg_partition catalog
+ * unless the new partition's definition happens to overlap with some
+ * existing partition's, in which case an error is thrown.
+ */
+static Datum *
+EvalPartitionBound(Relation rootParent, Relation parent, PartitionKey key,
+				   PartitionKeyTypeInfo *typinfo, PartitionValues *values,
+				   int *listnvalues)
+{
+	PartitionDesc	pdesc;
+	Datum	   *result;
+	Datum	   *rangemaxs;
+	Oid			overlapsWith;
+	int			i;
+
+	pdesc = RelationGetPartitionDesc(parent, key, typinfo, true);
+
+	/* result->oid is set by the caller */
+
+	*listnvalues = list_length(values->listvalues);
+	switch (key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			Assert(key->partnatts == 1);
+			Assert(list_length(values->listvalues) >= 1);
+
+			result = (Datum *) palloc0(*listnvalues * sizeof(Datum));
+			result = evalPartitionListBound(values->listvalues, typinfo,
+											*listnvalues);
+
+			/* TODO: uniqueify(result->listvalues) */
+
+			if (list_partition_overlaps(pdesc, key, *listnvalues, result,
+										&overlapsWith))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("invalid list partition definition"),
+					 errdetail("New partition's definition overlaps with partition \"%s\".",
+								get_rel_name(overlapsWith))));
+
+			break;
+
+		case PARTITION_STRAT_RANGE:
+			Assert(list_length(values->rangemaxs) == key->partnatts);
+			rangemaxs = evalPartitionRangeBound(values->rangemaxs, typinfo, key->partnatts);
+
+			if (range_partition_overlaps(pdesc, key, rangemaxs))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("invalid range partition definition"),
+					 errhint("Specify a bound value that is greater than that of the last partition")));
+
+			/*
+			 * Final result consists of Datum array where each datum is an
+			 * an array that contains a rangemax value for a key column.
+			 *
+			 * The reason to have to do this weird thing is to get around the
+			 * the fact about anyarray (which is the type of partrangemaxs
+			 * field of pg_partition system catalog) that it requires each
+			 * element to be of the same type.
+			 */
+			result = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+			for (i = 0; i < key->partnatts; i++)
+			{
+				Datum		datums[1];
+				bool		nulls[1];
+				int			dims[1];
+				int			lbs[1];
+				ArrayType  *rangemax;
+
+				datums[0] = rangemaxs[i];
+				nulls[0] = false;
+				dims[0] = 1;
+				lbs[0] = 1;
+
+				rangemax = construct_md_array(datums, nulls, 1, dims, lbs,
+												typinfo->typid[i], typinfo->typlen[i],
+												typinfo->typbyval[i], typinfo->typalign[i]);
+
+				result[i] = PointerGetDatum(rangemax);
+			}
+	}
+
+	return result;
+}
+
+/*
+ * evalPartitionListBound
+ *		Evaluate a list of expressions appearing in FOR VALUES IN (..) clause
+ *		for a list partition
+ */
+static Datum *
+evalPartitionListBound(List *values, PartitionKeyTypeInfo *typinfo, int nvalues)
+{
+	ListCell	*cell;
+	ParseState	*pstate;
+	EState		*estate;
+	ExprContext	*ecxt;
+	int			i;
+	Datum		*datum;
+
+	if (!values)
+		return NULL;
+
+	datum = (Datum *) palloc(nvalues * sizeof(Datum));
+	pstate = make_parsestate(NULL);
+	estate = CreateExecutorState();
+	ecxt = GetPerTupleExprContext(estate);
+
+	i = 0;
+	foreach(cell, values)
+	{
+		Node		*value = (Node *) lfirst(cell);
+		bool		isnull;
+		ExprState	*expr;
+		MemoryContext	oldcxt;
+
+		oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+		expr = ExecPrepareExpr((Expr *) value, estate);
+		datum[i] = ExecEvalExpr(expr, ecxt, &isnull, NULL);
+
+		if (isnull)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					errmsg("a partition bound value must not be NULL")));
+
+		MemoryContextSwitchTo(oldcxt);
+
+		if (!typinfo->typbyval[0])
+		{
+			if (typinfo->typlen[0] == -1)
+				datum[i] = PointerGetDatum(PG_DETOAST_DATUM_COPY(datum[i]));
+			else
+				datum[i] = datumCopy(datum[i], false, typinfo->typlen[0]);
+		}
+
+		ResetPerTupleExprContext(estate);
+		i++;
+	}
+
+	return datum;
+}
+
+/*
+ * evalPartitionRangeBound
+ *		Evaluate a list of expressions appearing in FOR VALUES LESS THAN (..)
+ *		clause for a range partition
+ */
+static Datum *
+evalPartitionRangeBound(List *values, PartitionKeyTypeInfo *typinfo, int partnatts)
+{
+	ListCell	*cell;
+	ParseState	*pstate;
+	EState		*estate;
+	ExprContext	*ecxt;
+	int			i;
+	Datum		*datum;
+
+	if (!values)
+		return NULL;
+
+	datum = (Datum *) palloc(partnatts * sizeof(Datum));
+	pstate = make_parsestate(NULL);
+	estate = CreateExecutorState();
+	ecxt = GetPerTupleExprContext(estate);
+
+	i = 0;
+	foreach(cell, values)
+	{
+		Node		*value = (Node *) lfirst(cell);
+		bool		isnull;
+		ExprState	*expr;
+		MemoryContext	oldcxt;
+
+		oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+		expr = ExecPrepareExpr((Expr *) value, estate);
+		datum[i] = ExecEvalExpr(expr, ecxt, &isnull, NULL);
+
+		if (isnull)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					errmsg("a partition value must not be NULL")));
+
+		MemoryContextSwitchTo(oldcxt);
+
+		if (!typinfo->typbyval[i])
+		{
+			if (typinfo->typlen[i] == -1)
+				datum[i] = PointerGetDatum(PG_DETOAST_DATUM_COPY(datum[i]));
+			else
+				datum[i] = datumCopy(datum[i], false, typinfo->typlen[i]);
+		}
+
+		ResetPerTupleExprContext(estate);
+		i++;
+	}
+
+	return datum;
+}
+
+/*
+ * ATExecRemovePartition - DROP/DETACH PARTITION / SUBPARTITION
+ */
+static void
+ATExecRemovePartition(Relation rootParent, RangeVar *name, bool is_detach,
+					bool is_subpart)
+{
+	Relation	partRel,
+				parentRel;
+	Oid			partitionOid,
+				parentOid;
+	ObjectAddress	obj;
+
+	partRel = heap_openrv(name, AccessExclusiveLock);
+	partitionOid = RelationGetRelid(partRel);
+	parentOid = get_partition_parent(partitionOid);
+	heap_close(partRel, AccessExclusiveLock);
+
+	if (!OidIsValid(parentOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("%s \"%s\" of \"%s\" does not exist",
+						!is_subpart ? "partition" : "sub-partition",
+						name->relname, RelationGetRelationName(rootParent))));
+
+	if (is_subpart && parentOid == RelationGetRelid(rootParent))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("sub-partition \"%s\" of \"%s\" does not exist",
+						name->relname, RelationGetRelationName(rootParent))));
+	else if (!is_subpart && parentOid != RelationGetRelid(rootParent))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("partition \"%s\" of \"%s\" does not exist",
+						name->relname, RelationGetRelationName(rootParent))));
+
+	if (is_detach && relid_is_internal_partition(partitionOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("\"%s\" is an internal partition", name->relname),
+				 errdetail("Detaching internal partitions is not supported")));
+
+	if (is_detach)
+		RemovePartitionEntryByRelId(partitionOid);
+	else
+	{
+		obj.classId = RelationRelationId;
+		obj.objectId = partitionOid;
+		obj.objectSubId = 0;
+		performDeletion(&obj, DROP_CASCADE, 0);
+	}
+
+	CommandCounterIncrement();
+
+	parentRel = heap_open(parentOid, AccessShareLock);
+	CacheInvalidateRelcache(parentRel);
+	heap_close(parentRel, AccessShareLock);
+}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 76f7297..0f11922 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,31 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+			if (relid_is_partition(RelationGetRelid(resultRel)))
+			{
+				switch (operation)
+				{
+					case CMD_INSERT:
+						ereport(ERROR,
+						  (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						   errmsg("cannot insert into partition \"%s\"",
+								  RelationGetRelationName(resultRel)),
+						   errhint("Insert into the root parent instead.")));
+						break;
+					case CMD_UPDATE:
+						ereport(ERROR,
+						  (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						   errmsg("cannot update partition \"%s\"",
+								  RelationGetRelationName(resultRel)),
+						   errhint("Update the root parent instead.")));
+						break;
+					default:
+						elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+						break;
+				}
+			}
+			break;
+		case RELKIND_PARTITIONED_REL:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index aabe108..bf3a162 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2600,6 +2600,8 @@ _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(is_dropped_copy);
 	COPY_SCALAR_FIELD(storage);
 	COPY_NODE_FIELD(raw_default);
 	COPY_NODE_FIELD(cooked_default);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index cfb5db6..2e538ef 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2339,6 +2339,8 @@ _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(is_dropped_copy);
 	COMPARE_SCALAR_FIELD(storage);
 	COMPARE_NODE_FIELD(raw_default);
 	COMPARE_NODE_FIELD(cooked_default);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 00b449d..cd8f653 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2344,6 +2344,8 @@ _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_BOOL_FIELD(is_dropped_copy);
 	WRITE_CHAR_FIELD(storage);
 	WRITE_NODE_FIELD(raw_default);
 	WRITE_NODE_FIELD(cooked_default);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 902bd40..5219d5f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -544,6 +544,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <partby>	PartitionBy SubPartitionBy
 %type <partelem>	part_elem
 %type <list>		part_params
+%type <list>		OptWithPartition
 %type <partvalues>	ForValues
 %type <list>		ValuesList
 %type <range>		using_table opt_using_table
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ce8ed33..94731ed 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -2670,9 +2670,6 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				transformAddPartition(&cxt, (PartitionStmt *) cmd->def);
 				newcmds = lappend(newcmds, cmd);
 				break;
-			case AT_DropPartition:
-			case AT_DropSubPartition:
-				break;
 			case AT_AttachPartition:
 			case AT_AttachSubPartition:
 				ereport(ERROR,
@@ -2684,6 +2681,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				{
 					PartitionStmt *partstmt = (PartitionStmt *) cmd->def;
 
+					/*
+					 * Add a RenameStmt to rename the partition after being
+					 * detached
+					 */
 					if (partstmt->using_table)
 					{
 						RenameStmt *stmt = makeNode(RenameStmt);
@@ -2695,6 +2696,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 						cxt.alist = lappend(cxt.alist, stmt);
 					}
+					cmd->def = (Node *) partstmt->name;
 
 					newcmds = lappend(newcmds, cmd);
 					break;
@@ -3072,43 +3074,124 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
  * Note that cxt->relation corresponds to the root partitioned table.
  */
 static void
-transformAddPartition(CreateStmtContext *cxt, PartitionStmt *stmt)
+transformAddPartition(CreateStmtContext *cxt, PartitionStmt *partstmt)
 {
+	CreateStmt	   *stmt;
 	Relation		parentRel;
-	bool			is_subpartition = stmt->parent != NULL;
+	List		   *partitionElts;
+	TupleDesc		tupdesc;
+	TupleConstr	   *constr;
+	int16			attno;
+	bool			is_subpartition = partstmt->parent != NULL;
 	PartitionKey	key;
 
 	/* Check if the target table is partitioned at all */
 	if (cxt->rel->rd_partkeys == NIL)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+				 errmsg("\"%s\" is not partitioned",
+								cxt->relation->relname)));
 
 	/* If adding a sub-partition, check if we can do so */
 	if (is_subpartition && list_length(cxt->rel->rd_partkeys) < 2)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("cannot add sub-partition"),
-				 errdetail("\"%s\" is not sub-partitioned", cxt->relation->relname)));
+				 errdetail("\"%s\" is not sub-partitioned",
+								cxt->relation->relname)));
 
-	if (stmt->parent)
+	if (partstmt->parent)
 	{
-		parentRel = heap_openrv_extended(stmt->parent, NoLock, true);
+		parentRel = heap_openrv_extended(partstmt->parent, NoLock, true);
 
 		if (parentRel == NULL)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 					 errmsg("partition \"%s\" of \"%s\" does not exist",
-							stmt->parent->relname, cxt->relation->relname),
-						parser_errposition(cxt->pstate, stmt->parent->location)));
+						partstmt->parent->relname, cxt->relation->relname),
+						parser_errposition(cxt->pstate,
+											partstmt->parent->location)));
 	}
 	else
 		parentRel = heap_openrv(cxt->relation, NoLock);
 
+	/*
+	 * Gin up column definition list for the partition by reading individual
+	 * attributes off the root parent's tuple descriptor. Do not miss dropped
+	 * columns.
+	 */
+	tupdesc = RelationGetDescr(cxt->rel);
+	constr = tupdesc->constr;
+	partitionElts = NIL;
+	for (attno = 1; attno <= tupdesc->natts; attno++)
+	{
+		Form_pg_attribute attr = tupdesc->attrs[attno - 1];
+		ColumnDef  *n;
+
+		n = makeNode(ColumnDef);
+		n->colname = pstrdup(NameStr(attr->attname));
+
+		/* A hack... */
+		if (attr->attisdropped)
+		{
+			/* ... is hack! This will have to go away eventually */
+			n->typeName = makeTypeNameFromNameList(list_make1(makeString("int2")));
+			n->is_dropped_copy = true;
+		}
+		else
+			n->typeName = makeTypeNameFromOid(attr->atttypid, attr->atttypmod);
+
+		n->inhcount = 0;
+		n->is_local = true;
+		n->is_not_null = attr->attnotnull;
+		n->is_from_type = false;
+		n->is_for_partition = true;
+		n->storage = attr->attstorage;
+		n->cooked_default = NULL;
+		n->raw_default = NULL;
+		n->collClause = NULL;
+		n->collOid = attr->attcollation;
+		n->constraints = NIL;
+		n->location = -1;
+
+		partitionElts = lappend(partitionElts, n);
+	}
+
+	/* Make a CreateStmt for the partition relation */
+	stmt = makeNode(CreateStmt);
+
+	partstmt->name->relpersistence = parentRel->rd_rel->relpersistence;
+	stmt->relation = partstmt->name;
+	stmt->tableElts = partitionElts;
+
+	/* Use own options if defined, otherwise immediate parent's */
+	if (partstmt->options)
+		stmt->options = partstmt->options;
+	else
+		stmt->options = untransformRelOptions(PointerGetDatum(parentRel->rd_options));
+
+	if (parentRel->rd_rel->relhasoids)
+		stmt->options = lcons(makeDefElem("oids",
+							  (Node *) makeInteger(true)), partstmt->options);
+
+	/* Use own tablespace if defined, otherwise immediate parent's */
+	if (partstmt->tablespacename)
+		stmt->tablespacename = partstmt->tablespacename;
+	else if (parentRel->rd_rel->reltablespace)
+		stmt->tablespacename = get_tablespace_name(parentRel->rd_rel->reltablespace);
+	else
+		stmt->tablespacename = NULL;
+
+	stmt->is_internal_partition = !is_subpartition &&
+								  list_length(cxt->rel->rd_partkeys) > 1;
+
+	/* Arrange the partition relation to be created */
+	cxt->blist = lappend(cxt->blist, stmt);
+
 	/* tranform FOR VALUES clause */
 	key = !is_subpartition ? linitial(cxt->rel->rd_partkeys)
 									: lsecond(cxt->rel->rd_partkeys);
-	stmt->values = transformPartitionValues(cxt, key, stmt->values);
+	partstmt->values = transformPartitionValues(cxt, key, partstmt->values);
 
 	heap_close(parentRel, NoLock);
 }
@@ -3225,8 +3308,8 @@ transformPartitionValues(CreateStmtContext *cxt, PartitionKey key,
 			}
 			break;
 	}
-
 	result->location = values->location;
+	free_partition_key_type_info(typinfo);
 
 	return result;
 }
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..46edef2 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -49,7 +49,8 @@ extern Relation heap_create(const char *relname,
 			char relpersistence,
 			bool shared_relation,
 			bool mapped_relation,
-			bool allow_system_table_mods);
+			bool allow_system_table_mods,
+			bool is_internal_partition);
 
 extern Oid heap_create_with_catalog(const char *relname,
 						 Oid relnamespace,
@@ -71,7 +72,8 @@ extern Oid heap_create_with_catalog(const char *relname,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
-						 ObjectAddress *typaddress);
+						 ObjectAddress *typaddress,
+						 bool is_internal_partition);
 
 extern void heap_create_init_fork(Relation rel);
 
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 44b4c06..7f1576a 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -40,6 +40,31 @@ typedef struct PartitionKeyTypeInfo
 	char	*typalign;
 } PartitionKeyTypeInfo;
 
+/*
+ * Information about partitions of a partitioned table or sub-partitions
+ * of an internal partition.
+ *
+ *	'numparts'		Length of each of the following C arrays
+ *	'oids'			Partition OIDs
+ *	'listnvalues'	Numbers of value datums in corresponding arrays in
+ *					'listvalues' below
+ *	'listvalues'	Pointers to arrays of value datums for all list
+ *					partitions
+ *	'rangemaxs'		Pointers to arrays of rangemax datums for all range
+ *					partitions (note that there are PARTITION_MAX_KEYS
+ *					members per array)
+ */
+typedef struct PartitionDescData
+{
+	int			numparts;
+	Oid		   *oids;
+	int		   *listnvalues;
+	Datum	  **listvalues;
+	Datum	   *rangemaxs[PARTITION_MAX_KEYS];
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
 extern void StorePartitionKey(Relation rel,
 					int16 partlevel,
 					int16 partnatts,
@@ -56,4 +81,34 @@ extern PartitionKeyTypeInfo *get_partition_key_type_info(Relation rel,
 extern void free_partition_key_type_info(PartitionKeyTypeInfo *typinfo);
 extern bool relid_is_partitioned(Oid relid);
 
+extern void StorePartition(Relation rel, PartitionKey key,
+					PartitionKeyTypeInfo *typinfo,
+					Oid partitionOid,
+					Oid parentOid,
+					bool valid,
+					int listnvalues,
+					Datum *boundDatums);
+extern void RemovePartitionEntryByRelId(Oid relid);
+extern void RelationDropPartitions(Oid relid);
+extern PartitionDesc RelationGetPartitionDesc(Relation relation,
+					PartitionKey key,
+					PartitionKeyTypeInfo *typinfo,
+					bool include_all);
+extern List *get_partitions(Oid relid, int lockmode);
+extern List *get_all_partitions(Oid relid, int lockmode);
+extern List *get_leaf_partitions(Oid relid, int lockmode);
+extern Oid get_partition_parent(Oid relid);
+extern Oid get_partition_root_parent(Oid relid);
+extern int get_partition_level(Oid relid);
+extern bool relid_is_partition(Oid relid);
+extern bool relid_is_internal_partition(Oid relid);
+
+extern bool list_partition_overlaps(PartitionDesc pdesc,
+								PartitionKey key,
+								int listnvalues,
+								Datum *listvalues,
+								Oid *overlapsWith);
+extern bool range_partition_overlaps(PartitionDesc pdesc,
+								PartitionKey key,
+								Datum *rangemaxs);
 #endif   /* PARTITION_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8128226..bf07577 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -591,6 +591,11 @@ 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
+									 * derived from root parent's column */
+	bool		is_dropped_copy;	/* column definition is a dummy one
+									 * corresponding to some dropped column
+									 * in the root parent */
 	char		storage;		/* attstorage setting, or 0 for default */
 	Node	   *raw_default;	/* default value (untransformed parse tree) */
 	Node	   *cooked_default; /* default value (transformed expr tree) */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 4242e84..1d339b6 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -91,6 +91,8 @@ typedef struct RelationData
 	struct RowSecurityDesc *rd_rsdesc;	/* row security policies, or NULL */
 
 	List	*rd_partkeys;		/* partition keys, or NIL */
+	MemoryContext 	rd_pdcxt;		/* private context for partdesc */
+	PartitionDesc	rd_partdesc;	/* partitions, or NULL */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 19e4e46..29abb9e 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -117,6 +117,7 @@ pg_namespace|t
 pg_opclass|t
 pg_operator|t
 pg_opfamily|t
+pg_partition|t
 pg_partitioned_rel|t
 pg_pltemplate|t
 pg_policy|t
-- 
1.7.1

  [text/x-diff] 0008-Rework-code-manipulating-the-PartitionKey-data-struc.patch (34.0K, ../56C1302D.6070403@lab.ntt.co.jp/9-0008-Rework-code-manipulating-the-PartitionKey-data-struc.patch)
  download | inline diff:
From 490d89a55bb0a680885704a8b6ddb96b85e160de Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 10 Feb 2016 19:28:22 +0900
Subject: [PATCH 08/10] Rework code manipulating the PartitionKey data structure.

Previously, type information about partition key columns was handled
separately. So, there were public interface functions to get partition
key type information wherever required. But that needed access to the
root parent relation at all times (to get to partition expressions) and
resulted in needing to pass around that rel and type information which
became unbearable by this point.

Instead, integrate type information into PartitionKey structure and build
it when building partition key itself. That means, partition expressions
are also built which wasn't the case previously. So, no more need for
RelationGetPartitionExpr() which had a weird interface anyway.
---
 src/backend/catalog/partition.c    |  298 +++++++++++++++++++-----------------
 src/backend/commands/tablecmds.c   |   60 +++----
 src/backend/parser/parse_utilcmd.c |   17 +--
 src/backend/utils/cache/relcache.c |   88 -----------
 src/include/catalog/partition.h    |   41 +++---
 src/include/utils/relcache.h       |    2 -
 6 files changed, 204 insertions(+), 302 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 33a16eb..cf48bf4 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -30,6 +30,8 @@
 #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"
@@ -61,11 +63,9 @@ typedef struct PartitionInfo
 
 static int32 range_partition_cmp_max(const void *a, const void *b,
 					void *arg);
-static PartitionDesc CopyPartitionDesc(PartitionDesc src, int partnatts,
-					char strategy, PartitionKeyTypeInfo *typinfo);
+static PartitionDesc CopyPartitionDesc(PartitionDesc src, PartitionKey key);
 static PartitionInfo **GetRelPartitions(Relation rel, PartitionKey key,
-					PartitionKeyTypeInfo *typinfo, bool only_valid,
-					int *numparts);
+					bool only_valid, int *numparts);
 static void free_partitions(PartitionInfo **p, int num);
 static int	oid_cmp(const void *p1, const void *p2);
 static List *get_leaf_partitions_recurse(Oid relid, int lockmode,
@@ -248,11 +248,9 @@ relid_is_partitioned(Oid relid)
 void
 RelationBuildPartitionKeys(Relation relation)
 {
-	List	   *keys = NIL;
-	HeapTuple	tuple;
-	bool		isnull;
-	int			level, i;
-	MemoryContext oldcxt = CurrentMemoryContext;
+	List		   *keys = NIL;
+	int				level;
+	MemoryContext	oldcxt = CurrentMemoryContext;
 
 	/*
 	 * Loop through the partition keys for this relation, if any.
@@ -261,10 +259,15 @@ RelationBuildPartitionKeys(Relation relation)
 	{
 		Relation		catalog;
 		Form_pg_partitioned_rel	form;
-		PartitionKey	key;
+		HeapTuple		tuple;
+		Datum			datum;
+		bool			isnull;
 		int2vector	   *partattrs;
+		ListCell	   *partexprs_item;
 		oidvector	   *opclass;
-		Datum			datum;
+		int				i;
+		PartitionKey	key;
+		KeyTypeInfo	   *typinfo;
 
 		tuple = SearchSysCache2(PARTEDRELIDLEVEL,
 							ObjectIdGetDatum(RelationGetRelid(relation)),
@@ -295,33 +298,83 @@ RelationBuildPartitionKeys(Relation relation)
 		Assert(!isnull);
 		opclass = (oidvector *) DatumGetPointer(datum);
 
-		key->partattrs = (AttrNumber *)
-							palloc0(key->partnatts * sizeof(AttrNumber));
+		datum = heap_getattr(tuple,
+								Anum_pg_partitioned_rel_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 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->partsupfunc = (FmgrInfo *)
-							palloc0(key->partnatts * sizeof(FmgrInfo));
+		key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+		/* Gather type info as well */
+		key->typinfo = typinfo = (KeyTypeInfo *) palloc0(sizeof(KeyTypeInfo));
+		typinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+		typinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+		typinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+		typinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+		typinfo->typalign = (char *) palloc0(key->partnatts * sizeof(bool));
 
 		/*
 		 * Copy partattrs. Further, determine and store the opfamily and
 		 * support function per attribute.
 		 */
+		partexprs_item = list_head(key->partexprs);
 		for (i = 0; i < key->partnatts; i++)
 		{
-			HeapTuple			tuple;
-			Form_pg_opclass 	form;
-			Oid					funcid;
+			HeapTuple		tuple;
+			AttrNumber		attno;
+			Form_pg_opclass form;
+			Oid				funcid;
 
-			key->partattrs[i] = partattrs->values[i];
+			key->partattrs[i] = attno = partattrs->values[i];
 
-			tuple = SearchSysCache(CLAOID,
-							ObjectIdGetDatum(opclass->values[i]),
-							0, 0, 0);
+			/* Collect type information */
+			if (attno != InvalidAttrNumber)
+			{
+				typinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+				typinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			}
+			else
+			{
+				typinfo->typid[i] = exprType(lfirst(partexprs_item));
+				typinfo->typmod[i] = exprTypmod(lfirst(partexprs_item));
+				partexprs_item = lnext(partexprs_item);
+			}
+			get_typlenbyvalalign(typinfo->typid[i],
+								 &typinfo->typlen[i],
+								 &typinfo->typbyval[i],
+								 &typinfo->typalign[i]);
+
+			/* Determine operator family */
+			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;
 
 			/*
@@ -336,9 +389,6 @@ RelationBuildPartitionKeys(Relation relation)
 			ReleaseSysCache(tuple);
 		}
 
-		/* partexprs are built and copied here as and when necessary */
-		key->partexprs = NIL;
-
 		keys = lappend(keys, key);
 
 		ReleaseSysCache(tuple);
@@ -353,6 +403,25 @@ RelationBuildPartitionKeys(Relation relation)
 	FreePartitionKeys(keys);
 }
 
+static KeyTypeInfo *
+copy_key_type_info(int n, KeyTypeInfo *typinfo)
+{
+	KeyTypeInfo   *result = (KeyTypeInfo *) palloc0(sizeof(KeyTypeInfo));
+
+	result->typid = (Oid *) palloc0(n * sizeof(Oid));
+	memcpy(result->typid, typinfo->typid, n * sizeof(Oid));
+	result->typmod = (int32 *) palloc0(n * sizeof(int32));
+	memcpy(result->typmod, typinfo->typmod, n * sizeof(int32));
+	result->typlen = (int16 *) palloc0(n * sizeof(int16));
+	memcpy(result->typlen, typinfo->typlen, n * sizeof(int16));
+	result->typbyval = (bool *) palloc0(n * sizeof(bool));
+	memcpy(result->typbyval, typinfo->typbyval, n * sizeof(bool));
+	result->typalign = (char *) palloc0(n * sizeof(bool));
+	memcpy(result->typalign, typinfo->typalign, n * sizeof(bool));
+
+	return result;
+}
+
 /*
  * CopyPartitionKeys
  *
@@ -389,6 +458,10 @@ CopyPartitionKeys(List *keys)
 		memcpy(newkey->partsupfunc, fromkey->partsupfunc,
 							newkey->partnatts * sizeof(FmgrInfo));
 
+		newkey->partexprs = copyObject(fromkey->partexprs);
+		newkey->typinfo = copy_key_type_info(fromkey->partnatts,
+											 fromkey->typinfo);
+
 		result = lappend(result, newkey);
 	}
 
@@ -396,6 +469,22 @@ CopyPartitionKeys(List *keys)
 }
 
 /*
+ * free_key_type_info
+ */
+static void
+free_key_type_info(KeyTypeInfo *typinfo)
+{
+	Assert(typinfo != NULL);
+
+	pfree(typinfo->typid);
+	pfree(typinfo->typmod);
+	pfree(typinfo->typlen);
+	pfree(typinfo->typbyval);
+	pfree(typinfo->typalign);
+	pfree(typinfo);
+}
+
+/*
  * FreePartitionKeys
  */
 void
@@ -413,83 +502,13 @@ FreePartitionKeys(List *keys)
 		pfree(key->partattrs);
 		pfree(key->partopfamily);
 		pfree(key->partsupfunc);
+		free_key_type_info(key->typinfo);
 		pfree(key);
 	}
 	pfree(keys);
 }
 
 /*
- * get_partition_key_type_info
- *		Returns a PartitionKeyTypeInfo object that includes type info
- *		for individual partition key attributes.
- *
- * Note to callers: make sure to call this on root parents (that is,
- * RELKIND_PARTITIONED_REL relations)
- */
-PartitionKeyTypeInfo *
-get_partition_key_type_info(Relation rel, PartitionKey key)
-{
-	int			i;
-	List	   *partexprs;
-	ListCell   *partexprs_item;
-	PartitionKeyTypeInfo *result;
-
-	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL);
-
-	partexprs = RelationGetPartitionExpr(rel, key->level);
-
-	result = (PartitionKeyTypeInfo *) palloc0(sizeof(PartitionKeyTypeInfo));
-	result->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
-	result->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
-	result->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
-	result->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
-	result->typalign = (char *) palloc0(key->partnatts * sizeof(bool));
-
-	partexprs_item = list_head(partexprs);
-	for (i = 0; i < key->partnatts; i++)
-	{
-		AttrNumber attno = key->partattrs[i];
-
-		if (attno != InvalidAttrNumber)
-		{
-			result->typid[i] = rel->rd_att->attrs[attno - 1]->atttypid;
-			result->typmod[i] = rel->rd_att->attrs[attno - 1]->atttypmod;
-		}
-		else
-		{
-			result->typid[i] = exprType(lfirst(partexprs_item));
-			result->typmod[i] = exprTypmod(lfirst(partexprs_item));
-			partexprs_item = lnext(partexprs_item);
-		}
-		get_typlenbyvalalign(result->typid[i],
-								&result->typlen[i],
-								&result->typbyval[i],
-								&result->typalign[i]);
-	}
-
-	if (partexprs)
-		pfree(partexprs);
-
-	return result;
-}
-
-/*
- * free_key_type_info
- */
-void
-free_partition_key_type_info(PartitionKeyTypeInfo *typinfo)
-{
-	Assert(typinfo != NULL);
-
-	pfree(typinfo->typid);
-	pfree(typinfo->typmod);
-	pfree(typinfo->typlen);
-	pfree(typinfo->typbyval);
-	pfree(typinfo->typalign);
-	pfree(typinfo);
-}
-
-/*
  * StorePartition
  *		Store information of new partition of rel into pg_partition
  *
@@ -497,7 +516,7 @@ free_partition_key_type_info(PartitionKeyTypeInfo *typinfo)
  * 'parentOid' is OID of the actual parent of 'partition'.
  */
 void
-StorePartition(Relation rel, PartitionKey key, PartitionKeyTypeInfo *typinfo,
+StorePartition(Relation rel, PartitionKey key,
 				Oid partitionOid,
 				Oid parentOid,
 				bool valid,
@@ -522,10 +541,10 @@ StorePartition(Relation rel, PartitionKey key, PartitionKeyTypeInfo *typinfo,
 			Assert(listnvalues > 0);
 			arr_listvalues = construct_array(boundDatums,
 										 listnvalues,
-										 typinfo->typid[0],
-										 typinfo->typlen[0],
-										 typinfo->typbyval[0],
-										 typinfo->typalign[0]);
+										 key->typinfo->typid[0],
+										 key->typinfo->typlen[0],
+										 key->typinfo->typbyval[0],
+										 key->typinfo->typalign[0]);
 			/* No range bounds */
 			nulls[Anum_pg_partition_partrangemaxs - 1] = true;
 			break;
@@ -614,8 +633,7 @@ RelationDropPartitions(Oid relid)
  * though, since it could disappear due to relcache invalidation.)
  */
 PartitionDesc
-RelationGetPartitionDesc(Relation relation, PartitionKey key,
-						 PartitionKeyTypeInfo *typinfo, bool include_all)
+RelationGetPartitionDesc(Relation relation, PartitionKey key, bool include_all)
 {
 	int		i,
 			j;
@@ -626,15 +644,11 @@ RelationGetPartitionDesc(Relation relation, PartitionKey key,
 
 	/* Quick copy and exit if already computed the descriptor */
 	if (relation->rd_partdesc != NULL)
-		return CopyPartitionDesc(relation->rd_partdesc,
-								 key->partnatts,
-								 key->strategy,
-								 typinfo);
+		return CopyPartitionDesc(relation->rd_partdesc, key);
 
 	result = (PartitionDesc) palloc0(sizeof(PartitionDescData));
 
-	partitions = GetRelPartitions(relation, key, typinfo, !include_all,
-								  &numparts);
+	partitions = GetRelPartitions(relation, key, !include_all, &numparts);
 	result->numparts = numparts;
 
 	if (partitions)
@@ -671,12 +685,12 @@ RelationGetPartitionDesc(Relation relation, PartitionKey key,
 					Datum   *from = &partitions[i]->listvalues[j];
 					Datum   *into = &result->listvalues[i][j];
 
-					if (!typinfo->typbyval[0])
+					if (!key->typinfo->typbyval[0])
 					{
-						if (typinfo->typlen[0] == -1)
+						if (key->typinfo->typlen[0] == -1)
 							*into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
 						else
-							*into = datumCopy(*from, false, typinfo->typlen[0]);
+							*into = datumCopy(*from, false, key->typinfo->typlen[0]);
 					}
 					else
 						*into = *from;
@@ -694,12 +708,12 @@ RelationGetPartitionDesc(Relation relation, PartitionKey key,
 					Datum   *from = &partitions[i]->rangemaxs[j];
 					Datum   *into = &result->rangemaxs[j][i];
 
-					if (!typinfo->typbyval[j])
+					if (!key->typinfo->typbyval[j])
 					{
-						if (typinfo->typlen[j] == -1)
+						if (key->typinfo->typlen[j] == -1)
 							*into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
 						else
-							*into = datumCopy(*from, false, typinfo->typlen[j]);
+							*into = datumCopy(*from, false, key->typinfo->typlen[j]);
 					}
 					else
 						*into = *from;
@@ -722,10 +736,7 @@ RelationGetPartitionDesc(Relation relation, PartitionKey key,
 	relation->rd_partdesc = MemoryContextAllocZero(relation->rd_pdcxt,
 													sizeof(PartitionDescData));
 	oldcxt = MemoryContextSwitchTo(relation->rd_pdcxt);
-	relation->rd_partdesc = CopyPartitionDesc(result,
-											  key->partnatts,
-											  key->strategy,
-											  typinfo);
+	relation->rd_partdesc = CopyPartitionDesc(result, key);
 	MemoryContextSwitchTo(oldcxt);
 
 	return result;
@@ -769,8 +780,7 @@ range_partition_cmp_max(const void *a, const void *b, void *arg)
  *		Copy PartitionDesc of a partitioned relation into caller's context
  */
 static PartitionDesc
-CopyPartitionDesc(PartitionDesc src, int partnatts, char strategy,
-				  PartitionKeyTypeInfo *typinfo)
+CopyPartitionDesc(PartitionDesc src, PartitionKey key)
 {
 	PartitionDesc	result;
 	int				i, j;
@@ -782,7 +792,7 @@ CopyPartitionDesc(PartitionDesc src, int partnatts, char strategy,
 	if (result->numparts > 0)
 	{
 		result->oids = (Oid *) palloc0(result->numparts * sizeof(Oid));
-		switch (strategy)
+		switch (key->strategy)
 		{
 			case PARTITION_STRAT_LIST:
 				result->listnvalues = (int *)
@@ -791,7 +801,7 @@ CopyPartitionDesc(PartitionDesc src, int partnatts, char strategy,
 								palloc0(result->numparts * sizeof(Datum *));
 				break;
 			case PARTITION_STRAT_RANGE:
-				for (i = 0; i < partnatts; i++)
+				for (i = 0; i < key->partnatts; i++)
 					result->rangemaxs[i] = (Datum *)
 								palloc0(result->numparts * sizeof(Datum));
 				break;
@@ -802,7 +812,7 @@ CopyPartitionDesc(PartitionDesc src, int partnatts, char strategy,
 	{
 		result->oids[i] = src->oids[i];
 
-		switch (strategy)
+		switch (key->strategy)
 		{
 			case PARTITION_STRAT_LIST:
 				result->listnvalues[i] = src->listnvalues[i];
@@ -814,12 +824,12 @@ CopyPartitionDesc(PartitionDesc src, int partnatts, char strategy,
 					Datum   *from = &src->listvalues[i][j];
 					Datum   *into = &result->listvalues[i][j];
 
-					if (!typinfo->typbyval[0])
+					if (!key->typinfo->typbyval[0])
 					{
-						if (typinfo->typlen[0] == -1)
+						if (key->typinfo->typlen[0] == -1)
 							*into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
 						else
-							*into = datumCopy(*from, false, typinfo->typlen[0]);
+							*into = datumCopy(*from, false, key->typinfo->typlen[0]);
 					}
 					else
 						*into = *from;
@@ -827,17 +837,17 @@ CopyPartitionDesc(PartitionDesc src, int partnatts, char strategy,
 				break;
 
 			case PARTITION_STRAT_RANGE:
-				for (j = 0; j < partnatts; j++)
+				for (j = 0; j < key->partnatts; j++)
 				{
 					Datum   *from = &src->rangemaxs[j][i];
 					Datum   *into = &result->rangemaxs[j][i];
 
-					if (!typinfo->typbyval[j])
+					if (!key->typinfo->typbyval[j])
 					{
-						if (typinfo->typlen[j] == -1)
+						if (key->typinfo->typlen[j] == -1)
 							*into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
 						else
-							*into = datumCopy(*from, false, typinfo->typlen[j]);
+							*into = datumCopy(*from, false, key->typinfo->typlen[j]);
 					}
 					else
 						*into = *from;
@@ -859,8 +869,8 @@ CopyPartitionDesc(PartitionDesc src, int partnatts, char strategy,
  * Returns number of partitions in *numparts.
  */
 static PartitionInfo **
-GetRelPartitions(Relation rel, PartitionKey key, PartitionKeyTypeInfo *typinfo,
-				 bool only_valid, int *numparts)
+GetRelPartitions(Relation rel, PartitionKey key, bool only_valid,
+				 int *numparts)
 {
 	List	   *partoids;
 	int			i;
@@ -906,10 +916,10 @@ GetRelPartitions(Relation rel, PartitionKey key, PartitionKeyTypeInfo *typinfo,
 		if (!isnull)
 		{
 			deconstruct_array(DatumGetArrayTypeP(datum),
-							  typinfo->typid[0],
-							  typinfo->typlen[0],
-							  typinfo->typbyval[0],
-							  typinfo->typalign[0],
+							  key->typinfo->typid[0],
+							  key->typinfo->typlen[0],
+							  key->typinfo->typbyval[0],
+							  key->typinfo->typalign[0],
 							  &result->listvalues, NULL, &result->listnvalues);
 
 			ReleaseSysCache(tuple);
@@ -941,10 +951,10 @@ GetRelPartitions(Relation rel, PartitionKey key, PartitionKeyTypeInfo *typinfo,
 			int			dummy;
 
 			deconstruct_array(arr,
-							  typinfo->typid[j],
-							  typinfo->typlen[j],
-							  typinfo->typbyval[j],
-							  typinfo->typalign[j],
+							  key->typinfo->typid[j],
+							  key->typinfo->typlen[j],
+							  key->typinfo->typbyval[j],
+							  key->typinfo->typalign[j],
 							  &datum, &nulls, &dummy);
 			Assert(!nulls[0]);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index adfaa2c..5e8726b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -458,12 +458,9 @@ static void ATExecChangePersistence(List **wqueue, AlteredTableInfo *tab,
 						Relation rel, char persistence, LOCKMODE lockmode);
 static void SetRelationPersistence(Oid relationId, char persistence);
 static Datum *EvalPartitionBound(Relation rootParent, Relation parent,
-						PartitionKey key, PartitionKeyTypeInfo *typinfo,
-						PartitionValues *values, int *listnvalues);
-static Datum *evalPartitionListBound(List *values, PartitionKeyTypeInfo *typinfo,
-						int nvalues);
-static Datum *evalPartitionRangeBound(List *values, PartitionKeyTypeInfo *typinfo,
-						int partnatts);
+						PartitionKey key, PartitionValues *values, int *listnvalues);
+static Datum *evalPartitionListBound(List *values, PartitionKey key);
+static Datum *evalPartitionRangeBound(List *values, PartitionKey key);
 static void ATExecAddPartition(Relation rootParent, RangeVar *name, RangeVar *parent,
 						PartitionValues *values);
 static void ATExecRemovePartition(Relation rootParent, RangeVar *name, bool is_detach,
@@ -6050,10 +6047,9 @@ is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
 	foreach(cell, keys)
 	{
 		PartitionKey	key = lfirst(cell);
-		int16		level = key->level;
 		int			partnatts = key->partnatts;
 		AttrNumber *partattrs = key->partattrs;
-		List	   *partexprs = RelationGetPartitionExpr(rel, level);
+		List	   *partexprs = key->partexprs;
 		ListCell   *partexpr_item;
 		int			i;
 
@@ -12918,7 +12914,6 @@ ATExecAddPartition(Relation rootParent, RangeVar *name, RangeVar *parent,
 	Datum		   *boundDatums;
 	int				listnvalues;
 	int				parent_level;
-	PartitionKeyTypeInfo *typinfo;
 
 	partRel = heap_openrv(name, NoLock);
 
@@ -12934,14 +12929,12 @@ ATExecAddPartition(Relation rootParent, RangeVar *name, RangeVar *parent,
 	partKey = is_subpart ? list_nth(rootParent->rd_partkeys, parent_level) :
 							linitial(rootParent->rd_partkeys);
 
-	typinfo = get_partition_key_type_info(rootParent, partKey);
-
 	/* Evaluate and validate new partition's bound */
-	boundDatums = EvalPartitionBound(rootParent, parentRel, partKey, typinfo,
-									 values, &listnvalues);
+	boundDatums = EvalPartitionBound(rootParent, parentRel, partKey, values,
+									 &listnvalues);
 
 	/* Update the catalog */
-	StorePartition(rootParent, partKey, typinfo,
+	StorePartition(rootParent, partKey,
 					RelationGetRelid(partRel),
 					RelationGetRelid(parentRel),
 					true,
@@ -12951,7 +12944,6 @@ ATExecAddPartition(Relation rootParent, RangeVar *name, RangeVar *parent,
 	/* Tell world about the new partition of parent. */
 	CacheInvalidateRelcache(parentRel);
 
-	free_partition_key_type_info(typinfo);
 	heap_close(partRel, NoLock);
 	if (parentRel != rootParent)
 		heap_close(parentRel, AccessExclusiveLock);
@@ -12967,8 +12959,7 @@ ATExecAddPartition(Relation rootParent, RangeVar *name, RangeVar *parent,
  */
 static Datum *
 EvalPartitionBound(Relation rootParent, Relation parent, PartitionKey key,
-				   PartitionKeyTypeInfo *typinfo, PartitionValues *values,
-				   int *listnvalues)
+				   PartitionValues *values, int *listnvalues)
 {
 	PartitionDesc	pdesc;
 	Datum	   *result;
@@ -12976,7 +12967,7 @@ EvalPartitionBound(Relation rootParent, Relation parent, PartitionKey key,
 	Oid			overlapsWith;
 	int			i;
 
-	pdesc = RelationGetPartitionDesc(parent, key, typinfo, true);
+	pdesc = RelationGetPartitionDesc(parent, key, true);
 
 	/* result->oid is set by the caller */
 
@@ -12988,8 +12979,7 @@ EvalPartitionBound(Relation rootParent, Relation parent, PartitionKey key,
 			Assert(list_length(values->listvalues) >= 1);
 
 			result = (Datum *) palloc0(*listnvalues * sizeof(Datum));
-			result = evalPartitionListBound(values->listvalues, typinfo,
-											*listnvalues);
+			result = evalPartitionListBound(values->listvalues, key);
 
 			/* TODO: uniqueify(result->listvalues) */
 
@@ -13005,7 +12995,7 @@ EvalPartitionBound(Relation rootParent, Relation parent, PartitionKey key,
 
 		case PARTITION_STRAT_RANGE:
 			Assert(list_length(values->rangemaxs) == key->partnatts);
-			rangemaxs = evalPartitionRangeBound(values->rangemaxs, typinfo, key->partnatts);
+			rangemaxs = evalPartitionRangeBound(values->rangemaxs, key);
 
 			if (range_partition_overlaps(pdesc, key, rangemaxs))
 				ereport(ERROR,
@@ -13037,8 +13027,10 @@ EvalPartitionBound(Relation rootParent, Relation parent, PartitionKey key,
 				lbs[0] = 1;
 
 				rangemax = construct_md_array(datums, nulls, 1, dims, lbs,
-												typinfo->typid[i], typinfo->typlen[i],
-												typinfo->typbyval[i], typinfo->typalign[i]);
+											  key->typinfo->typid[i],
+											  key->typinfo->typlen[i],
+											  key->typinfo->typbyval[i],
+											  key->typinfo->typalign[i]);
 
 				result[i] = PointerGetDatum(rangemax);
 			}
@@ -13053,19 +13045,19 @@ EvalPartitionBound(Relation rootParent, Relation parent, PartitionKey key,
  *		for a list partition
  */
 static Datum *
-evalPartitionListBound(List *values, PartitionKeyTypeInfo *typinfo, int nvalues)
+evalPartitionListBound(List *values, PartitionKey key)
 {
 	ListCell	*cell;
 	ParseState	*pstate;
 	EState		*estate;
 	ExprContext	*ecxt;
 	int			i;
-	Datum		*datum;
+	Datum	   *datum;
 
 	if (!values)
 		return NULL;
 
-	datum = (Datum *) palloc(nvalues * sizeof(Datum));
+	datum = (Datum *) palloc(list_length(values) * sizeof(Datum));
 	pstate = make_parsestate(NULL);
 	estate = CreateExecutorState();
 	ecxt = GetPerTupleExprContext(estate);
@@ -13089,12 +13081,12 @@ evalPartitionListBound(List *values, PartitionKeyTypeInfo *typinfo, int nvalues)
 
 		MemoryContextSwitchTo(oldcxt);
 
-		if (!typinfo->typbyval[0])
+		if (!key->typinfo->typbyval[0])
 		{
-			if (typinfo->typlen[0] == -1)
+			if (key->typinfo->typlen[0] == -1)
 				datum[i] = PointerGetDatum(PG_DETOAST_DATUM_COPY(datum[i]));
 			else
-				datum[i] = datumCopy(datum[i], false, typinfo->typlen[0]);
+				datum[i] = datumCopy(datum[i], false, key->typinfo->typlen[0]);
 		}
 
 		ResetPerTupleExprContext(estate);
@@ -13110,7 +13102,7 @@ evalPartitionListBound(List *values, PartitionKeyTypeInfo *typinfo, int nvalues)
  *		clause for a range partition
  */
 static Datum *
-evalPartitionRangeBound(List *values, PartitionKeyTypeInfo *typinfo, int partnatts)
+evalPartitionRangeBound(List *values, PartitionKey key)
 {
 	ListCell	*cell;
 	ParseState	*pstate;
@@ -13122,7 +13114,7 @@ evalPartitionRangeBound(List *values, PartitionKeyTypeInfo *typinfo, int partnat
 	if (!values)
 		return NULL;
 
-	datum = (Datum *) palloc(partnatts * sizeof(Datum));
+	datum = (Datum *) palloc(key->partnatts * sizeof(Datum));
 	pstate = make_parsestate(NULL);
 	estate = CreateExecutorState();
 	ecxt = GetPerTupleExprContext(estate);
@@ -13146,12 +13138,12 @@ evalPartitionRangeBound(List *values, PartitionKeyTypeInfo *typinfo, int partnat
 
 		MemoryContextSwitchTo(oldcxt);
 
-		if (!typinfo->typbyval[i])
+		if (!key->typinfo->typbyval[i])
 		{
-			if (typinfo->typlen[i] == -1)
+			if (key->typinfo->typlen[i] == -1)
 				datum[i] = PointerGetDatum(PG_DETOAST_DATUM_COPY(datum[i]));
 			else
-				datum[i] = datumCopy(datum[i], false, typinfo->typlen[i]);
+				datum[i] = datumCopy(datum[i], false, key->typinfo->typlen[i]);
 		}
 
 		ResetPerTupleExprContext(estate);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 94731ed..cdfea1c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3208,12 +3208,8 @@ transformPartitionValues(CreateStmtContext *cxt, PartitionKey key,
 {
 	int			i;
 	ListCell   *cell;
-	PartitionKeyTypeInfo *typinfo;
 	PartitionValues		*result = (PartitionValues *)
 											makeNode(PartitionValues);
-
-	typinfo = get_partition_key_type_info(cxt->rel, key);
-
 	switch (key->strategy)
 	{
 		case PARTITION_STRAT_LIST:
@@ -3234,8 +3230,8 @@ transformPartitionValues(CreateStmtContext *cxt, PartitionKey key,
 				valuetype = exprType(value);
 				value = coerce_to_target_type(cxt->pstate,
 											value, valuetype,
-											typinfo->typid[0],
-											typinfo->typmod[0],
+											key->typinfo->typid[0],
+											key->typinfo->typmod[0],
 											COERCION_ASSIGNMENT,
 											COERCE_IMPLICIT_CAST,
 											-1);
@@ -3244,7 +3240,7 @@ transformPartitionValues(CreateStmtContext *cxt, PartitionKey key,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("partition key column is of type %s"
 								" but expression is of type %s",
-									format_type_be(typinfo->typid[0]),
+									format_type_be(key->typinfo->typid[0]),
 									format_type_be(valuetype)),
 						 errhint("You will need to rewrite or cast the expression."),
 						 parser_errposition(cxt->pstate, exprLocation(orig_value))));
@@ -3288,8 +3284,8 @@ transformPartitionValues(CreateStmtContext *cxt, PartitionKey key,
 				valuetype = exprType(value);
 				value = coerce_to_target_type(NULL,
 											value, valuetype,
-											typinfo->typid[i],
-											typinfo->typmod[i],
+											key->typinfo->typid[i],
+											key->typinfo->typmod[i],
 											COERCION_ASSIGNMENT,
 											COERCE_IMPLICIT_CAST,
 											-1);
@@ -3298,7 +3294,7 @@ transformPartitionValues(CreateStmtContext *cxt, PartitionKey key,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("partition key column is of type %s"
 								" but expression is of type %s",
-									format_type_be(typinfo->typid[0]),
+									format_type_be(key->typinfo->typid[0]),
 									format_type_be(valuetype)),
 						 errhint("You will need to rewrite or cast the expression."),
 						 parser_errposition(cxt->pstate, exprLocation(orig_value))));
@@ -3309,7 +3305,6 @@ transformPartitionValues(CreateStmtContext *cxt, PartitionKey key,
 			break;
 	}
 	result->location = values->location;
-	free_partition_key_type_info(typinfo);
 
 	return result;
 }
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ddb0da2..f6c656c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4514,94 +4514,6 @@ RelationGetExclusionInfo(Relation indexRelation,
 }
 
 /*
- * RelationGetPartitionExpr -- get the partition key expressions
- *
- * The returned node tree is copied into the caller's memory context. (We
- * don't want to return a pointer to the relcache copy,since it could
- * disappear due to relcache invalidation.)
- */
-List *
-RelationGetPartitionExpr(Relation relation, int16 level)
-{
-	ListCell   *cell;
-	Relation	catalog;
-	HeapTuple	tuple;
-	List	   *result = NIL;
-	Datum		exprsDatum;
-	bool		isnull;
-	char	   *exprsString;
-	MemoryContext oldcxt;
-
-	/* Shouldn't happen but play safe! */
-	if (relation->rd_partkeys == NIL)
-		return NIL;
-
-	/* Find the proper key in relcache */
-	foreach(cell, relation->rd_partkeys)
-	{
-		PartitionKey	key = lfirst(cell);
-
-		if (key->level != level)
-			continue;
-
-		/* Quick copy if we already computed the result */
-		if (key->partexprs)
-			return (List *) copyObject(key->partexprs);
-
-		/* Need to compute from the tuple in the syscache */
-		tuple = SearchSysCache2(PARTEDRELIDLEVEL,
-								RelationGetRelid(relation),
-								level);
-		Assert(HeapTupleIsValid(tuple));
-
-		/*
-		 * We build the tree we intend to return in the caller's context.
-		 * After successfully completing the work, we copy it into the
-		 * relcache entry. This avoids problems if we get some sort of
-		 * error partway through.
-		 */
-		catalog = heap_open(PartitionedRelRelationId, AccessShareLock);
-		exprsDatum = heap_getattr(tuple,
-								Anum_pg_partitioned_rel_partexprs,
-								RelationGetDescr(catalog),
-								&isnull);
-
-		/* No expressions in the key at this level */
-		if (isnull)
-		{
-			ReleaseSysCache(tuple);
-			heap_close(catalog, AccessShareLock);
-			return NIL;
-		}
-
-		exprsString = TextDatumGetCString(exprsDatum);
-		result = (List *) stringToNode(exprsString);
-		pfree(exprsString);
-
-		/*
-		 * Run the expressions through eval_const_expressions. This is not just an
-		 * optimization, but is necessary, because 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.
-		 */
-		result = (List *) eval_const_expressions(NULL, (Node *) result);
-
-		/* May as well fix opfuncids too */
-		fix_opfuncids((Node *) result);
-
-		/* Now save a copy of the completed tree in the relcache entry. */
-		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
-		key->partexprs = (List *) copyObject(result);
-		MemoryContextSwitchTo(oldcxt);
-
-		ReleaseSysCache(tuple);
-		heap_close(catalog, AccessShareLock);
-	}
-
-	return result;
-}
-
-/*
  * Routines to support ereport() reports of relation-related errors
  *
  * These could have been put into elog.c, but it seems like a module layering
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 7f1576a..c54fc1b 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -16,29 +16,30 @@
 #include "fmgr.h"
 #include "utils/relcache.h"
 
-/* Partition key information for one level */
-typedef struct PartitionKeyData
-{
-	char		strategy;		/* partition strategy */
-	int16		level;			/* partition level */
-	int16		partnatts;		/* number of partition attributes */
-	AttrNumber *partattrs;		/* partition attnums */
-	Oid		   *partopfamily;	/* OIDs of opfamily per col */
-	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
-	List	   *partexprs;		/* partition key expressions, if any */
-} PartitionKeyData;
-
-typedef struct PartitionKeyData *PartitionKey;
-
 /* Type information for partition key columns */
-typedef struct PartitionKeyTypeInfo
+typedef struct KeyTypeInfo
 {
 	Oid		*typid;
 	int32	*typmod;
 	int16	*typlen;
 	bool	*typbyval;
 	char	*typalign;
-} PartitionKeyTypeInfo;
+} KeyTypeInfo;
+
+/* Partition key information for one level */
+typedef struct PartitionKeyData
+{
+	char			strategy;		/* partition strategy */
+	int16			level;			/* partition level */
+	int16			partnatts;		/* number of partition attributes */
+	AttrNumber	   *partattrs;		/* partition attnums */
+	List		   *partexprs;		/* partition key expressions, if any */
+	KeyTypeInfo	   *typinfo;		/* key column type information */
+	Oid			   *partopfamily;	/* OIDs of opfamily per col */
+	FmgrInfo	   *partsupfunc;	/* lookup info for support funcs */
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
 
 /*
  * Information about partitions of a partitioned table or sub-partitions
@@ -76,13 +77,9 @@ extern void RemovePartitionKeysByRelId(Oid relid);
 extern void RelationBuildPartitionKeys(Relation relation);
 extern List *CopyPartitionKeys(List *keys);
 extern void FreePartitionKeys(List *keys);
-extern PartitionKeyTypeInfo *get_partition_key_type_info(Relation rel,
-					PartitionKey key);
-extern void free_partition_key_type_info(PartitionKeyTypeInfo *typinfo);
 extern bool relid_is_partitioned(Oid relid);
 
 extern void StorePartition(Relation rel, PartitionKey key,
-					PartitionKeyTypeInfo *typinfo,
 					Oid partitionOid,
 					Oid parentOid,
 					bool valid,
@@ -91,9 +88,7 @@ extern void StorePartition(Relation rel, PartitionKey key,
 extern void RemovePartitionEntryByRelId(Oid relid);
 extern void RelationDropPartitions(Oid relid);
 extern PartitionDesc RelationGetPartitionDesc(Relation relation,
-					PartitionKey key,
-					PartitionKeyTypeInfo *typinfo,
-					bool include_all);
+					PartitionKey key, bool include_all);
 extern List *get_partitions(Oid relid, int lockmode);
 extern List *get_all_partitions(Oid relid, int lockmode);
 extern List *get_leaf_partitions(Oid relid, int lockmode);
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 0aa2104..1b48304 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -42,8 +42,6 @@ extern Oid	RelationGetOidIndex(Relation relation);
 extern Oid	RelationGetReplicaIndex(Relation relation);
 extern List *RelationGetIndexExpressions(Relation relation);
 extern List *RelationGetIndexPredicate(Relation relation);
-extern List *RelationGetPartitionExpr(Relation relation,
-									int16 level);
 
 typedef enum IndexAttrBitmapKind
 {
-- 
1.7.1

  [text/x-diff] 0009-Tuple-routing-functionality.patch (40.7K, ../56C1302D.6070403@lab.ntt.co.jp/10-0009-Tuple-routing-functionality.patch)
  download | inline diff:
From 0d7d2c90ca2a61dd679bbc98ecf69d865c8e8055 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 10 Feb 2016 19:42:54 +0900
Subject: [PATCH 09/10] Tuple routing functionality.

Newly introduced get_partition_for_tuple takes a tuple (TupleTableSlot)
and returns OID of the leaf partition to which the tuple maps to. Teach
CopyFrom() and ExecInsert() to utilize this new interface.

Newly introduced PartitionKeyExecInfo structure can be used to derive
partition key from a tuple being routed. It mirrors the PartitionKey but
contains some executor state as well. New field ri_PartitionKeyInfo
is a list of these structures.

ResultRelInfo has one more new field ri_PartitionDesc which is the
partition descriptor of the root table and initialized in
InitResultRelInfo along with ri_PartitionKeyInfo. Have to find a
way to be able to somehow cache relation descriptors of internal
partition in EState because they need to be computed (well copied
from relcache) for each tuple routed.

ExecUpdate and ExecDelete require more information viz. OID of the
partition that the modified or to-be-deleted row came from. Extend
JunkFilter mechanism to help with that. Also, during rewrite, if the
UPDATE/DELETE target is a partitioned table, add junk TLE for tableoid
column so that the above works. Of course, UPDATE and DELETE cases
don't work yet because there is no way yet to scan a partitioned table
which will be fixed shortly.

Note that INSERT's ON CONFLICT clause is not supported.
---
 src/backend/catalog/partition.c        |  338 ++++++++++++++++++++++++++++++++
 src/backend/commands/copy.c            |  106 ++++++++++-
 src/backend/executor/execMain.c        |   26 +--
 src/backend/executor/nodeModifyTable.c |  299 ++++++++++++++++++++++++++--
 src/backend/parser/parse_clause.c      |    9 +
 src/backend/rewrite/rewriteHandler.c   |   25 +++
 src/include/catalog/partition.h        |    9 +
 src/include/executor/executor.h        |   15 ++
 src/include/nodes/execnodes.h          |   25 +++
 src/include/nodes/nodes.h              |    1 +
 10 files changed, 815 insertions(+), 38 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index cf48bf4..9baa0a0 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -76,6 +76,31 @@ static int32 rangemaxs_cmp_datum(const Datum *vals,
 					int partnatts,
 					FmgrInfo *partsupfunc);
 
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					TupleTableSlot *slot,
+					EState *estate,
+					Datum *values,
+					bool *isnull);
+static Oid get_partition_for_tuple_recurse(Relation rel,
+					ListCell *key_cell,
+					ListCell *pkinfo_cell,
+					PartitionDesc pdesc,
+					TupleTableSlot *slot,
+					EState *estate);
+static Oid list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+					Datum value);
+static Oid range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+					Datum *values);
+typedef int32 (*bsearch_compare_fn) (const Datum *vals,
+							const Datum *rangemaxs,
+							int partnatts,
+							FmgrInfo *partsupfunc);
+static int range_minmax_bsearch(PartitionDesc pdesc,
+					FmgrInfo *partsupfunc,
+					int partnatts,
+					const Datum *key,
+					bsearch_compare_fn cmp_fn);
+
 /*
  * StorePartitionKey
  *		Store the partition keys of rel into pg_partitioned_rel catalog
@@ -1447,3 +1472,316 @@ rangemaxs_cmp_datum(const Datum *vals, const Datum *rangemaxs,
 
 	return 0;
 }
+
+/* ----------------
+ *	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 relations;
+ * especially partition expression state. Normally we build a
+ * PartitionKeyExecInfo for a partitioned relation just once per
+ * command, and then use it for (potentially) many tuples.
+ * ----------------
+ */
+List *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	List	   *result = NIL;
+	ListCell   *cell;
+	int16		level;
+
+	level = 1;
+	foreach (cell, rel->rd_partkeys)
+	{
+		int				i;
+		PartitionKey	key = (PartitionKey) lfirst(cell);
+		PartitionKeyExecInfo   *pi = makeNode(PartitionKeyExecInfo);
+		int numKeys = key->partnatts;
+
+		if (numKeys < 1 || numKeys > PARTITION_MAX_KEYS)
+		elog(ERROR, "invalid partnatts %d for level %d key of partitioned"
+					" relation %u", numKeys, level, RelationGetRelid(rel));
+
+		pi->pi_NumKeyAttrs = numKeys;
+		for (i = 0; i < numKeys; i++)
+			pi->pi_KeyAttrNumbers[i] = key->partattrs[i];
+
+		/* Fetch any partition key expressions */
+		pi->pi_Expressions = copyObject(key->partexprs);
+		pi->pi_ExpressionsState = NIL;
+
+		result = lappend(result, pi);
+		level++;
+	}
+
+	return result;
+}
+
+/*
+ * FormPartitionKeyDatum
+ *		Construct values[] and isnull[] arrays for partition key columns
+ *		from the tuple contained in slot
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Expressions != NIL &&
+		pkinfo->pi_ExpressionsState == NIL)
+	{
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionsState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Expressions,
+							estate);
+		/* Check caller has set up context correctly */
+		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	}
+	partexpr_item = list_head(pkinfo->pi_ExpressionsState);
+
+	for (i = 0; i < pkinfo->pi_NumKeyAttrs; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_KeyAttrNumbers[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
+ *		Find the partition for tuple (in slot)
+ */
+Oid
+get_partition_for_tuple(Relation rel,
+						List *pkinfo,
+						PartitionDesc pdesc,
+						TupleTableSlot *slot,
+						EState *estate)
+{
+	if (pdesc->numparts == 0)
+		return InvalidOid;
+
+	return get_partition_for_tuple_recurse(rel,
+										list_head(rel->rd_partkeys),
+										list_head(pkinfo),
+										pdesc, slot, estate);
+}
+
+static Oid
+get_partition_for_tuple_recurse(Relation rel,
+						ListCell *key_cell,
+						ListCell *pkinfo_cell,
+						PartitionDesc pdesc,
+						TupleTableSlot *slot,
+						EState *estate)
+{
+	PartitionKey			key = lfirst(key_cell);
+	PartitionKeyExecInfo   *pkinfo = lfirst(pkinfo_cell);
+	Relation		targetRel;
+	PartitionKey	targetKey;
+	PartitionDesc	target_pdesc;
+	Oid		targetOid,
+			relid = RelationGetRelid(rel);
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		i;
+
+	if (pdesc->numparts == 0)
+		return InvalidOid;
+
+	/* Extract partition key from tuple */
+	Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	FormPartitionKeyDatum(pkinfo,
+						  slot,
+						  estate,
+						  values,
+						  isnull);
+
+	/* Disallow nulls in partition key */
+	for (i = 0; i < pkinfo->pi_NumKeyAttrs; i++)
+		if (isnull[i])
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("null value for partition key column \"%s\" not allowed",
+							get_attname(relid, pkinfo->pi_KeyAttrNumbers[i]))));
+
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			/* Remember, list partition key always consists of single column */
+			targetOid = list_partition_for_tuple(key, pdesc, values[0]);
+			break;
+
+		case PARTITION_STRAT_RANGE:
+			targetOid = range_partition_for_tuple(key, pdesc, values);
+			break;
+	}
+
+	/* this is how recursion ends (or doesn't occur at all) */
+	if (lnext(key_cell) == NULL || !OidIsValid(targetOid))
+		return targetOid;
+
+	targetRel = heap_open(targetOid, AccessShareLock);
+	targetKey = lfirst(lnext(key_cell));
+	target_pdesc = RelationGetPartitionDesc(targetRel, targetKey, true);
+	targetOid = get_partition_for_tuple_recurse(rel, lnext(key_cell),
+											lnext(pkinfo_cell),
+											target_pdesc, slot, estate);
+	heap_close(targetRel, AccessShareLock);
+
+	return targetOid;
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple
+ *
+ * There is single datum to compare because the partition key in case of
+ * list partitioning consists of only one column.
+ */
+static Oid
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum value)
+{
+	int			i,
+				j;
+	int32		result;
+	FmgrInfo	cmpfn = key->partsupfunc[0];
+
+	Assert(pdesc->numparts > 0);
+
+	for (i = 0; i < pdesc->numparts; i++)
+	{
+		for (j = 0; j < pdesc->listnvalues[i]; j++)
+		{
+			result = compare_using_func(cmpfn,
+										pdesc->listvalues[i][j],
+										value);
+
+			if (!result)
+				return pdesc->oids[i];
+		}
+	}
+
+	return InvalidOid;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Find the range partition for a tuple
+ *
+ * Range partitioning supports multi-column key, so the key may contain
+ * more than one datum. Comparison function rangemaxs_cmp_datum can
+ * deal with that.
+ */
+static Oid
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *values)
+{
+	int			partidx;
+
+	Assert(pdesc->numparts > 0);
+
+	partidx = range_minmax_bsearch(pdesc,
+								   key->partsupfunc,
+								   key->partnatts,
+								   values,
+								   rangemaxs_cmp_datum);
+
+	if (partidx >= 0)
+		return pdesc->oids[partidx];
+
+	return InvalidOid;
+}
+
+/*
+ * range_minmax_bsearch
+ *		Search for partition such that left.rangemax <= key < rangemax
+ *
+ * Returns index of the so found partition or -1 if key is found to be
+ * greater than the rangemax of the last partition.
+ */
+static int
+range_minmax_bsearch(PartitionDesc pdesc,
+					 FmgrInfo *partsupfunc,
+					 int partnatts,
+					 const Datum *key,
+					 bsearch_compare_fn cmp_fn)
+{
+	int32	cmpval;
+	int		low, high, idx, i, result;
+	Datum  *rangemaxs;
+
+	rangemaxs = (Datum *) palloc0(partnatts * sizeof(Datum));
+
+	/* Good ol' bsearch */
+	low = 0;
+	high = pdesc->numparts - 1;
+	result = -1;
+	while (low <= high)
+	{
+		idx = (low + high) / 2;
+
+		for (i = 0; i < partnatts; i++)
+			rangemaxs[i] = pdesc->rangemaxs[i][idx];
+
+		cmpval = cmp_fn(key, rangemaxs, partnatts, partsupfunc);
+		if (cmpval < 0)
+		{
+			if (idx == 0)
+			{
+				/* Nothing on left */
+				result = idx;
+				break;
+			}
+			else
+			{
+				for (i = 0; i < partnatts; i++)
+					rangemaxs[i] = pdesc->rangemaxs[i][idx-1];
+
+				cmpval = cmp_fn(key, rangemaxs, partnatts, partsupfunc);
+
+				if (cmpval >= 0)
+				{
+					result = idx;
+					break;
+				}
+				else
+					high = idx - 1;
+			}
+		}
+		else
+			low = idx + 1;
+	}
+
+	pfree(rangemaxs);
+	return result;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 62b650f..e54a61e 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -36,6 +36,7 @@
 #include "miscadmin.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "parser/parsetree.h"
 #include "nodes/makefuncs.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
@@ -43,6 +44,7 @@
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "catalog/partition.h"
 #include "utils/portal.h"
 #include "utils/rel.h"
 #include "utils/rls.h"
@@ -2223,6 +2225,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;	/* for partitioned table case */
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2231,7 +2234,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;
@@ -2240,6 +2243,8 @@ CopyFrom(CopyState cstate)
 	HeapTuple  *bufferedTuples = NULL;	/* initialize to silence warning */
 	Size		bufferedTuplesSize = 0;
 	int			firstBufferedLineNo = 0;
+	Oid				prev_partOid = InvalidOid;
+	ResultRelInfo  *prev_partrri = NULL;
 
 	Assert(cstate->rel);
 
@@ -2381,10 +2386,14 @@ CopyFrom(CopyState cstate)
 	 * 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.
+	 *
+	 * Also, Do not use multi- mode if inserting into a partitioned table
+	 * because we need to determine a partition for each row individually.
 	 */
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		resultRelInfo->ri_PartitionDesc != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2409,7 +2418,9 @@ CopyFrom(CopyState cstate)
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (resultRelInfo->ri_PartitionDesc == NULL)
+		bistate = GetBulkInsertState();
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2481,6 +2492,76 @@ CopyFrom(CopyState cstate)
 			if (cstate->rel->rd_att->constr)
 				ExecConstraints(resultRelInfo, slot, estate);
 
+			/* Find the partition for this tuple */
+			if (resultRelInfo->ri_PartitionDesc)
+			{
+				Oid		partOid;
+
+				econtext->ecxt_scantuple = slot;	/* for partition key expressions */
+				partOid = get_partition_for_tuple(cstate->rel,
+												  resultRelInfo->ri_PartitionKeyInfo,
+												  resultRelInfo->ri_PartitionDesc,
+												  slot,
+												  estate);
+				if (OidIsValid(partOid))
+				{
+					saved_resultRelInfo = resultRelInfo;
+
+					/* check if new tuple mapped to the same partition as the last one */
+					if (partOid != prev_partOid)
+					{
+						Relation	partRelDesc;
+
+						/* close the last partition */
+						if (prev_partrri)
+						{
+							heap_close(prev_partrri->ri_RelationDesc, RowExclusiveLock);
+							ExecCloseIndices(prev_partrri);
+							pfree(prev_partrri);
+						}
+
+						/* open the new one */
+						partRelDesc = heap_open(partOid, RowExclusiveLock);
+						resultRelInfo = makeNode(ResultRelInfo);
+						InitResultRelInfo(resultRelInfo,
+										  partRelDesc,
+										  1,		/* dummy */
+										  0);
+						ExecOpenIndices(resultRelInfo, false);
+						prev_partOid = partOid;
+					}
+					else
+					{
+						/* keep using the last one */
+						resultRelInfo = prev_partrri;
+					}
+
+					estate->es_result_relation_info = resultRelInfo;
+				}
+				else
+				{
+					char	   *val_desc;
+					Bitmapset  *modifiedCols;
+					Bitmapset  *insertedCols;
+					Bitmapset  *updatedCols;
+
+					insertedCols = GetInsertedColumns(resultRelInfo, estate);
+					updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+					modifiedCols = bms_union(insertedCols, updatedCols);
+					val_desc = ExecBuildSlotValueDescription(RelationGetRelid(cstate->rel),
+															 slot,
+															 RelationGetDescr(cstate->rel),
+															 modifiedCols,
+															 64);
+
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot find partition of relation \"%s\" for the new row",
+									RelationGetRelationName(cstate->rel)),
+							 val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+				}
+			}
+
 			if (useHeapMultiInsert)
 			{
 				/* Add this tuple to the tuple buffer */
@@ -2511,7 +2592,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),
@@ -2532,6 +2614,14 @@ CopyFrom(CopyState cstate)
 			 */
 			processed++;
 		}
+
+		/* Restore the parent resultRelInfo */
+		if (saved_resultRelInfo)
+		{
+			prev_partrri = resultRelInfo;
+			resultRelInfo = saved_resultRelInfo;
+			estate->es_result_relation_info = resultRelInfo;
+		}
 	}
 
 	/* Flush any remaining buffered tuples */
@@ -2544,7 +2634,15 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
+
+	if (prev_partrri)
+	{
+		heap_close(prev_partrri->ri_RelationDesc, RowExclusiveLock);
+		ExecCloseIndices(prev_partrri);
+		pfree(prev_partrri);
+	}
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 0f11922..13b5815 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -87,25 +87,9 @@ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
 						  Bitmapset *modifiedCols,
 						  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
-static char *ExecBuildSlotValueDescription(Oid reloid,
-							  TupleTableSlot *slot,
-							  TupleDesc tupdesc,
-							  Bitmapset *modifiedCols,
-							  int maxfieldlen);
 static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
 				  Plan *planTree);
 
-/*
- * Note that GetUpdatedColumns() also exists in commands/trigger.c.  There does
- * not appear to be any good header to put it into, given the structures that
- * it uses, so we let them be duplicated.  Be sure to update both if one needs
- * to be changed, however.
- */
-#define GetInsertedColumns(relinfo, estate) \
-	(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->insertedCols)
-#define GetUpdatedColumns(relinfo, estate) \
-	(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->updatedCols)
-
 /* end of local decls */
 
 
@@ -1177,6 +1161,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_REL:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1273,6 +1258,13 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	if (resultRelationDesc->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+	{
+		resultRelInfo->ri_PartitionKeyInfo = BuildPartitionKeyExecInfo(resultRelationDesc);
+		resultRelInfo->ri_PartitionDesc = RelationGetPartitionDesc(resultRelationDesc,
+											linitial(resultRelationDesc->rd_partkeys),
+											true);
+	}
 }
 
 /*
@@ -1914,7 +1906,7 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
  * column involved, that subset will be returned with a key identifying which
  * columns they are.
  */
-static char *
+char *
 ExecBuildSlotValueDescription(Oid reloid,
 							  TupleTableSlot *slot,
 							  TupleDesc tupdesc,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 27051e8..492a9d0 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -39,11 +39,13 @@
 
 #include "access/htup_details.h"
 #include "access/xact.h"
+#include "catalog/partition.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/nodeModifyTable.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
+#include "parser/parsetree.h"
 #include "nodes/nodeFuncs.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
@@ -226,6 +228,8 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;	/* for partitioned table case */
+	ExprContext   *econtext;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -340,6 +344,62 @@ ExecInsert(ModifyTableState *mtstate,
 		if (resultRelationDesc->rd_att->constr)
 			ExecConstraints(resultRelInfo, slot, estate);
 
+		/*
+		 * Switch resultRelInfo to one corresponding to the partition
+		 * that this tuple maps to.
+		 */
+		if (resultRelInfo->ri_PartitionDesc)
+		{
+			Oid		partOid;
+
+			econtext = GetPerTupleExprContext(estate);
+			econtext->ecxt_scantuple = slot;
+			partOid = get_partition_for_tuple(resultRelationDesc,
+											  resultRelInfo->ri_PartitionKeyInfo,
+											  resultRelInfo->ri_PartitionDesc,
+											  slot,
+											  estate);
+			if (partOid != InvalidOid)
+			{
+				Relation	partRelDesc;
+
+				partRelDesc = heap_open(partOid, RowExclusiveLock);
+
+				saved_resultRelInfo = resultRelInfo;
+				resultRelInfo = makeNode(ResultRelInfo);
+				InitResultRelInfo(resultRelInfo,
+								  partRelDesc,
+								  1,		/* dummy */
+								  0);
+
+				ExecOpenIndices(resultRelInfo, false);
+				estate->es_result_relation_info = resultRelInfo;
+				resultRelationDesc = resultRelInfo->ri_RelationDesc;
+			}
+			else
+			{
+				char	   *val_desc;
+				Bitmapset  *modifiedCols;
+				Bitmapset  *insertedCols;
+				Bitmapset  *updatedCols;
+
+				insertedCols = GetInsertedColumns(resultRelInfo, estate);
+				updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+				modifiedCols = bms_union(insertedCols, updatedCols);
+				val_desc = ExecBuildSlotValueDescription(RelationGetRelid(resultRelationDesc),
+														 slot,
+														 RelationGetDescr(resultRelationDesc),
+														 modifiedCols,
+														 64);
+
+				ereport(ERROR,
+						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						 errmsg("cannot find partition of relation \"%s\" for the new row",
+								RelationGetRelationName(resultRelationDesc)),
+						 val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+			}
+		}
+
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
 		{
 			/* Perform a speculative insertion. */
@@ -477,6 +537,17 @@ ExecInsert(ModifyTableState *mtstate,
 	/* AFTER ROW INSERT Triggers */
 	ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes);
 
+	/* Restore the resultRelInfo, if had been switched. */
+	if (saved_resultRelInfo)
+	{
+		heap_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock);
+		ExecCloseIndices(resultRelInfo);
+		pfree(resultRelInfo);
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+		saved_resultRelInfo = NULL; /* aka, paranoia! */
+	}
+
 	list_free(recheckIndexes);
 
 	/*
@@ -502,6 +573,29 @@ ExecInsert(ModifyTableState *mtstate,
 	return NULL;
 }
 
+/*
+ * find_partition_in_es_rt
+ *		A temporary band-aid solution to get partition's RT index in executor
+ */
+static int
+find_partition_in_es_rt(EState *estate, Oid oid)
+{
+	ListCell   *lc;
+	int			index = 1;
+
+	foreach(lc, estate->es_range_table)
+	{
+		RangeTblEntry   *rte = (RangeTblEntry *) lfirst(lc);
+
+		if (rte->relid == oid)
+			return index;
+
+		index++;
+	}
+
+	return 0;
+}
+
 /* ----------------------------------------------------------------
  *		ExecDelete
  *
@@ -522,6 +616,7 @@ ExecInsert(ModifyTableState *mtstate,
  */
 static TupleTableSlot *
 ExecDelete(ItemPointer tupleid,
+		   Oid partitionOid,
 		   HeapTuple oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
@@ -533,12 +628,14 @@ ExecDelete(ItemPointer tupleid,
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
 	TupleTableSlot *slot = NULL;
+	Index		resultRelationRTindex;
 
 	/*
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
+	resultRelationRTindex = resultRelInfo->ri_RangeTableIndex;
 
 	/* BEFORE ROW DELETE Triggers */
 	if (resultRelInfo->ri_TrigDesc &&
@@ -599,6 +696,13 @@ ExecDelete(ItemPointer tupleid,
 	}
 	else
 	{
+		if (partitionOid != InvalidOid)
+		{
+			resultRelationRTindex = find_partition_in_es_rt(estate,
+															partitionOid);
+			resultRelationDesc = heap_open(partitionOid, RowExclusiveLock);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -649,7 +753,7 @@ ldelete:;
 							 errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
 
 				/* Else, already deleted by self; nothing to do */
-				return NULL;
+				goto cleanup;
 
 			case HeapTupleMayBeUpdated:
 				break;
@@ -666,7 +770,7 @@ ldelete:;
 					epqslot = EvalPlanQual(estate,
 										   epqstate,
 										   resultRelationDesc,
-										   resultRelInfo->ri_RangeTableIndex,
+										   resultRelationRTindex,
 										   LockTupleExclusive,
 										   &hufd.ctid,
 										   hufd.xmax);
@@ -677,11 +781,11 @@ ldelete:;
 					}
 				}
 				/* tuple already deleted; nothing to do */
-				return NULL;
+				goto cleanup;
 
 			default:
 				elog(ERROR, "unrecognized heap_delete status: %u", result);
-				return NULL;
+				goto cleanup;
 		}
 
 		/*
@@ -751,10 +855,15 @@ ldelete:;
 		if (BufferIsValid(delbuffer))
 			ReleaseBuffer(delbuffer);
 
-		return rslot;
+		slot = rslot;
 	}
 
-	return NULL;
+cleanup:
+	/* close any partition relation, if we opened */
+	if (resultRelationDesc != resultRelInfo->ri_RelationDesc)
+		heap_close(resultRelationDesc, RowExclusiveLock);
+
+	return slot;
 }
 
 /* ----------------------------------------------------------------
@@ -781,6 +890,8 @@ ldelete:;
  */
 static TupleTableSlot *
 ExecUpdate(ItemPointer tupleid,
+		   Oid srcPartitionOid,
+		   bool is_partkey_update,
 		   HeapTuple oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
@@ -790,6 +901,8 @@ ExecUpdate(ItemPointer tupleid,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;	/* for partitioned table case */
+	ExprContext   *econtext;
 	Relation	resultRelationDesc;
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
@@ -896,6 +1009,72 @@ lreplace:;
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
+		 * Check whether the new row changes partition
+		 */
+		if (resultRelInfo->ri_PartitionDesc)
+		{
+			Oid		partOid;
+
+			if (is_partkey_update)
+			{
+				econtext = GetPerTupleExprContext(estate);
+				econtext->ecxt_scantuple = slot;
+				partOid = get_partition_for_tuple(resultRelationDesc,
+												  resultRelInfo->ri_PartitionKeyInfo,
+												  resultRelInfo->ri_PartitionDesc,
+												  slot,
+												  estate);
+
+				if (partOid != srcPartitionOid)
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot perform update that causes a row to change partition")));
+			}
+			else
+				partOid = srcPartitionOid;
+
+			if (OidIsValid(partOid))
+			{
+				Relation	partRelDesc;
+
+				partRelDesc = heap_open(partOid, RowExclusiveLock);
+
+				saved_resultRelInfo = resultRelInfo;
+				resultRelInfo = makeNode(ResultRelInfo);
+				InitResultRelInfo(resultRelInfo,
+								  partRelDesc,
+								  find_partition_in_es_rt(estate, partOid),
+								  0);
+
+				ExecOpenIndices(resultRelInfo, false);
+				estate->es_result_relation_info = resultRelInfo;
+				resultRelationDesc = resultRelInfo->ri_RelationDesc;
+			}
+			else
+			{
+				char	   *val_desc;
+				Bitmapset  *modifiedCols;
+				Bitmapset  *insertedCols;
+				Bitmapset  *updatedCols;
+
+				insertedCols = GetInsertedColumns(resultRelInfo, estate);
+				updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+				modifiedCols = bms_union(insertedCols, updatedCols);
+				val_desc = ExecBuildSlotValueDescription(RelationGetRelid(resultRelationDesc),
+														 slot,
+														 RelationGetDescr(resultRelationDesc),
+														 modifiedCols,
+														 64);
+
+				ereport(ERROR,
+						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						 errmsg("cannot find partition of relation \"%s\" for the new row",
+								RelationGetRelationName(resultRelationDesc)),
+						 val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+			}
+		}
+
+		/*
 		 * replace the heap tuple
 		 *
 		 * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check
@@ -943,7 +1122,7 @@ lreplace:;
 							 errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
 
 				/* Else, already updated by self; nothing to do */
-				return NULL;
+				goto cleanup;
 
 			case HeapTupleMayBeUpdated:
 				break;
@@ -973,11 +1152,11 @@ lreplace:;
 					}
 				}
 				/* tuple already deleted; nothing to do */
-				return NULL;
+				goto cleanup;
 
 			default:
 				elog(ERROR, "unrecognized heap_update status: %u", result);
-				return NULL;
+				goto cleanup;
 		}
 
 		/*
@@ -1008,6 +1187,17 @@ lreplace:;
 	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, tuple,
 						 recheckIndexes);
 
+	/* Restore the resultRelInfo, if had been switched. */
+	if (saved_resultRelInfo)
+	{
+		heap_close(resultRelationDesc, RowExclusiveLock);
+		ExecCloseIndices(resultRelInfo);
+		pfree(resultRelInfo);
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+		saved_resultRelInfo = NULL;
+	}
+
 	list_free(recheckIndexes);
 
 	/*
@@ -1027,6 +1217,15 @@ lreplace:;
 		return ExecProcessReturning(resultRelInfo->ri_projectReturning,
 									slot, planSlot);
 
+cleanup:
+	if (saved_resultRelInfo)
+	{
+		heap_close(resultRelationDesc, RowExclusiveLock);
+		ExecCloseIndices(resultRelInfo);
+		pfree(resultRelInfo);
+		estate->es_result_relation_info = saved_resultRelInfo;
+	}
+
 	return NULL;
 }
 
@@ -1214,8 +1413,8 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 */
 
 	/* Execute UPDATE with projection */
-	*returning = ExecUpdate(&tuple.t_self, NULL,
-							mtstate->mt_conflproj, planSlot,
+	*returning = ExecUpdate(&tuple.t_self, InvalidOid, false,
+							NULL, mtstate->mt_conflproj, planSlot,
 							&mtstate->mt_epqstate, mtstate->ps.state,
 							canSetTag);
 
@@ -1299,6 +1498,7 @@ ExecModifyTable(ModifyTableState *node)
 	ItemPointerData tuple_ctid;
 	HeapTupleData oldtupdata;
 	HeapTuple	oldtuple;
+	Oid			partitionOid = InvalidOid;
 
 	/*
 	 * This should NOT get called during EvalPlanQual; we should have passed a
@@ -1396,7 +1596,9 @@ ExecModifyTable(ModifyTableState *node)
 				bool		isNull;
 
 				relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
-				if (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW)
+				if (relkind == RELKIND_RELATION ||
+					relkind == RELKIND_PARTITIONED_REL ||
+					relkind == RELKIND_MATVIEW)
 				{
 					datum = ExecGetJunkAttribute(slot,
 												 junkfilter->jf_junkAttNo,
@@ -1447,6 +1649,22 @@ ExecModifyTable(ModifyTableState *node)
 				}
 				else
 					Assert(relkind == RELKIND_FOREIGN_TABLE);
+
+				/*
+				 * If this is a partition key update, get the source
+				 * partition OID.
+				 */
+				if (relkind == RELKIND_PARTITIONED_REL)
+				{
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkExtraAttNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "tableoid is NULL");
+
+					partitionOid = DatumGetObjectId(datum);
+				}
 			}
 
 			/*
@@ -1464,11 +1682,12 @@ ExecModifyTable(ModifyTableState *node)
 								  estate, node->canSetTag);
 				break;
 			case CMD_UPDATE:
-				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
-								&node->mt_epqstate, estate, node->canSetTag);
+				slot = ExecUpdate(tupleid, partitionOid, node->mt_partkeyupd,
+								oldtuple, slot, planSlot, &node->mt_epqstate,
+								estate, node->canSetTag);
 				break;
 			case CMD_DELETE:
-				slot = ExecDelete(tupleid, oldtuple, planSlot,
+				slot = ExecDelete(tupleid, partitionOid, oldtuple, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			default:
@@ -1500,6 +1719,35 @@ ExecModifyTable(ModifyTableState *node)
 	return NULL;
 }
 
+/*
+ * IsPartitionKeyUpdate
+ *		Checks if updated columns include any of the partitin key columns
+ *
+ * If there is a partition expression, simply return 'false'. We could maybe
+ * check if any updated column is involved in the expression hence causing
+ * partition key to be changed in a given row, but later.
+ */
+static bool
+IsPartitionKeyUpdate(ResultRelInfo *resultRelInfo, EState *estate)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	ListCell   *cell;
+	Bitmapset  *updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+
+	foreach (cell, rel->rd_partkeys)
+	{
+		PartitionKey	key = lfirst(cell);
+		int		i;
+
+		for (i = 0; i < key->partnatts; i++)
+			if (key->partattrs[i] == 0 ||
+				bms_is_member((int ) key->partattrs[i], updatedCols))
+				return true;
+	}
+
+	return false;
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitModifyTable
  * ----------------------------------------------------------------
@@ -1516,6 +1764,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	char		relkind;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1828,10 +2077,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 				if (operation == CMD_UPDATE || operation == CMD_DELETE)
 				{
 					/* For UPDATE/DELETE, find the appropriate junk attr now */
-					char		relkind;
-
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_REL ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
@@ -1852,6 +2100,17 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 						if (!AttributeNumberIsValid(j->jf_junkAttNo))
 							elog(ERROR, "could not find junk wholerow column");
 					}
+
+					/*
+					 * For partitioned table, find the extra junk attribute,
+					 * ie, "tableoid".
+					 */
+					if (relkind == RELKIND_PARTITIONED_REL)
+					{
+						j->jf_junkExtraAttNo = ExecFindJunkAttribute(j, "tableoid");
+						if (!AttributeNumberIsValid(j->jf_junkExtraAttNo))
+								elog(ERROR, "could not find junk tableoid column");
+					}
 				}
 
 				resultRelInfo->ri_junkFilter = j;
@@ -1866,6 +2125,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		}
 	}
 
+	relkind = mtstate->resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+	if (relkind == RELKIND_PARTITIONED_REL &&
+		operation == CMD_UPDATE &&
+		IsPartitionKeyUpdate(mtstate->resultRelInfo, estate))
+		mtstate->mt_partkeyupd = true;
+
 	/*
 	 * Set up a tuple table slot for use for trigger output tuples. In a plan
 	 * containing multiple ModifyTable nodes, all can share one such slot, so
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 04fa127..499da4c 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -2881,6 +2881,15 @@ transformOnConflictArbiter(ParseState *pstate,
 				 parser_errposition(pstate,
 								  exprLocation((Node *) onConflictClause))));
 
+	/* ON CONFLICT on partitioned tables currently disallowed */
+	if (pstate->p_target_relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			  errmsg("ON CONFLICT is not supported with partitioned tables"),
+				 parser_errposition(pstate,
+								  exprLocation((Node *) onConflictClause))));
+
+
 	/* ON CONFLICT DO NOTHING does not require an inference clause */
 	if (infer)
 	{
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..fa28b4d 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)
 	{
 		/*
@@ -1293,6 +1294,30 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 
 		parsetree->targetList = lappend(parsetree->targetList, tle);
 	}
+
+	/*
+	 * Also emit tableoid so that ExecModifyTable knows which partition
+	 * a given row came from. ExecUpdate will use it to throw error if
+	 * the new row does not map to the same partition.
+	 */
+	if (target_relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+	{
+		var = makeVar(parsetree->resultRelation,
+					  TableOidAttributeNumber,
+					  OIDOID,
+					  -1,
+					  InvalidOid,
+					  0);
+
+		attrname = "tableoid";
+
+		tle = makeTargetEntry((Expr *) var,
+							  list_length(parsetree->targetList) + 1,
+							  pstrdup(attrname),
+							  true);
+
+		parsetree->targetList = lappend(parsetree->targetList, tle);
+	}
 }
 
 
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index c54fc1b..81d8232 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 "utils/relcache.h"
 
 /* Type information for partition key columns */
@@ -106,4 +108,11 @@ extern bool list_partition_overlaps(PartitionDesc pdesc,
 extern bool range_partition_overlaps(PartitionDesc pdesc,
 								PartitionKey key,
 								Datum *rangemaxs);
+
+extern List *BuildPartitionKeyExecInfo(Relation rel);
+extern Oid get_partition_for_tuple(Relation rel,
+								List *pkinfo,
+								PartitionDesc pdesc,
+								TupleTableSlot *slot,
+								EState *estate);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 1a44085..a769126 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -219,6 +219,21 @@ extern void EvalPlanQualBegin(EPQState *epqstate, EState *parentestate);
 extern void EvalPlanQualEnd(EPQState *epqstate);
 
 /*
+ * Following used to be in execMain.c, brought here to be useful for copy.c,
+ * trigger.c, among others probably.
+ */
+#define GetInsertedColumns(relinfo, estate) \
+	(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->insertedCols)
+#define GetUpdatedColumns(relinfo, estate) \
+	(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->updatedCols)
+
+extern char *ExecBuildSlotValueDescription(Oid reloid,
+							  TupleTableSlot *slot,
+							  TupleDesc tupdesc,
+							  Bitmapset *modifiedCols,
+							  int maxfieldlen);
+
+/*
  * prototypes from functions in execProcnode.c
  */
 extern PlanState *ExecInitNode(Plan *node, EState *estate, int eflags);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 064a050..dec6200 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -76,6 +76,27 @@ typedef struct IndexInfo
 	bool		ii_BrokenHotChain;
 } IndexInfo;
 
+/*
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		NumKeyAttrs			number of columns in partition key
+ *		KeyAttrNumbers		underlying-rel attribute numbers used as keys
+ *							(zeroes indicate expressions)
+ *		Expressions			expr trees for expression entries, or NIL if none
+ *		ExpressionsState	exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag		type;
+	int			pi_NumKeyAttrs;
+	AttrNumber	pi_KeyAttrNumbers[PARTITION_MAX_KEYS];
+	List	   *pi_Expressions;			/* list of Expr */
+	List	   *pi_ExpressionsState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
 /* ----------------
  *	  ExprContext_CB
  *
@@ -290,6 +311,7 @@ typedef struct JunkFilter
 	AttrNumber *jf_cleanMap;
 	TupleTableSlot *jf_resultSlot;
 	AttrNumber	jf_junkAttNo;
+	AttrNumber  jf_junkExtraAttNo;
 } JunkFilter;
 
 /* ----------------
@@ -341,6 +363,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionKeyInfo;
+	struct PartitionDescData *ri_PartitionDesc;
 } ResultRelInfo;
 
 /* ----------------
@@ -1134,6 +1158,7 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	bool        mt_partkeyupd;  /* Does UPDATE change partition key cols */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 8032f0d..666bc1e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -37,6 +37,7 @@ typedef enum NodeTag
 	T_ResultRelInfo,
 	T_EState,
 	T_TupleTableSlot,
+	T_PartitionKeyExecInfo,
 
 	/*
 	 * TAGS FOR PLAN NODES (plannodes.h)
-- 
1.7.1

  [text/x-diff] 0010-Embarrasingly-small-optimizer-patch-to-work-with-par.patch (13.7K, ../56C1302D.6070403@lab.ntt.co.jp/11-0010-Embarrasingly-small-optimizer-patch-to-work-with-par.patch)
  download | inline diff:
From db212397d755dbcdc7d5394b42a1d7b3cbdcb3aa Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 12 Feb 2016 19:25:58 +0900
Subject: [PATCH 10/10] Embarrasingly small optimizer patch to work with partitioned tables.

Basically, this creates a expand_inherited_rtentry clone that works
for partitioned tables instead of inheritance sets. AppendRelInfo's
are created for leaf partitions and added to PlannerInfo.append_rel_list.
Then, wherever RangeTblEntry.inh check is used to switch to append rel
processing, a check for relid_is_partitioned(relid) is added.

Note that it is not the intention of this commit to make constraint
exclusion also work for partitioned tables. A subsequent commit will
teach partition module to install an equivalent CHECK constraint
whenever a new partition is created that will enable constraint
exclusion based partition pruning.

One ugliness: EXPLAIN's set_deparse_planstate() uses appendplans[0] or
mergeplans[0] to initialize deparse_namespace.outer_planstate. That
gives expected behavior for Append plans meant for inheritance sets
because the parent is 0th member. Same is not true in case of Append
meant for partitioned tables (which this patch creates), so a user
will see first leaf partition's name where they would expect to see
name of the table they used in the query (IOW, the parent table).
---
 src/backend/optimizer/path/allpaths.c  |    7 +-
 src/backend/optimizer/plan/planner.c   |    3 +
 src/backend/optimizer/prep/prepunion.c |  231 ++++++++++++++++++++++++++++++++
 src/backend/optimizer/util/plancat.c   |   14 ++
 src/backend/optimizer/util/relnode.c   |    4 +-
 src/include/optimizer/prep.h           |    2 +
 6 files changed, 258 insertions(+), 3 deletions(-)

diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 380b15e..81c2323 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -19,6 +19,7 @@
 
 #include "access/sysattr.h"
 #include "access/tsmapi.h"
+#include "catalog/partition.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
@@ -324,7 +325,8 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 		 */
 		set_dummy_rel_pathlist(rel);
 	}
-	else if (rte->inh)
+	else if (rte->inh || (rte->rtekind == RTE_RELATION &&
+							relid_is_partitioned(rte->relid)))
 	{
 		/* It's an "append relation", process accordingly */
 		set_append_rel_size(root, rel, rti, rte);
@@ -401,7 +403,8 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	{
 		/* We already proved the relation empty, so nothing more to do */
 	}
-	else if (rte->inh)
+	else if (rte->inh || (rte->rtekind == RTE_RELATION &&
+							relid_is_partitioned(rte->relid)))
 	{
 		/* It's an "append relation", process accordingly */
 		set_append_rel_pathlist(root, rel, rti, rte);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index f77c804..581924b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -538,6 +538,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	 */
 	expand_inherited_tables(root);
 
+	/* ... and partitioned tables. */
+	expand_partitioned_tables(root);
+
 	/*
 	 * Set hasHavingQual to remember if HAVING clause is present.  Needed
 	 * because preprocess_expression will reduce a constant-true condition to
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index e509a1a..54fae4b 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -33,6 +33,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/sysattr.h"
+#include "catalog/partition.h"
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
@@ -112,6 +113,11 @@ 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 void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *rte,
+					Index rti);
+static void make_var_translation_list(Relation oldrelation,
+							  Index newvarno,
+							  List **translated_vars);
 
 
 /*
@@ -1217,6 +1223,29 @@ expand_inherited_tables(PlannerInfo *root)
 }
 
 /*
+ * expand_partitioned_tables
+ *		Expand each rangetable entry that is partitioned table into an
+ *		"append relation".
+ */
+void
+expand_partitioned_tables(PlannerInfo *root)
+{
+	Index		nrtes;
+	Index		rti;
+	ListCell   *rl;
+
+	nrtes = list_length(root->parse->rtable);
+	rl = list_head(root->parse->rtable);
+	for (rti = 1; rti <= nrtes; rti++)
+	{
+		RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl);
+
+		expand_partitioned_rtentry(root, rte, rti);
+		rl = lnext(rl);
+	}
+}
+
+/*
  * expand_inherited_rtentry
  *		Check whether a rangetable entry represents an inheritance set.
  *		If so, add entries for all the child tables to the query's
@@ -1433,6 +1462,161 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 }
 
 /*
+ * expand_partitioned_rtentry
+ *		Check whether a rangetable entry is a partitioned table.
+ *		If so, add entries for all the leaf partitions to the query's
+ *		rangetable, and build AppendRelInfo nodes for all the leaf partitions
+ *		and add them to root->append_rel_list.
+ *
+ * A childless table is never considered to be an inheritance set; therefore
+ * a parent RTE must always have at least two associated AppendRelInfos.
+ */
+static void
+expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
+{
+	Query	   *parse = root->parse;
+	Oid			parentOID;
+	PlanRowMark *oldrc;
+	Relation	oldrelation;
+	LOCKMODE	lockmode;
+	List	   *partOIDs;
+	List	   *appinfos;
+	ListCell   *l;
+
+	/* Quick exit */
+	if (rte->rtekind != RTE_RELATION || !relid_is_partitioned(rte->relid))
+		return;
+
+	parentOID = rte->relid;
+
+	/*
+	 * Locking policy same as that described in expand_inherited_rtentry().
+	 */
+	oldrc = get_plan_rowmark(root->rowMarks, rti);
+	if (rti == parse->resultRelation)
+		lockmode = RowExclusiveLock;
+	else if (oldrc && RowMarkRequiresRowShareLock(oldrc->markType))
+		lockmode = RowShareLock;
+	else
+		lockmode = AccessShareLock;
+
+	/* Scan for all members of inheritance set, acquire needed locks */
+	partOIDs = get_leaf_partitions(parentOID, lockmode);
+
+	/*
+	 * If parent relation is selected FOR UPDATE/SHARE, we need to mark its
+	 * PlanRowMark as isParent = true, and generate a new PlanRowMark for each
+	 * partition.
+	 */
+	if (oldrc)
+		oldrc->isParent = true;
+
+	/*
+	 * Must open the parent relation to examine its tupdesc.  We need not lock
+	 * it; we assume the rewriter already did.
+	 */
+	oldrelation = heap_open(parentOID, NoLock);
+
+	/* Scan the inheritance set and expand it */
+	appinfos = NIL;
+	foreach(l, partOIDs)
+	{
+		Oid			partOID = lfirst_oid(l);
+		Relation	newrelation;
+		RangeTblEntry *partrte;
+		Index		partRTindex;
+		AppendRelInfo *appinfo;
+
+		newrelation = heap_open(partOID, NoLock);
+
+		/*
+		 * It is possible that the parent table has partitions that are temp
+		 * tables of other backends (yeah it's possible - imagine if other
+		 * backend creates a leaf partition of a temp partitioned table).
+		 * We cannot safely access such partitions (because of buffering issues),
+		 * and the best thing to do seems to be to silently ignore them.
+		 */
+		if (RELATION_IS_OTHER_TEMP(newrelation))
+		{
+			heap_close(newrelation, lockmode);
+			continue;
+		}
+
+		/*
+		 * Build an RTE for the partition, and attach to query's rangetable list.
+		 * We copy most fields of the parent's RTE, but replace relation OID
+		 * and relkind, Also, set requiredPerms to zero since all required
+		 * permissions checks are done on the original RTE.
+		 */
+		partrte = copyObject(rte);
+		partrte->relid = partOID;
+		partrte->relkind = newrelation->rd_rel->relkind;
+		partrte->inh = false;
+		partrte->requiredPerms = 0;
+		parse->rtable = lappend(parse->rtable, partrte);
+		partRTindex = list_length(parse->rtable);
+
+		/*
+		 * Build an AppendRelInfo for this parent and child.
+		 */
+		appinfo = makeNode(AppendRelInfo);
+		appinfo->parent_relid = rti;
+		appinfo->child_relid = partRTindex;
+		appinfo->parent_reltype = oldrelation->rd_rel->reltype;
+		appinfo->child_reltype = newrelation->rd_rel->reltype;
+		make_var_translation_list(oldrelation, partRTindex, &appinfo->translated_vars);
+		appinfo->parent_reloid = parentOID;
+		appinfos = lappend(appinfos, appinfo);
+
+		/*
+		 * Translate the column permissions bitmaps to the partition's attnums (we
+		 * have to build the translated_vars list before we can do this).
+		 *
+		 * 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.
+		 */
+		partrte->selectedCols = translate_col_privs(rte->selectedCols,
+												   appinfo->translated_vars);
+		partrte->insertedCols = translate_col_privs(rte->insertedCols,
+												   appinfo->translated_vars);
+		partrte->updatedCols = translate_col_privs(rte->updatedCols,
+												   appinfo->translated_vars);
+
+		/*
+		 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+		 */
+		if (oldrc)
+		{
+			PlanRowMark *newrc = makeNode(PlanRowMark);
+
+			newrc->rti = partRTindex;
+			newrc->prti = rti;
+			newrc->rowmarkId = oldrc->rowmarkId;
+			/* Reselect rowmark type, because relkind might not match parent */
+			newrc->markType = select_rowmark_type(partrte, oldrc->strength);
+			newrc->allMarkTypes = (1 << newrc->markType);
+			newrc->strength = oldrc->strength;
+			newrc->waitPolicy = oldrc->waitPolicy;
+			newrc->isParent = false;
+
+			/* Include partition's rowmark type in parent's allMarkTypes */
+			oldrc->allMarkTypes |= newrc->allMarkTypes;
+
+			root->rowMarks = lappend(root->rowMarks, newrc);
+		}
+
+		/* Close partition relations, but keep locks */
+			heap_close(newrelation, NoLock);
+	}
+
+	heap_close(oldrelation, NoLock);
+
+	/* Otherwise, OK to add to root->append_rel_list */
+	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
  * make_inh_translation_list
  *	  Build the list of translations from parent Vars to child Vars for
  *	  an inheritance child.
@@ -1536,6 +1720,53 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation,
 }
 
 /*
+ * make_var_translation_list
+ *	  Build the list of translations from parent Vars to partition Vars for
+ *	  a partition which is basically changing varnos to refer to the partition's
+ *	  RT entry.
+ */
+static void
+make_var_translation_list(Relation oldrelation,
+							  Index newvarno,
+							  List **translated_vars)
+{
+	List	   *vars = NIL;
+	TupleDesc	old_tupdesc = RelationGetDescr(oldrelation);
+	int			oldnatts = old_tupdesc->natts;
+	int			old_attno;
+
+	for (old_attno = 0; old_attno < oldnatts; old_attno++)
+	{
+		Form_pg_attribute att;
+		char	   *attname;
+		Oid			atttypid;
+		int32		atttypmod;
+		Oid			attcollation;
+
+		att = old_tupdesc->attrs[old_attno];
+		if (att->attisdropped)
+		{
+			/* Just put NULL into this list entry */
+			vars = lappend(vars, NULL);
+			continue;
+		}
+		attname = NameStr(att->attname);
+		atttypid = att->atttypid;
+		atttypmod = att->atttypmod;
+		attcollation = att->attcollation;
+
+		vars = lappend(vars, makeVar(newvarno,
+									 (AttrNumber) (old_attno + 1),
+									 atttypid,
+									 atttypmod,
+									 attcollation,
+									 0));
+	}
+
+	*translated_vars = vars;
+}
+
+/*
  * translate_col_privs
  *	  Translate a bitmapset representing per-column privileges from the
  *	  parent rel's attribute numbering to the child's.
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 0ea9fcf..4453aae 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -805,6 +805,14 @@ estimate_rel_size(Relation rel, int32 *attr_widths,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+			if (relid_is_internal_partition(RelationGetRelid(rel)))
+			{
+				/* No storage */
+				*pages = 0;
+				*tuples = 0;
+				*allvisfrac = 0;
+				break;
+			}
 		case RELKIND_INDEX:
 		case RELKIND_MATVIEW:
 		case RELKIND_TOASTVALUE:
@@ -929,6 +937,12 @@ estimate_rel_size(Relation rel, int32 *attr_widths,
 			*tuples = rel->rd_rel->reltuples;
 			*allvisfrac = 0;
 			break;
+		case RELKIND_PARTITIONED_REL:
+			/* No storage */
+			*pages = 0;
+			*tuples = 0;
+			*allvisfrac = 0;
+			break;
 		default:
 			/* else it has no disk storage; probably shouldn't get here? */
 			*pages = 0;
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 420692f..394021f 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
+#include "catalog/partition.h"
 #include "catalog/pg_class.h"
 #include "foreign/foreign.h"
 #include "optimizer/clauses.h"
@@ -199,7 +200,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 	 * not in the main join tree, but we will need RelOptInfos to plan access
 	 * to them.
 	 */
-	if (rte->inh)
+	if (rte->inh || (rte->rtekind == RTE_RELATION &&
+							relid_is_partitioned(rte->relid)))
 	{
 		ListCell   *l;
 
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index cebd8b6..611ed49 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -64,4 +64,6 @@ extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node,
 extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 								  RelOptInfo *child_rel);
 
+extern void expand_partitioned_tables(PlannerInfo *root);
+
 #endif   /* PREP_H */
-- 
1.7.1

view thread (161+ messages)  latest in thread

Message-ID: <56C1302D.6070403@lab.ntt.co.jp>
Permalink:  ../56C1302D.6070403@lab.ntt.co.jp/
Also on:    postgresql.org/message-id/56C1302D.6070403@lab.ntt.co.jp

 ·  · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-hackers@postgresql.org
  Cc: Langote_Amit_f8@lab.ntt.co.jp
  Subject: Re: Declarative partitioning
  In-Reply-To: <56C1302D.6070403@lab.ntt.co.jp>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox