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

