>From 3639bf66ff5380a7704588b5e1d3a19dcfb16cd1 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 15:54:17 +0900
Subject: [PATCH 4/6] Infrastructure for partition metadata storage and manipulation.

Catalog pg_partition with:

  partrelid		oid
  partbound		pg_node_tree

Now since partition metadata is stored in a dedicated catalog, it is possible
to use that knowledge to enforce special behaviors for inheritance children
(or relations in general) that are partitions. Some such behaviors include:

 * Cannot drop using DROP TABLE without first "detaching" from its parent
 * Cannot add, drop new columns
 * Cannot rename, change type of existing columns

It also becomes now possible to check, from the FOR VALUES specification, that
the new partition (in CREATE TABLE PARTITION OF parent or ALTER TABLE parent
ATTACH PARTITION) does not overlap with any existing partition of parent.

One can get a partition's check expression (that is, an expression tree that
can be used as a check constraint for the rows in it).  ExecConstraints() now
checks using that expression, whether a row being inserted or new row value in
update case, violate the partition boundary specification.  Also, optimizer's
constraint exclusion uses it remove useless partitions.  This expression tree
is cached in the partition's relcache.

When attaching a partition, rows in the table are checked whether any violates
the partition bound specification (AND'ed with the parent's check expression,
if parent itself is a partition), unless NO VALIDATE clause is specified with
the command.

