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