This commit also invents an internal representation of partition bounds in
the form of structs PartitionListBound and PartitionRangeBound, which consist
of datums and information like number of values in a list, booleans for
whether a range bound is infinite, inclusive (vs. exclusive), lower bound
(vs. upper bound), etc. One can get "partition descriptor" for a partitioned
table, which comes in the form of a struct PartitionDesc.  It consists of
of an array of oids of partitions, array of pointers to PartitionListBound
structs (NULL if not list partitioned, and 2 arrays of pointers to
PartitionRangeBound structs, (each NULL, if not range partitioned).
A pointer to this structure is also cached in relcache.

One more data structure that this commit invents for future use is struct
PartitionDescNode.  It represents a node in partition tree where each node
of the tree corresponds to a partitioned table in the tree.  The tree
structure is such that a parent node links to the first child node (which
corresponds with the first partition of the parent that is partitioned
itself), which in turn, links to its right sibling (the next partition
that is partitioned) and so on.  Each node consists of some book-keeping
information including a pointer to PartitionDesc of the corresponding table
and recursive pointers just mentioned viz. downlink and next.

Support to route tuples inserted into a partitioned parent to the appropriate
leaf partition will be added shortly which leverages PartitionDescNode.
---
 doc/src/sgml/catalogs.sgml                 |   56 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/heap.c                 |    6 +
 src/backend/catalog/partition.c            | 1531 ++++++++++++++++++++++++++++
 src/backend/commands/copy.c                |    2 +-
 src/backend/commands/lockcmds.c            |    4 +-
 src/backend/commands/tablecmds.c           |  210 ++++-
 src/backend/executor/execMain.c            |   75 ++-
 src/backend/executor/nodeModifyTable.c     |    4 +-
 src/backend/nodes/readfuncs.c              |   33 +
 src/backend/optimizer/util/plancat.c       |   13 +
 src/backend/parser/analyze.c               |    1 +
 src/backend/parser/parse_clause.c          |    4 +-
 src/backend/parser/parse_relation.c        |    3 +-
 src/backend/parser/parse_utilcmd.c         |    6 +
 src/backend/utils/adt/ruleutils.c          |   78 ++
 src/backend/utils/cache/relcache.c         |    7 +
 src/backend/utils/cache/syscache.c         |   12 +
 src/bin/pg_dump/common.c                   |   86 ++
 src/bin/pg_dump/pg_dump.c                  |   98 ++-
 src/bin/pg_dump/pg_dump.h                  |   12 +
 src/bin/psql/describe.c                    |   46 +-
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/partition.h            |   57 +
 src/include/catalog/pg_partition.h         |   54 +
 src/include/nodes/execnodes.h              |    4 +
 src/include/utils/rel.h                    |    4 +
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/sanity_check.out |    1 +
 29 files changed, 2377 insertions(+), 36 deletions(-)
 create mode 100644 src/include/catalog/pg_partition.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 652f779..9e7c1d9 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -224,6 +224,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partition"><structname>pg_partition</structname></link></entry>
+      <entry>information about partitions</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
       <entry>information about partitioned tables, including the partition key</entry>
      </row>
@@ -4677,6 +4682,57 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partition">
+  <title><structname>pg_partition</structname></title>
+
+  <indexterm zone="catalog-pg-partition">
+   <primary>pg_partition</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partition</structname> stores partition bounds
+   of tables that are partitions.
+  </para>
+
+  <table>
+   <title><structname>pg_partition</> Columns</title>
+
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+
+     <row>
+      <entry><structfield>partrelid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>The OID of the <structname>pg_class</> entry for this partition</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression tree (in <function>nodeToString()</function> representation)
+       for partition boundary value.  For list partitions, it is simply a list
+       of values.  For range partitions, it consists of lower and upper bound
+       values along with their inclusivity.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-partitioned">
   <title><structname>pg_partitioned</structname></title>
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4dc6300..ee1be31 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
 	pg_foreign_table.h pg_policy.h pg_replication_origin.h \
 	pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
-	pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
+	pg_collation.h pg_range.h pg_transform.h pg_partitioned.h pg_partition.h\
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 932a48b..2e99661 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1808,7 +1808,13 @@ heap_drop_with_catalog(Oid relid)
 	 * If a partitioned table, delete the pg_partitioned tuples.
 	 */
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+	{
 		RemovePartitionKeyByRelId(relid);
+		RelationDropPartitions(relid);
+	}
+
+	if (relid_is_partition(relid))
+		RemovePartitionEntryByRelId(relid);
 
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 5e238dc..46bbb9e 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -18,19 +18,26 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/nbtree.h"
+#include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaddress.h"
 #include "catalog/partition.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/var.h"
 #include "storage/lmgr.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -41,10 +48,88 @@
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
+/*
+ * Information about a single partition
+ */
+typedef struct PartitionInfo
+{
+	Oid						oid;		/* partition OID */
+	PartitionListBound	   *list;		/* list partition bound */
+	PartitionRangeBound	   *rangelower;	/* range partition lower bound */
+	PartitionRangeBound	   *rangeupper;	/* range partition upper bound */
+} PartitionInfo;
+
+typedef struct translate_var_attno_mutator_context
+{
+	AttrNumber	old_attno;
+	AttrNumber	new_attno;
+} translate_var_attno_mutator_context;
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+	int		nparts;		/* number of partitions */
+	Oid	   *oids;		/* OIDs of the partitions */
+	PartitionListBound  **lists;		/* list bounds */
+	PartitionRangeBound **rangelowers;	/* range lower bounds */
+	PartitionRangeBound **rangeuppers;	/* range upper bounds */
+} PartitionDescData;
+
 static PartitionKey CopyPartitionKey(PartitionKey fromkey);
 static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
 								KeyTypeCollInfo *tcinfo);
 static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+static bool list_overlaps_existing_partition(PartitionKey key,
+							PartitionListSpec *list_spec,
+							PartitionDesc pdesc,
+							Oid *with);
+static bool partition_range_empty(PartitionKey key,
+							PartitionRangeSpec *range_spec);
+static int32 compare_range_keys(PartitionKey key, Datum *val1, Datum *val2);
+static bool range_overlaps_existing_partition(PartitionKey key,
+							PartitionRangeSpec *range_spec,
+							PartitionDesc pdesc,
+							Oid *with);
+static PartitionListBound *make_list_from_spec(PartitionKey key, List *values);
+static PartitionRangeBound *make_range_from_spec(PartitionKey key, List *val,
+							bool inclusive, bool lower);
+static bool partition_range_overlaps(PartitionKey key,
+							PartitionRangeBound *part1_lower,
+							PartitionRangeBound *part1_upper,
+							PartitionRangeBound *part2_lower,
+							PartitionRangeBound *part2_upper);
+static int32 partition_range_cmp_bounds(PartitionKey key,
+							PartitionRangeBound *b1,
+							PartitionRangeBound *b2);
+
+static PartitionDesc RelationGetPartitionDesc(Relation relation);
+static PartitionDesc GetPartitionDescCopy(PartitionKey key, PartitionDesc src);
+static PartitionInfo **GetRelPartitions(Relation rel, int *nparts);
+static List *get_partitions(Oid relid, int lockmode);
+static int32 range_partition_cmp_upper(const void *a, const void *b,
+							void *arg);
+static PartitionListBound *copyPartitionListBound(PartitionListBound *src,
+							PartitionKey key);
+static PartitionRangeBound *copyPartitionRangeBound(PartitionRangeBound *src,
+							PartitionKey key);
+static void free_partition_info_array(PartitionInfo **p, int num);
+
+static Expr *get_check_expr_for_list(PartitionKey key, List *values);
+static Expr *get_check_expr_for_range(PartitionKey key,
+						 List *lowervalues,
+						 bool lowerinc,
+						 List *uppervalues,
+						 bool upperinc);
+static Node *translate_var_attno(Node *expr, AttrNumber attno,
+							AttrNumber new_attno);
+static Node *translate_var_attno_mutator(Node *node,
+							translate_var_attno_mutator_context *cxt);
+static Node *generate_partition_check_expr(Relation rel);
+
+static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionDescNode pdnode);
 
 /*
  * StorePartitionKey
@@ -449,3 +534,1449 @@ FreePartitionKey(PartitionKey key)
 	free_key_type_coll_info(key->tcinfo);
 	pfree(key);
 }
+
+/*
+ * StorePartitionBound
+ *
+ * Store partition boundary info in pg_partition after checking for its
+ * validity.
+ */
+void
+StorePartitionBound(Oid relid, Oid parentId, Node *bound)
+{
+	Relation		pg_partition;
+	Relation		parent;
+	PartitionKey	key;
+	PartitionDesc	pdesc;
+	Oid				with;
+	HeapTuple		tuple;
+	char		   *boundString;
+	Datum			values[Natts_pg_partition];
+	bool			nulls[Natts_pg_partition];
+	ParseState	   *pstate = make_parsestate(NULL);
+
+	parent = heap_open(parentId, AccessShareLock);
+	key = parent->rd_partkey;
+	pdesc = RelationGetPartitionDesc(parent);
+
+	/*
+	 * Check if 'bound' happens to overlap with any of parent's existing
+	 * partitions.
+	 */
+	switch (key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+		{
+			PartitionListSpec *list;
+
+			Assert(IsA(bound, PartitionListSpec));
+			list = (PartitionListSpec *) bound;
+			if (list_overlaps_existing_partition(key, list, pdesc, &with))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("new partition's list of values overlaps with that"
+							" of partition \"%s\" of \"%s\"",
+							get_rel_name(with),	get_rel_name(parentId)),
+					 errhint("Please specify a list of values that does not"
+							 " overlap with any existing list partition's list."),
+					 parser_errposition(pstate, list->location)));
+			break;
+		}
+
+		case PARTITION_STRAT_RANGE:
+		{
+			PartitionRangeSpec *range;
+
+			Assert(IsA(bound, PartitionRangeSpec));
+			range = (PartitionRangeSpec *) bound;
+			if (partition_range_empty(key, range))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("new partition's range contains no element"),
+					 errhint("Please specify a non-empty range."),
+					 parser_errposition(pstate, range->location)));
+
+			if (range_overlaps_existing_partition(key, range, pdesc, &with))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("new partition's range overlaps with that of"
+								" partition \"%s\" of \"%s\"",
+								get_rel_name(with),	get_rel_name(parentId)),
+						 errhint("Please specify a range that does not overlap"
+								 " with any existing partition's range."),
+						 parser_errposition(pstate, range->location)));
+			break;
+		}
+	}
+
+	pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);
+
+	boundString = nodeToString(bound);
+
+	/* Build the tuple - bound can never be null */
+	MemSet(nulls, false, sizeof(nulls));
+
+	values[Anum_pg_partition_partrelid - 1] = ObjectIdGetDatum(relid);
+	values[Anum_pg_partition_partbound - 1] = CStringGetTextDatum(boundString);
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partition), values, nulls);
+
+	simple_heap_insert(pg_partition, tuple);
+
+	/* Update the indexes on pg_partition */
+	CatalogUpdateIndexes(pg_partition, tuple);
+
+	/*
+	 * Invalid the parent's relcache so that the partition is now included in
+	 * its partition descriptor.
+	 */
+	CacheInvalidateRelcache(parent);
+
+	heap_close(parent, AccessShareLock);
+	heap_close(pg_partition, RowExclusiveLock);
+}
+
+/*
+ * Does new list partition 'bound' overlap any of existing list partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+								 PartitionListSpec *list_spec,
+								 PartitionDesc pdesc,
+								 Oid *with)
+{
+	PartitionListBound *new_list;
+	int			i;
+
+	if (pdesc->nparts == 0)
+		return false;
+
+	new_list = make_list_from_spec(key, list_spec->values);
+
+	for (i = 0; i < pdesc->nparts; i++)
+	{
+		int		j;
+
+		if (pdesc->lists[i]->contains_null && new_list->contains_null)
+			return true;
+
+		for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+		{
+			int		k;
+
+			for (k = 0; k < new_list->nvalues; k++)
+			{
+				int32	result;
+
+				result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+										  key->tcinfo->typcoll[0],
+										  pdesc->lists[i]->values[j],
+										  new_list->values[k]));
+				if (!result)
+				{
+					*with = pdesc->oids[i];
+					return true;
+				}
+			}
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Is new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+	PartitionRangeBound *lower,
+						*upper;
+
+	lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+								 true);
+	upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+								 false);
+
+	/*
+	 * Only either lower or upper is allowed to be unbounded, which if
+	 * one of them is, the range is not empty.
+	 */
+	if (lower->infinite || upper->infinite)
+		return false;
+
+	/*
+	 * If upper > lower, ouright empty or if lower = upper and either of
+	 * them is exclusive.
+	 */
+	if (compare_range_keys(key, upper->val, lower->val) < 0 ||
+		(compare_range_keys(key, lower->val, upper->val) == 0 &&
+		 (!lower->inclusive || !upper->inclusive)))
+		return true;
+
+	return false;
+}
+
+/* Compare range composite keys using logic similar to RowCompareExpr */
+static int32
+compare_range_keys(PartitionKey key, Datum *val1, Datum *val2)
+{
+	int32	result;
+	int		i;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+											 key->tcinfo->typcoll[i],
+											 val1[i], val2[i]));
+		if (!result)
+			continue;
+		break;
+	}
+
+	return result;
+}
+
+/*
+ * Does new range partition's range overlap any of existing range partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+								  PartitionRangeSpec *range_spec,
+								  PartitionDesc pdesc,
+								  Oid *with)
+{
+	int		i;
+	PartitionRangeBound *new_lower,
+						*new_upper;
+
+	if (pdesc->nparts == 0)
+		return false;
+
+	/*
+	 * Create internal representation of rangelower and rangeupper from
+	 * range_spec.
+	 */
+	new_lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+									 true);
+	new_upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+									 false);
+
+	/* Check with existing partitions for overlap */
+	for (i = 0; i < pdesc->nparts; i++)
+	{
+		if (partition_range_overlaps(key, new_lower, new_upper,
+									 pdesc->rangelowers[i],
+									 pdesc->rangeuppers[i]))
+		{
+			*with = pdesc->oids[i];
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Make a PartitionListBound from list of values using the information in key
+ */
+static PartitionListBound *
+make_list_from_spec(PartitionKey key, List *values)
+{
+	PartitionListBound *list;
+	ListCell   *cell;
+	int			i;
+
+	list = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+	list->nvalues = list_length(values);
+	list->values = (Datum *) palloc(list->nvalues * sizeof(Datum));
+
+	i = 0;
+	foreach (cell, values)
+	{
+		Const	*val = lfirst(cell);
+
+		if (val->constisnull)
+			list->contains_null = true;
+		else
+			list->values[i] = datumCopy(val->constvalue,
+										key->tcinfo->typbyval[0],
+										key->tcinfo->typlen[0]);
+		i++;
+	}
+
+	return list;
+}
+
+/*
+ * Make a PartitionRangeBound from arguments using the information in key
+ */
+static PartitionRangeBound *
+make_range_from_spec(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+	PartitionRangeBound *range;
+	ListCell *cell;
+
+	range = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	range->infinite = (val == NIL);
+	range->inclusive = inclusive;
+	range->lower = lower;
+
+	if (val)
+	{
+		int		i;
+
+		range->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+
+		i = 0;
+		foreach (cell, val)
+		{
+			Const *val = lfirst(cell);
+
+			if (val->constisnull)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot specify NULL in range bound")));
+			else
+				range->val[i] = datumCopy(val->constvalue,
+										  key->tcinfo->typbyval[i],
+										  key->tcinfo->typlen[i]);
+			i++;
+		}
+	}
+
+	return range;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key,
+			   PartitionRangeBound *part1_lower,
+			   PartitionRangeBound *part1_upper,
+			   PartitionRangeBound *part2_lower,
+			   PartitionRangeBound *part2_upper)
+{
+	if (partition_range_cmp_bounds(key, part1_lower, part2_lower) >= 0 &&
+		partition_range_cmp_bounds(key, part1_lower, part2_upper) <= 0)
+		return true;
+
+	if (partition_range_cmp_bounds(key, part2_lower, part1_lower) >= 0 &&
+		partition_range_cmp_bounds(key, part2_lower, part1_upper) <= 0)
+		return true;
+
+	return false;
+}
+
+/* Returns for two range partition bounds whether, b1 <=, =, >= b2 */
+static int32
+partition_range_cmp_bounds(PartitionKey key,
+						   PartitionRangeBound *b1,
+						   PartitionRangeBound *b2)
+{
+	int32		result;
+
+	/*
+	 * First, handle cases involving infinity, which don't require invoking
+	 * the comparison proc.
+	 */
+	if (b1->infinite && b2->infinite)
+	{
+		/*
+		 * Both are infinity, so they are equal unless one is lower and the
+		 * other not.
+		 */
+		if (b1->lower == b2->lower)
+			return 0;
+		else
+			return b1->lower ? -1 : 1;
+	}
+	else if (b1->infinite)
+		return b1->lower ? -1 : 1;
+	else if (b2->infinite)
+		return b2->lower ? 1 : -1;
+
+	/*
+	 * Both boundaries are finite, so compare the held values.
+	 */
+	result = compare_range_keys(key, b1->val, b2->val);
+
+	/*
+	 * If the comparison is anything other than equal, we're done. If they
+	 * compare equal though, we still have to consider whether the boundaries
+	 * are inclusive or exclusive.
+	 */
+	if (result == 0)
+	{
+		if (!b1->inclusive && !b2->inclusive)
+		{
+			/* both are exclusive */
+			if (b1->lower == b2->lower)
+				return 0;
+			else
+				return b1->lower ? 1 : -1;
+		}
+		else if (!b1->inclusive)
+			return b1->lower ? 1 : -1;
+		else if (!b2->inclusive)
+			return b2->lower ? -1 : 1;
+		else
+		{
+			/*
+			 * Both are inclusive and the values held are equal, so they are
+			 * equal regardless of whether they are upper or lower boundaries,
+			 * or a mix.
+			 */
+			return 0;
+		}
+	}
+
+	return result;
+}
+
+/*
+ *  RemovePartitionByRelId
+ *		Remove a pg_partition entry for a partition.
+ */
+void
+RemovePartitionEntryByRelId(Oid relid)
+{
+	Relation		rel;
+	HeapTuple		tuple;
+
+	rel = heap_open(PartitionRelationId, RowExclusiveLock);
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition entry of relation %u",
+					 relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	/* Update the indexes on pg_partitioned_rel */
+	CatalogUpdateIndexes(rel, tuple);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * does relid have pg_partition entry?
+ */
+bool
+relid_is_partition(Oid relid)
+{
+	return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * Returns inheritance parent of relid by scanning pg_inherits
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+
+	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+	ScanKeyInit(&key[1],
+				Anum_pg_inherits_inhseqno,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(1));
+
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+							  NULL, 2, key);
+
+	tuple = systable_getnext(scan);
+	Assert(HeapTupleIsValid(tuple));
+
+	form = (Form_pg_inherits) GETSTRUCT(tuple);
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return form->inhparent;
+}
+
+/*
+ * 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
+ *
+ * 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.)
+ *
+ * Never returns NULL.  If there are no partitions yet, returns the struct
+ * with nparts set to 0.
+ */
+static PartitionDesc
+RelationGetPartitionDesc(Relation relation)
+{
+	int				i,
+					nparts;
+	PartitionKey	key = relation->rd_partkey;
+	MemoryContext	oldcxt;
+	PartitionDesc	result;
+	PartitionInfo **parts;
+
+	/* Quick copy and exit if already computed the descriptor */
+	if (relation->rd_partdesc != NULL)
+		return GetPartitionDescCopy(key, relation->rd_partdesc);
+
+	result = (PartitionDesc) palloc0(sizeof(PartitionDescData));
+
+	parts = GetRelPartitions(relation, &nparts);
+	result->nparts = nparts;
+
+	if (parts)
+	{
+		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+		switch (key->strategy)
+		{
+			case PARTITION_STRAT_LIST:
+				result->lists = (PartitionListBound **) palloc0(nparts *
+												sizeof(PartitionListBound *));
+				break;
+
+			case PARTITION_STRAT_RANGE:
+				result->rangelowers = (PartitionRangeBound **) palloc0(nparts *
+												sizeof(PartitionRangeBound *));
+				result->rangeuppers = (PartitionRangeBound **) palloc0(nparts *
+												sizeof(PartitionRangeBound *));
+
+				/* Sort on range->upper */
+				qsort_arg(parts, nparts, sizeof(PartitionInfo *),
+						  range_partition_cmp_upper, key);
+				break;
+		}
+
+		for (i = 0; i < nparts; i++)
+		{
+			result->oids[i] = parts[i]->oid;
+
+			if (parts[i]->list)
+				result->lists[i] = copyPartitionListBound(parts[i]->list, key);
+			else
+			{
+				result->rangelowers[i] = copyPartitionRangeBound(parts[i]->rangelower, key);
+				result->rangeuppers[i] = copyPartitionRangeBound(parts[i]->rangeupper, key);
+			}
+		}
+
+		/* Clean up */
+		free_partition_info_array(parts, nparts);
+	}
+
+	/* 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 = GetPartitionDescCopy(key, result);
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+}
+
+/*
+ * GetPartitionDescCopy
+ *		Copy PartitionDesc of a partitioned relation into caller's context
+ *
+ * Like its caller, never returns NULL.
+ */
+static PartitionDesc
+GetPartitionDescCopy(PartitionKey key, PartitionDesc src)
+{
+	PartitionDesc	result;
+	int				i;
+
+	result = (PartitionDesc) palloc0(sizeof(PartitionDescData));
+	result->nparts = src->nparts;
+
+	/* If there are partitions, actually copy partitions */
+	if (src->nparts > 0)
+	{
+		result->oids = (Oid *) palloc0(result->nparts * sizeof(Oid));
+
+		switch (key->strategy)
+		{
+			case PARTITION_STRAT_LIST:
+				result->lists = (PartitionListBound **) palloc0(src->nparts *
+												sizeof(PartitionListBound *));
+				break;
+
+			case PARTITION_STRAT_RANGE:
+				result->rangelowers = (PartitionRangeBound **) palloc0(src->nparts *
+												sizeof(PartitionRangeBound *));
+				result->rangeuppers = (PartitionRangeBound **) palloc0(src->nparts *
+												sizeof(PartitionRangeBound *));
+				break;
+		}
+
+		for (i = 0; i < src->nparts; i++)
+		{
+			result->oids[i] = src->oids[i];
+
+			switch (key->strategy)
+			{
+				case PARTITION_STRAT_LIST:
+					result->lists[i] = copyPartitionListBound(src->lists[i], key);
+					break;
+
+				case PARTITION_STRAT_RANGE:
+					result->rangelowers[i] = copyPartitionRangeBound(src->rangelowers[i], key);
+					result->rangeuppers[i] = copyPartitionRangeBound(src->rangeuppers[i], key);
+					break;
+			}
+		}
+	}
+
+	return result;
+}
+
+/*
+ * GetRelPartitions
+ *		Return information of partitions of rel
+ *
+ * Returns number of partitions in *nparts.
+ */
+static PartitionInfo **
+GetRelPartitions(Relation rel, int *nparts)
+{
+	int				i;
+	Relation		partrel;
+	List		   *partoids;
+	ListCell	   *cell;
+	PartitionInfo **parts;
+	PartitionKey	key = rel->rd_partkey;
+
+	partoids = get_partitions(RelationGetRelid(rel), NoLock);
+
+	/* no partitions found */
+	if ((*nparts = list_length(partoids)) == 0)
+		return NULL;
+
+	partrel = heap_open(PartitionRelationId, AccessShareLock);
+
+	parts = (PartitionInfo **) palloc0(*nparts * sizeof(PartitionInfo *));
+
+	i = 0;
+	foreach(cell, partoids)
+	{
+		Oid 		partrelid = lfirst_oid(cell);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *bound;
+
+		tuple = SearchSysCache1(PARTRELID, partrelid);
+		Assert(HeapTupleIsValid(tuple));
+
+		parts[i] = (PartitionInfo *) palloc0(sizeof(PartitionInfo));
+		parts[i]->oid = partrelid;
+
+		datum = fastgetattr(tuple,
+							Anum_pg_partition_partbound,
+							partrel->rd_att,
+							&isnull);
+
+		bound = stringToNode(TextDatumGetCString(datum));
+
+		switch (key->strategy)
+		{
+			case PARTITION_STRAT_LIST:
+			{
+				PartitionListSpec  *list_spec;
+
+				Assert(IsA(bound, PartitionListSpec));
+				list_spec = (PartitionListSpec *) bound;
+				parts[i]->list = make_list_from_spec(key, list_spec->values);
+			}
+			break;
+
+			case PARTITION_STRAT_RANGE:
+			{
+				PartitionRangeSpec *range_spec;
+
+				Assert(IsA(bound, PartitionRangeSpec));
+				range_spec = (PartitionRangeSpec *) bound;
+
+				parts[i]->rangelower = make_range_from_spec(key,
+														range_spec->lower,
+														range_spec->lowerinc,
+														true);
+				parts[i]->rangeupper = make_range_from_spec(key,
+														range_spec->upper,
+														range_spec->upperinc,
+														false);
+			}
+			break;
+		}
+
+		ReleaseSysCache(tuple);
+		i++;
+	}
+
+	heap_close(partrel, AccessShareLock);
+
+	return parts;
+}
+
+/*
+ * Wrapper around find_inheritance_children for use by partitioning
+ * related code
+ */
+static List *
+get_partitions(Oid relid, int lockmode)
+{
+	return find_inheritance_children(relid, lockmode);
+}
+
+/*
+ * range_partition_cmp_upper
+ *		Compare two range partition PartitionInfos using their rangeupper
+ *		bounds
+ *
+ * Used as a callback to qsort_arg for sorting range partitions.
+ */
+static int32
+range_partition_cmp_upper(const void *a, const void *b, void *arg)
+{
+	PartitionRangeBound *b1 = (*(const PartitionInfo **) a)->rangeupper,
+						*b2 = (*(const PartitionInfo **) b)->rangeupper;
+
+	if (b1->infinite || b2->infinite)
+		return b1->infinite ? 1 : -1;
+
+	return compare_range_keys((PartitionKey) arg, b1->val, b2->val);
+}
+
+/*
+ * Helper routine to copy a list partition bound struct
+ */
+static PartitionListBound *
+copyPartitionListBound(PartitionListBound *src, PartitionKey key)
+{
+	int				i;
+	PartitionListBound  *result;
+
+	result = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+	result->nvalues = src->nvalues;
+	result->contains_null = src->contains_null;
+	result->values = (Datum *) palloc0(src->nvalues * sizeof(Datum));
+
+	for (i = 0; i < src->nvalues; i++)
+		result->values[i] = datumCopy(src->values[i],
+									  key->tcinfo->typbyval[0],
+									  key->tcinfo->typlen[0]);
+	return result;
+}
+
+/*
+ * Helper routine to copy a range partition bound struct
+ */
+static PartitionRangeBound *
+copyPartitionRangeBound(PartitionRangeBound *src, PartitionKey key)
+{
+	int				i;
+	PartitionRangeBound  *result;
+
+	result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	result->infinite = src->infinite;
+	result->inclusive = src->inclusive;
+	result->lower = src->lower;
+
+	if (src->val)
+	{
+		result->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+		for (i = 0; i < key->partnatts; i++)
+			result->val[i] = datumCopy(src->val[i],
+										 key->tcinfo->typbyval[i],
+										 key->tcinfo->typlen[i]);
+	}
+
+	return result;
+}
+
+/*
+ * Helper routine to deep-free a PartitionInfo array
+ */
+static void
+free_partition_info_array(PartitionInfo **p, int num)
+{
+	int	i;
+
+	for (i = 0; i < num; i++)
+	{
+		if (p[i]->list)
+		{
+			pfree(p[i]->list->values);
+			pfree(p[i]->list);
+		}
+
+		if (p[i]->rangelower)
+		{
+			if (p[i]->rangelower->val)
+				pfree(p[i]->rangelower->val);
+			pfree(p[i]->rangelower);
+		}
+
+		if (p[i]->rangeupper)
+		{
+			if (p[i]->rangeupper->val)
+				pfree(p[i]->rangeupper->val);
+			pfree(p[i]->rangeupper);
+		}
+
+		pfree(p[i]);
+	}
+
+	pfree(p);
+}
+
+/*
+ * get_check_expr_from_partbound - for direct use by tablecmds.c
+ */
+Expr *
+get_check_expr_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+	PartitionKey key = parent->rd_partkey;
+	Expr   *my_expr;
+	int		i;
+	ListCell *partexprs_item;
+
+	if (IsA(bound, PartitionListSpec))
+	{
+		PartitionListSpec *list_spec = (PartitionListSpec *) bound;
+
+		my_expr = get_check_expr_for_list(key, list_spec->values);
+	}
+	else if (IsA(bound, PartitionRangeSpec))
+	{
+		PartitionRangeSpec *range_spec = (PartitionRangeSpec *) bound;
+
+		my_expr = get_check_expr_for_range(key,
+										   range_spec->lower,
+										   range_spec->lowerinc,
+										   range_spec->upper,
+										   range_spec->upperinc);
+	}
+
+	/*
+	 * Translate vars in the generated expression with the correct attnos.
+	 * Note that the vars in my_expr bear attnos dictated by key which carries
+	 * attnos of the parent.
+	 */
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber	attno = key->partattrs[i],
+					new_attno;
+		char	   *attname;
+
+		if (attno != 0)
+		{
+			attname = get_attname(RelationGetRelid(parent), attno);
+			new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+			my_expr = (Expr *) translate_var_attno((Node *) my_expr,
+												   attno,
+												   new_attno);
+		}
+		else
+		{
+			Node *expr = (Node *) lfirst(partexprs_item);
+			Bitmapset  *expr_attrs = NULL;
+			int			index;
+
+			pull_varattnos(expr, 1, &expr_attrs);
+			partexprs_item = lnext(partexprs_item);
+
+			index = -1;
+			while ((index = bms_next_member(expr_attrs, index)) > 0)
+			{
+				AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+				attname = get_attname(RelationGetRelid(parent), attno);
+				new_attno = get_attnum(RelationGetRelid(rel), attname);
+				my_expr = (Expr *) translate_var_attno((Node *) my_expr,
+													   attno,
+													   new_attno);
+			}
+		}
+	}
+
+	return my_expr;
+}
+
+/*
+ * get_check_expr_for_list
+ */
+static Expr *
+get_check_expr_for_list(PartitionKey key, List *values)
+{
+	ArrayExpr		   *values_arr = makeNode(ArrayExpr);
+	ScalarArrayOpExpr  *arrayop_expr;
+	Node   *key_col;
+	Oid		operoid;
+	StrategyNumber strategy = BTEqualStrategyNumber;
+
+	/* Btree equality operator of specified opfamily */
+	operoid = get_opfamily_member(key->partopfamily[0],
+								  key->tcinfo->typid[0],
+								  key->tcinfo->typid[0],
+								  strategy);
+
+	/* lexpr */
+	if (key->partattrs[0] != 0)
+		key_col = (Node *) makeVar(1, key->partattrs[0],
+									key->tcinfo->typid[0],
+									key->tcinfo->typmod[0],
+									key->tcinfo->typcoll[0],
+									0);
+	else
+		key_col = (Node *) linitial(key->partexprs);
+
+	/* rexpr */
+	values_arr->array_typeid = get_array_type(key->tcinfo->typid[0]);
+	values_arr->array_collid = key->tcinfo->typcoll[0];
+	values_arr->element_typeid = key->tcinfo->typid[0];
+	values_arr->elements = values;
+	values_arr->multidims = false;
+	values_arr->location = -1;
+
+	/* build the actual predicate expression node */
+	arrayop_expr = makeNode(ScalarArrayOpExpr);
+	arrayop_expr->opno = operoid;
+	arrayop_expr->opfuncid = get_opcode(operoid);
+	arrayop_expr->useOr = true;
+	arrayop_expr->inputcollid = key->tcinfo->typcoll[0];
+	arrayop_expr->args = list_make2(key_col, values_arr);
+	arrayop_expr->location = -1;
+
+	return (Expr *) arrayop_expr;
+}
+
+/*
+ * get_check_expr_for_range
+ */
+static Expr *
+get_check_expr_for_range(PartitionKey key,
+						 List *lowervalues,
+						 bool lowerinc,
+						 List *uppervalues,
+						 bool upperinc)
+{
+	List	   *and_args = NIL;
+	ListCell   *cell1,
+			   *cell2,
+			   *partexprs_item;
+	int			i;
+	uint16		strategy;
+
+	/*
+	 * Handle the case with only one bound.
+	 *
+	 * In this case, consider only the first column of the key and generate
+	 * appropriate opclause since only the comparison with the first column
+	 * would have determined whether a rows went into such a partition.
+	 * It's always true that -INF < someval and someval < +INF.
+	 */
+	if (lowervalues == NIL || uppervalues == NIL)
+	{
+		List   *values;
+		Const  *key_val;
+		Node   *key_col;
+		Oid		operoid;
+		bool	islower,
+				inclusive;
+
+		if (lowervalues != NIL)
+		{
+			values = lowervalues;
+			islower = true;
+			inclusive = lowerinc;
+		}
+		else
+		{
+			values = uppervalues;
+			islower = false;
+			inclusive = upperinc;
+		}
+
+		/* lexpr */
+		if (key->partattrs[0] != 0)
+			key_col = (Node *) makeVar(1, key->partattrs[0],
+									  key->tcinfo->typid[0],
+									  key->tcinfo->typmod[0],
+									  key->tcinfo->typcoll[0],
+									  0);
+		else
+			key_col = (Node *) linitial(key->partexprs);
+
+		/* rexpr */
+		key_val = linitial(values);
+
+		if (islower)
+			strategy = inclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber;
+		else
+			strategy = inclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber;
+
+		operoid = get_opfamily_member(key->partopfamily[0],
+									  key->tcinfo->typid[0],
+									  key->tcinfo->typid[0],
+									  strategy);
+
+		and_args = lappend(and_args,
+							   make_opclause(operoid, BOOLOID,
+											 false,
+											 (Expr *) key_col,
+											 (Expr *) key_val,
+											 InvalidOid,
+											 key->tcinfo->typcoll[0]));
+
+		return makeBoolExpr(AND_EXPR, and_args, -1);
+	}
+
+	/* Both bounds constrain values going into the partition. */
+	i = 0;
+	partexprs_item = list_head(key->partexprs);
+	forboth (cell1, lowervalues, cell2, uppervalues)
+	{
+		Node   *key_col;
+		Const  *lower_val = lfirst(cell1);
+		Const  *upper_val = lfirst(cell2);
+		Expr   *lower_clause,
+			   *upper_clause;
+		Oid 	eqop,
+				lowerop,
+				upperop;
+		EState		   *estate;
+		MemoryContext	oldcxt;
+		Expr		   *test_expr;
+		ExprState	   *test_exprstate;
+		Datum			test_result;
+		bool 			isNull;
+
+		/* lexpr */
+		if (key->partattrs[i] != 0)
+		{
+			key_col = (Node *) makeVar(1, key->partattrs[i],
+									  key->tcinfo->typid[i],
+									  key->tcinfo->typmod[i],
+									  key->tcinfo->typcoll[i],
+									  0);
+		}
+		else
+		{
+			key_col = (Node *) lfirst(partexprs_item);
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		/*
+		 * If lower_val = upper_val (= val, say), create a column = val
+		 * constraint.
+		 */
+		eqop = get_opfamily_member(key->partopfamily[i],
+								   key->tcinfo->typid[i],
+								   key->tcinfo->typid[i],
+								   BTEqualStrategyNumber);
+
+		estate = CreateExecutorState();
+		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+		test_expr = make_opclause(eqop,
+								  BOOLOID,
+								  false,
+								  (Expr *) lower_val,
+								  (Expr *) upper_val,
+								  InvalidOid,
+								  key->tcinfo->typcoll[i]);
+		fix_opfuncids((Node *) test_expr);
+		test_exprstate = ExecInitExpr(test_expr, NULL);
+		test_result = ExecEvalExprSwitchContext(test_exprstate,
+												GetPerTupleExprContext(estate),
+												&isNull, NULL);
+		MemoryContextSwitchTo(oldcxt);
+		FreeExecutorState(estate);
+
+		if (DatumGetBool(test_result))
+		{
+			and_args = lappend(and_args,
+								make_opclause(eqop,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  /* could've been upper_val */
+									  (Expr *) lower_val,
+									  InvalidOid,
+									  key->tcinfo->typcoll[i]));
+
+			/* Go to the next column. */
+		}
+		else
+		{
+			/*
+			 * For all columns but last, use inclusive strategy (<= or >=).
+			 *
+			 * For the last column, the bound's inclusivity determines.
+			 */
+			strategy = i < key->partnatts - 1 || lowerinc
+									? BTGreaterEqualStrategyNumber
+									: BTGreaterStrategyNumber;
+			lowerop = get_opfamily_member(key->partopfamily[i],
+										  key->tcinfo->typid[i],
+										  key->tcinfo->typid[i],
+										  strategy);
+			strategy = i < key->partnatts - 1 || upperinc
+									? BTLessEqualStrategyNumber
+									: BTLessStrategyNumber;
+			upperop = get_opfamily_member(key->partopfamily[i],
+										  key->tcinfo->typid[i],
+										  key->tcinfo->typid[i],
+										  strategy);
+
+			lower_clause = make_opclause(lowerop,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) lower_val,
+									  InvalidOid,
+									  key->tcinfo->typcoll[i]);
+			upper_clause = make_opclause(upperop,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->tcinfo->typcoll[i]);
+
+			and_args = lappend(and_args,
+							   makeBoolExpr(AND_EXPR,
+								list_make2(lower_clause, upper_clause), -1));
+
+			/* No need to constraint further columns. */
+			break;
+		}
+
+		i++;
+	}
+
+	return makeBoolExpr(AND_EXPR, and_args, -1);
+}
+
+/*
+ * translate_var_attno
+ *		Changes Vars with a given attno in the provided expression tree to
+ *		Vars with new_attno
+ */
+static Node *
+translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno)
+{
+	translate_var_attno_mutator_context cxt;
+
+	cxt.old_attno = attno;
+	cxt.new_attno = new_attno;
+
+	return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt);
+}
+
+/*
+ * translate_var_attno_mutator
+ */
+static Node *
+translate_var_attno_mutator(Node *node,
+							 translate_var_attno_mutator_context *cxt)
+{
+	if (node == NULL)
+		return NULL;
+
+	if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno)
+	{
+		Var		*newvar = copyObject(node);
+
+		newvar->varattno = cxt->new_attno;
+
+		return (Node *) newvar;
+	}
+
+	return expression_tree_mutator(node, translate_var_attno_mutator,
+								  (void *) cxt);
+}
+
+/*
+ * RelationGetPartitionConstraint
+ *		Returns the equivelent CHECK constraint expression for a partition
+ */
+Node *
+RelationGetPartitionCheckExpr(Relation rel)
+{
+	/* Quick exit */
+	if (!relid_is_partition(RelationGetRelid(rel)))
+		return NULL;
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+		return copyObject(rel->rd_partcheck);
+
+	/* None of those, generate */
+	return generate_partition_check_expr(rel);
+}
+
+/*
+ * generate_partition_constraints
+ *		Generate partition check constraint expression rel's pg_partition
+ *		entry
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * routine fails partway through, we build in working memory and then copy
+ * the completed structure into cache memory.
+ */
+static Node *
+generate_partition_check_expr(Relation rel)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	Node	   *my_expr = NULL,
+			   *result = NULL;
+	Oid			relid = RelationGetRelid(rel);
+	Relation	parent = heap_open(get_partition_parent(relid), NoLock);
+
+	/* quick copy */
+	if (rel->rd_partcheck)
+	{
+		heap_close(parent, NoLock);
+		return copyObject(rel->rd_partcheck);
+	}
+
+	tuple = SearchSysCache1(PARTRELID, relid);
+	Assert(HeapTupleIsValid(tuple));
+	boundDatum = SysCacheGetAttr(PARTRELID, tuple,
+								 Anum_pg_partition_partbound,
+								 &isnull);
+	Assert(!isnull);
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+
+	my_expr = (Node *) get_check_expr_from_partbound(rel, parent, bound);
+
+	/*
+	 * If parent is also a partition, create a AND_EXPR BoolExpr with just
+	 * created expression and parent's check expression, as its args.
+	 */
+	if (relid_is_partition(RelationGetRelid(parent)))
+	{
+		Node	   *parent_expr;
+
+		parent_expr = generate_partition_check_expr(parent);
+		result = (Node *) makeBoolExpr(AND_EXPR,
+									   list_make2(parent_expr, my_expr), -1);
+	}
+	else
+		result = my_expr;
+
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	rel->rd_partcheck = copyObject(result);
+	MemoryContextSwitchTo(oldcxt);
+
+	ReleaseSysCache(tuple);
+	heap_close(parent, NoLock);
+
+	return result;
+}
+
+/*
+ * RelationGetPartitionDescNode
+ *		Recursively form partition node tree rooted at rel's node
+ */
+PartitionDescNode
+RelationGetPartitionDescNode(Relation rel)
+{
+	PartitionDescNode	root;
+
+	/*
+	 * Recurse to build the tree of partition descriptor nodes with
+	 * rel's descriptor as the root of the tree.
+	 */
+	root = GetPartitionDescNodeRecurse(rel, 0);
+	root->index = 0;
+	root->num_leaf_partitions = get_leaf_partition_count(root);
+
+	return root;
+}
+
+/*
+ * GetPartitionDescNodeRecurse
+ *		Workhorse of RelationGetPartitionDescNode
+ *
+ * offset is the index (0-based) of the first leaf node in the tree, as
+ * as determined and passed by the parent or the left sibling.
+ */
+static PartitionDescNode
+GetPartitionDescNodeRecurse(Relation rel, int offset)
+{
+	PartitionDescNode	root,
+						prev;
+	PartitionDesc		pdesc = RelationGetPartitionDesc(rel);
+	int					i;
+
+	/* First build our own node */
+	root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+	root->pdesc = pdesc;
+	root->relid = RelationGetRelid(rel);
+	root->offset = offset;
+	root->downlink = NULL;
+	root->next = NULL;
+
+	/*
+	 * Recursively add nodes for partitions that are themselves partitioned,
+	 * linking ourselves to the first such node via 'downlink'.  Each node
+	 * thus created (being on the same level) is linked to the right sibling
+	 * via 'next'.  For each node, determine the offset to pass and once
+	 * created, update its index and num_leaf_partitions.
+	 */
+	prev = NULL;
+	for (i = 0; i < pdesc->nparts; i++)
+	{
+		Oid			relid = pdesc->oids[i];
+		int			offset;
+		Relation	rel;
+		PartitionDescNode pdnode;
+
+		if (!relid_is_partitioned(relid))
+			continue;
+
+		rel = heap_open(relid, AccessShareLock);
+
+		/* Offset for the new node */
+		if (prev)
+			/*
+			 * Count leaves on the left - prev node has the account of those
+			 * on its own left and then there may have been some siblings
+			 * in-between that were leaves themselves.
+			 */
+			offset = prev->offset + prev->num_leaf_partitions +
+												(i - prev->index - 1);
+		else
+			/*
+			 * We're the first child - account of leaf nodes comes from the
+			 * parent.  Left siblings were all leaves themselves.
+			 */
+			offset = root->offset + i;
+
+		pdnode = GetPartitionDescNodeRecurse(rel, offset);
+		pdnode->index = i;
+		pdnode->num_leaf_partitions = get_leaf_partition_count(pdnode);
+
+		heap_close(rel, AccessShareLock);
+
+		/* Found our first child; link to it. */
+		if (root->downlink == NULL)
+			root->downlink = pdnode;
+
+		/* Link new node to the left sibling, if any  */
+		if (prev)
+			prev->next = pdnode;
+
+		prev = pdnode;
+	}
+
+	return root;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively count the number of leaf partitions in the partition
+ *		tree rooted at pdnode
+ */
+static int
+get_leaf_partition_count(PartitionDescNode pdnode)
+{
+	int		i;
+	int 	result = 0;
+	PartitionDescNode node = pdnode->downlink;
+
+	for (i = 0; i < pdnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are of leaf partitions */
+		if (node && i == node->index)
+		{
+			result += get_leaf_partition_count(node);
+			node = node->next;
+		}
+		else
+			result += 1;
+	}
+
+	return result;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively compute the list of OIDs of leaf partitions in the
+ *		partition tree rooted at pdnode
+ */
+List *
+get_leaf_partition_oids(PartitionDescNode pdnode)
+{
+	int		i;
+	List   *result = NIL;
+	Oid	   *myparts = pdnode->pdesc->oids;
+	int		n_myparts = pdnode->pdesc->nparts;
+	PartitionDescNode node = pdnode->downlink;
+
+	for (i = 0; i < n_myparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are leaf partitions */
+		if (node && i == node->index)
+		{
+			result = list_concat(result, get_leaf_partition_oids(node));
+			node = node->next;
+		}
+		else
+			result = lappend_oid(result, myparts[i]);
+	}
+
+	return result;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 403738d..14e81de 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2464,7 +2464,7 @@ CopyFrom(CopyState cstate)
 		if (!skip_tuple)
 		{
 			/* Check the constraints of the tuple */
-			if (cstate->rel->rd_att->constr)
+			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
 			if (useHeapMultiInsert)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62671a8..5120c93 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -16,6 +16,7 @@
 
 #include "access/heapam.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_inherits_fn.h"
 #include "commands/lockcmds.h"
 #include "miscadmin.h"
@@ -63,7 +64,8 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (recurse)
+		/* Force inheritance recursion, if partitioned table. */
+		if (recurse || relid_is_partitioned(reloid))
 			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
 	}
 }
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3af134b..c894d11 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -121,6 +121,19 @@ static List *on_commits = NIL;
 
 
 /*
+ * For AT ATTACH PARTITION <table> FOR VALUES <partition_bound_spec>,
+ *
+ * Struct describing partition check expression corresponding to
+ * <partition_bound_spec> to validate for rows in <table> during
+ * Phase 3 scan.
+ */
+typedef struct PartitionCheck
+{
+	Expr	*expr;
+	List	*state;
+} PartitionCheck;
+
+/*
  * State information for ALTER TABLE
  *
  * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
@@ -163,6 +176,8 @@ typedef struct AlteredTableInfo
 	Oid			newTableSpace;	/* new tablespace; 0 means no change */
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;		/* if above is true */
+	PartitionCheck *partitionCheck; /* partition check expression for
+									 * ATTACH PARTITION validation */
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -712,6 +727,19 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	StoreCatalogInheritance(relationId, inheritOids);
 
 	/*
+	 * Process and store partition bound.  Do this before
+	 * CommandCounterIncrement() below, so that we are not deemed an existing
+	 * partition by StorePartitionBound() (which is basically found out by
+	 * querying pg_inherits).
+	 */
+	if (stmt->partbound)
+	{
+		Oid		parentId = linitial_oid(inheritOids);
+
+		StorePartitionBound(relationId, parentId, stmt->partbound);
+	}
+
+	/*
 	 * We must bump the command counter to make the newly-created relation
 	 * tuple visible for opening.
 	 */
@@ -995,6 +1023,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 	if (!OidIsValid(relOid))
 		return;
 
+	if (relid_is_partition(relOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+						get_rel_name(get_partition_parent(relOid))),
+				 errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
 	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
 	if (!HeapTupleIsValid(tuple))
 		return;					/* concurrently dropped, so nothing to do */
@@ -1086,7 +1121,8 @@ ExecuteTruncate(TruncateStmt *stmt)
 		rels = lappend(rels, rel);
 		relids = lappend_oid(relids, myrelid);
 
-		if (recurse)
+		/* Force inheritance recursion, if partitioned table. */
+		if (recurse || relid_is_partitioned(myrelid))
 		{
 			ListCell   *child;
 			List	   *children;
@@ -1810,8 +1846,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				Node	   *expr;
 				bool		found_whole_row;
 
-				/* ignore if the constraint is non-inheritable */
-				if (check[i].ccnoinherit)
+				/*
+				 * ignore if the constraint is non-inheritable and this is not
+				 * the partition case (constraints of partitioned tables are
+				 * always present in their partitions)
+				 */
+				if (check[i].ccnoinherit && !is_partition)
 					continue;
 
 				/* Adjust Vars to match new table's column numbering */
@@ -2246,6 +2286,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot rename column of typed table")));
 
+	if (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
@@ -2472,11 +2517,13 @@ renameatt(RenameStmt *stmt)
 		return InvalidObjectAddress;
 	}
 
+	/* Force inheritance recursion, if partitioned table. */
 	attnum =
 		renameatt_internal(relid,
 						   stmt->subname,		/* old att name */
 						   stmt->newname,		/* new att name */
-						   interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
+						   interpretInhOption(stmt->relation->inhOpt) ||
+						   relid_is_partitioned(relid),	/* recursive? */
 						   false,		/* recursing? */
 						   0,	/* expected inhcount */
 						   stmt->behavior);
@@ -2624,11 +2671,13 @@ RenameConstraint(RenameStmt *stmt)
 		}
 	}
 
+	/* Force inheritance recursion, if partitioned table. */
 	return
 		rename_constraint_internal(relid, typid,
 								   stmt->subname,
 								   stmt->newname,
-		 stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false,	/* recursive? */
+		 stmt->relation ? interpretInhOption(stmt->relation->inhOpt) ||
+								   relid_is_partitioned(relid) : false,	/* recursive? */
 								   false,		/* recursing? */
 								   0 /* expected inhcount */ );
 
@@ -2870,8 +2919,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
 
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
+	/* Force inheritance recursion, if partitioned table */
 	ATController(stmt,
-				 rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+				 rel, stmt->cmds,
+				 interpretInhOption(stmt->relation->inhOpt) ||
+					relid_is_partitioned(RelationGetRelid(rel)),
 				 lockmode);
 }
 
@@ -3988,7 +4040,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
 			 * Test the current data within the table against new constraints
 			 * generated by ALTER TABLE commands, but don't rebuild data.
 			 */
-			if (tab->constraints != NIL || tab->new_notnull)
+			if (tab->constraints != NIL || tab->new_notnull || tab->partitionCheck)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4132,6 +4184,21 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/*
+	 * Build expression execution states for partition check expression
+	 * as well.
+	 */
+	if (tab->partitionCheck)
+	{
+		List *expr;
+
+		needscan = true;
+		Assert(tab->partitionCheck->expr);
+		expr = list_make1(tab->partitionCheck->expr);
+		tab->partitionCheck->state = (List *)
+									ExecPrepareExpr((Expr *) expr, estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4321,6 +4388,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+			if (tab->partitionCheck &&
+				!ExecQual(tab->partitionCheck->state, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("table contains a row violating the partition bound specification")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4518,7 +4591,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 	 */
 	if (recurse &&
 		(rel->rd_rel->relkind == RELKIND_RELATION ||
-		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		 rel->rd_rel->relkind == RELKIND_PARTITIONED_REL))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4840,6 +4914,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5295,6 +5374,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	List	   *indexoidlist;
 	ListCell   *indexoidscan;
 	ObjectAddress address;
+	Oid			relid;
 
 	/*
 	 * lookup the attribute
@@ -5362,6 +5442,28 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	list_free(indexoidlist);
 
 	/*
+	 * If rel is partition, throw error if the attribute is not null in
+	 * parent
+	 */
+	relid = RelationGetRelid(rel);
+	if (relid_is_partition(relid))
+	{
+		Oid			parentId = get_partition_parent(relid);
+		Relation	parent = heap_open(parentId, AccessShareLock);
+		TupleDesc	tupDesc = RelationGetDescr(parent);
+		AttrNumber	parent_attnum;
+
+		parent_attnum = get_attnum(parentId, colName);
+		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" is not null in parent",
+							colName),
+					 errhint("Please drop not null in the parent instead")));
+		heap_close(parent, AccessShareLock);
+	}
+
+	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
 	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5891,6 +5993,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
 	 */
@@ -5936,11 +6043,13 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 		if (!is_expr)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("cannot drop column named in partition key")));
+					 errmsg("cannot drop column named in%s partition key",
+							recursing ? " downstream" : "")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("cannot drop column referenced in partition key expression")));
+					 errmsg("cannot drop column referenced in%s partition key expression",
+							recursing ? " downstream" : "")));
 	}
 
 	ReleaseSysCache(tuple);
@@ -6374,8 +6483,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 	/*
 	 * If adding a NO INHERIT constraint, no need to find our children.
+	 * However, that applies only to regular inheritance parents.
 	 */
-	if (constr->is_no_inherit)
+	if (constr->is_no_inherit && !relid_is_partitioned(RelationGetRelid(rel)))
 		return address;
 
 	/*
@@ -7936,9 +8046,12 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 	/*
 	 * Propagate to children as appropriate.  Unlike most other ALTER
 	 * routines, we have to do this one level of recursion at a time; we can't
-	 * use find_all_inheritors to do it in one pass.
+	 * use find_all_inheritors to do it in one pass.  Note that if the parent
+	 * is a partitioned table, we propagate to children (partitions) despite
+	 * is_no_inherit_constraint.
 	 */
-	if (!is_no_inherit_constraint)
+	if (!is_no_inherit_constraint ||
+		relid_is_partitioned(RelationGetRelid(rel)))
 		children = find_inheritance_children(RelationGetRelid(rel), lockmode);
 	else
 		children = NIL;
@@ -8068,6 +8181,11 @@ ATPrepAlterColumnType(List **wqueue,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot alter column type of typed table")));
 
+	if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot alter column type of a partition")));
+
 	/* lookup the attribute so we can check inheritance status */
 	tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
 	if (!HeapTupleIsValid(tuple))
@@ -8098,11 +8216,13 @@ ATPrepAlterColumnType(List **wqueue,
 		if (!is_expr)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("cannot alter column named in partition key")));
+					 errmsg("cannot alter column named in%s partition key",
+							recursing ? " downstream" : "")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("cannot alter column referenced in partition key expression")));
+					 errmsg("cannot alter column referenced in%s partition key expression",
+							recursing ? " downstream" : "")));
 	}
 
 	/* Look up the target type */
@@ -10235,6 +10355,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (relid_is_partition(RelationGetRelid(child_rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10291,6 +10416,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
 
+	if (relid_is_partition(RelationGetRelid(parent_rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
+
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
 	 * (In particular, this disallows making a rel inherit from itself.)
@@ -10677,6 +10807,11 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 	ObjectAddress	address;
 	Relation		parent_rel;
 
+	if (relid_is_partition(RelationGetRelid(rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	/*
 	 * AccessShareLock on the parent is probably enough, seeing that DROP
 	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
@@ -12566,6 +12701,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 */
 	ATSimplePermissions(attachRel, ATT_TABLE);
 
+	if (relid_is_partition(RelationGetRelid(attachRel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is already a partition",
+						RelationGetRelationName(attachRel))));
+
 	if (attachRel->rd_rel->reloftype)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12599,6 +12740,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 		errmsg("cannot attach a temporary relation of another session as partition ")));
 
 	/* If parent has OIDs, attachRel must have OIDs */
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	/* If parent has OIDs then child must have OIDs */
 	if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12641,6 +12788,30 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	/* OK to create inheritance.  Rest of the checks performed there */
 	CreateInheritance(attachRel, rel);
 
+	/*
+	 * Set up to have the rows in table to be checked for violation of the
+	 * partition bound spec in phase 3 scan.
+	 */
+	if (!cmd->skip_validate)
+	{
+		AlteredTableInfo *tab;
+		PartitionCheck *check;
+		Expr *my_expr,
+			 *parent_expr = (Expr *) RelationGetPartitionCheckExpr(rel);
+
+		tab = ATGetQueueEntry(wqueue, attachRel);
+		check = (PartitionCheck *) palloc0(sizeof(PartitionCheck));
+		my_expr = get_check_expr_from_partbound(attachRel, rel, cmd->bound);
+		check->expr = parent_expr ? makeBoolExpr(AND_EXPR,
+										list_make2(parent_expr, my_expr), -1)
+									: my_expr;
+		tab->partitionCheck = check;
+	}
+
+	/* Store bound into the catalog */
+	StorePartitionBound(RelationGetRelid(attachRel), RelationGetRelid(rel),
+						cmd->bound);
+
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
 
 	/* keep our lock until commit */
@@ -12665,6 +12836,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	/* All inheritance related checks are performed within the function */
 	RemoveInheritance(partRel, rel);
 
+	/* Remove the pg_partition entry */
+	RemovePartitionEntryByRelId(RelationGetRelid(partRel));
+
+	/*
+	 * Invalidate the relcache so that the partition is no longer included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
 
 	/* keep our lock until commit */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 67492a5..23ed0b6 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1250,6 +1251,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck = (Expr *)
+						RelationGetPartitionCheckExpr(resultRelationDesc);
 }
 
 /*
@@ -1683,6 +1686,49 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = make_ands_implicit(resultRelInfo->ri_PartitionCheck);
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1694,9 +1740,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1727,7 +1773,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1751,6 +1797,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			char	   *val_desc;
+
+			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+			modifiedCols = bms_union(insertedCols, updatedCols);
+			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+													 slot,
+													 tupdesc,
+													 modifiedCols,
+													 64);
+			ereport(ERROR,
+					(errcode(ERRCODE_CHECK_VIOLATION),
+					 errmsg("new row violates the partition boundary"
+							" specification of \"%s\"",
+							RelationGetRelationName(rel)),
+			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+		}
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 7ad1140..6ecb6be 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6f28047..1ea1439 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2249,6 +2249,35 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readPartitionListSpec
+ */
+static PartitionListSpec *
+_readPartitionListSpec(void)
+{
+	READ_LOCALS(PartitionListSpec);
+
+	READ_NODE_FIELD(values);
+
+	READ_DONE();
+}
+
+/*
+ * _readPartitionRangeSpec
+ */
+static PartitionRangeSpec *
+_readPartitionRangeSpec(void)
+{
+	READ_LOCALS(PartitionRangeSpec);
+
+	READ_BOOL_FIELD(lowerinc);
+	READ_NODE_FIELD(lower);
+	READ_BOOL_FIELD(upperinc);
+	READ_NODE_FIELD(upper);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2478,6 +2507,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("PARTITIONLISTVALUES", 19))
+		return_value = _readPartitionListSpec();
+	else if (MATCH("PARTITIONRANGE", 14))
+		return_value = _readPartitionRangeSpec();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 9c11b09..ed3193b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_constraint.h"
 #include "foreign/fdwapi.h"
@@ -1124,6 +1125,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	Node		*pexpr;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1203,6 +1205,17 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition check constraint, if any */
+	pexpr = RelationGetPartitionCheckExpr(relation);
+	if (pexpr)
+	{
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes(pexpr, 1, varno, 0);
+
+		result = list_concat(result, make_ands_implicit((Expr *) pexpr));
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 7d2fedf..f55129a 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,6 +25,7 @@
 #include "postgres.h"
 
 #include "access/sysattr.h"
+#include "catalog/partition.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 307e383..5124dbf 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -202,8 +202,8 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	pstate->p_target_rangetblentry = rte;
 
 	/*
-	 * Override specified inheritance option, if relation is a partitioned
-	 * table and it's a target of INSERT (ie, alsoSource == false).
+	 * For UPDATE/DELETE, force inheritance recursion, if partitioned
+	 * table.
 	 */
 	if (alsoSource)
 		rte->inh |= relid_is_partitioned(rte->relid);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index abcc1d6..b3870f6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1218,8 +1218,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
 	 *
-	 * Note: we override the specified inheritance option, if relation is a
-	 * partitioned table.
+	 * Note: We force inheritance recursion, if partitioned table.
 	 */
 	rte->lateral = false;
 	rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c0a1bb0..d2273c6 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -32,6 +32,7 @@
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
 #include "catalog/index.h"
+#include "catalog/partition.h"
 #include "catalog/namespace.h"
 #include "catalog/partition.h"
 #include "catalog/pg_am.h"
@@ -3105,6 +3106,11 @@ transformPartitionOf(CreateStmtContext *cxt, Node *bound)
 	if (parentRel->rd_rel->relhasoids)
 		cxt->hasoids = true;
 
+	/*
+	 * Create ColumnDefs correponding to parent's attributes as "inherited"
+	 * attributes.  Also, set is_for_partition whose function is similar to
+	 * is_from_type for MergeAttributes()'s purposes.
+	 */
 	tupdesc = RelationGetDescr(parentRel);
 	for (i = 0; i < tupdesc->natts; i++)
 	{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 167c856..b3d9cc2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8221,6 +8221,84 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionListSpec:
+			{
+				PartitionListSpec *list_spec = (PartitionListSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " IN (");
+				sep = "";
+				foreach (cell, list_spec->values)
+				{
+					Const *val = lfirst(cell);
+
+					appendStringInfoString(buf, sep);
+					get_const_expr(val, context, -1);
+					sep = ", ";
+				}
+
+				appendStringInfoString(buf, ")");
+			}
+			break;
+
+		case T_PartitionRangeSpec:
+			{
+				PartitionRangeSpec *range_spec = (PartitionRangeSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " START");
+				if (!range_spec->lower)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->lower)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (!range_spec->lowerinc)
+						appendStringInfoString(buf, " EXCLUSIVE");
+				}
+
+				appendStringInfoString(buf, " END");
+
+				if (!range_spec->upper)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->upper)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (range_spec->upperinc)
+						appendStringInfoString(buf, " INCLUSIVE");
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e48a1f4..36a2dbf 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -50,6 +50,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/partition.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
@@ -2055,6 +2056,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	FreePartitionKey(relation->rd_partkey);
+	if (relation->rd_pdcxt)
+		MemoryContextDelete(relation->rd_pdcxt);
+	if (relation->rd_partcheck)
+		pfree(relation->rd_partcheck);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -4984,6 +4989,8 @@ load_relcache_init_file(bool shared)
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
 		rel->rd_partkey = NULL;
+		rel->rd_partdesc = NULL;
+		rel->rd_partcheck = NULL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index a6563fe..4a5783a 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -569,6 +570,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionRelationId,		/* PARTRELID */
+		PartitionRelidIndexId,
+		1,
+		{
+			Anum_pg_partition_partrelid,
+			0,
+			0,
+			0
+		},
+		128
+	},
 	{PartitionedRelationId,		/* PARTEDRELID */
 		PartitionedRelidIndexId,
 		1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 163d013..06c8c23 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6085,6 +6085,63 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find all the inheritance information */
+
+	appendPQExpBufferStr(query,
+						 "SELECT partrelid, inhparent AS partparent, "
+						 "		 pg_get_expr(partbound, partrelid) AS partbound "
+						 "FROM pg_partition, pg_inherits "
+						 "WHERE partrelid = inhrelid");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -15278,6 +15335,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -15306,8 +15374,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -15336,7 +15407,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -15401,15 +15473,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15579,6 +15658,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0292859..760067a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 78a1fdb..6bbfc9a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1694,6 +1694,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(partbound, partrelid)"
+			 " FROM pg_catalog.pg_partition p"
+			 " JOIN pg_catalog.pg_inherits i"
+			 " ON p.partrelid = i.inhrelid"
+			 " WHERE p.partrelid = '%s';", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2455,8 +2483,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2485,9 +2517,15 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
+		/* print child tables (exclude, if parent is a partitioned table) */
 		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s' AND c.relkind != 'P')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index cadb741..73786c0 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -322,6 +322,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication
 DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
 #define PartitionedRelidIndexId          3351
 
+DECLARE_UNIQUE_INDEX(pg_partition_partrelid_index, 3354, on pg_partition using btree(partrelid oid_ops));
+#define PartitionRelidIndexId          3354
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 31dbe17..6204ad2 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -42,6 +42,53 @@ typedef struct PartitionKeyData
 
 typedef struct PartitionKeyData *PartitionKey;
 
+/* Internal representation of a list partition bound */
+typedef struct PartitionListBound
+{
+	bool	contains_null;
+	int		nvalues;	/* number of values in the following array */
+	Datum  *values;		/* values contained in the list */
+} PartitionListBound;
+
+/* Internal representation of a range partition bound */
+typedef struct
+{
+	Datum	   *val;			/* composite bound value, if any */
+	bool		infinite;		/* bound is +/- infinity */
+	bool		inclusive;		/* bound is inclusive (vs exclusive) */
+	bool		lower;			/* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/*
+ * Partition tree node
+ *
+ *	pkinfo					PartitionKey executor state
+ *	pdesc					info about immediate partitions (see
+							PartitionDescData)
+ *	index					index within 0..(parent_pdesc->nparts - 1)
+ *	num_leaf_partitions		number of leaf partitions in the partition
+ *							tree rooted at this node
+ *	offset					index (0-based) of the first leaf partition
+ *							in the partition tree rooted at this node
+ *	downlink				link to the leftmost child node
+ *	next					link to the right sibling node on a given level
+ */
+typedef struct PartitionDescNodeData
+{
+	PartitionDesc		pdesc;
+	Oid					relid;
+	int					index;
+	int					offset;
+	int					num_leaf_partitions;
+
+	struct PartitionDescNodeData *downlink;
+	struct PartitionDescNodeData *next;
+} PartitionDescNodeData;
+
+typedef struct PartitionDescNodeData *PartitionDescNode;
+
 extern void StorePartitionKey(Relation rel,
 					char strategy,
 					int16 partnatts,
@@ -53,4 +100,14 @@ extern void RelationBuildPartitionKey(Relation relation);
 extern void FreePartitionKey(PartitionKey key);
 extern bool relid_is_partitioned(Oid relid);
 
+extern void StorePartitionBound(Oid relid, Oid parentId, Node *bound);
+extern void RemovePartitionEntryByRelId(Oid relid);
+extern bool relid_is_partition(Oid relid);
+extern Oid get_partition_parent(Oid relid);
+extern void RelationDropPartitions(Oid relid);
+extern Node *RelationGetPartitionCheckExpr(Relation rel);
+extern Expr *get_check_expr_from_partbound(Relation rel, Relation parent,
+										   Node *bound);
+extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
+extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
 #endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_partition.h b/src/include/catalog/pg_partition.h
new file mode 100644
index 0000000..d31a6b7
--- /dev/null
+++ b/src/include/catalog/pg_partition.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partition.h
+ *	  definition of the system "partition" relation (pg_partition)
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partition.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITION_H
+#define PG_PARTITION_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_rel definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_rel
+ * ----------------
+ */
+#define PartitionRelationId 3353
+
+CATALOG(pg_partition,3353) BKI_WITHOUT_OIDS
+{
+	Oid			partrelid;		/* partition relation OID */
+
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+	pg_node_tree	partbound;	/* node representation of partition bound */
+#endif
+} FormData_pg_partition;
+
+/* ----------------
+ *      Form_pg_partition corresponds to a pointer to a tuple with
+ *      the format of pg_partition relation.
+ * ----------------
+ */
+typedef FormData_pg_partition *Form_pg_partition;
+
+/* ----------------
+ *      compiler constants for pg_partition
+ * ----------------
+ */
+#define Natts_pg_partition				2
+#define Anum_pg_partition_partrelid		1
+#define Anum_pg_partition_partbound		2
+
+#endif   /* PG_PARTITION_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ee4e189..74288e2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -319,6 +319,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -343,6 +345,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	Expr	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 9bc2a31..c77eb2d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -91,6 +91,10 @@ typedef struct RelationData
 	struct RowSecurityDesc *rd_rsdesc;	/* row security policies, or NULL */
 
 	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+	MemoryContext 	rd_pdcxt;		/* private context for partdesc */
+	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
+	Node		   *rd_partcheck;	/* partition CHECK constraint expression
+									 * tree */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index e727842..4e493e0 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTRELID,
 	PARTEDRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1e473d..57bde9d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
 pg_opclass|t
 pg_operator|t
 pg_opfamily|t
+pg_partition|t
 pg_partitioned|t
 pg_pltemplate|t
 pg_policy|t
-- 
1.7.1

