From 99c32c8bb1d424aecdf24fcb0e96a470c91d83a0 Mon Sep 17 00:00:00 2001
From: "dgrowley@gmail.com" <dgrowley@gmail.com>
Date: Thu, 2 Aug 2018 16:02:42 +1200
Subject: [PATCH v1 2/4] Store partition details in pg_partition instead of
 pg_inherits

---
 contrib/postgres_fdw/postgres_fdw.c        |   4 +-
 src/backend/catalog/Makefile               |   2 +-
 src/backend/catalog/heap.c                 |  72 ++---
 src/backend/catalog/index.c                |   2 +-
 src/backend/catalog/partition.c            | 140 +++++----
 src/backend/catalog/pg_inherits.c          |   6 +
 src/backend/commands/analyze.c             |  12 +-
 src/backend/commands/lockcmds.c            | 127 +++++---
 src/backend/commands/publicationcmds.c     |  10 +-
 src/backend/commands/tablecmds.c           | 477 +++++++++++++++++++++--------
 src/backend/commands/trigger.c             |  10 +-
 src/backend/commands/vacuum.c              |   3 +-
 src/backend/executor/execPartition.c       |   3 +-
 src/backend/optimizer/prep/prepunion.c     | 215 ++++++++-----
 src/backend/partitioning/partbounds.c      |  11 +-
 src/backend/rewrite/rewriteDefine.c        |   2 +-
 src/backend/tcop/utility.c                 |   9 +-
 src/backend/utils/cache/partcache.c        |  70 +++--
 src/backend/utils/cache/syscache.c         |  14 +-
 src/bin/pg_dump/common.c                   |  22 ++
 src/bin/pg_dump/pg_dump.c                  |  30 +-
 src/bin/pg_dump/pg_dump.h                  |   1 +
 src/bin/psql/describe.c                    |  60 +++-
 src/bin/psql/tab-complete.c                |   1 +
 src/include/catalog/heap.h                 |   4 +-
 src/include/catalog/indexing.h             |   6 +
 src/include/catalog/partition.h            |   2 +
 src/include/catalog/pg_class.dat           |  18 +-
 src/include/catalog/pg_class.h             |   3 +-
 src/include/catalog/toasting.h             |   1 +
 src/include/nodes/parsenodes.h             |   2 +-
 src/include/utils/rel.h                    |   6 +
 src/include/utils/syscache.h               |   1 +
 src/test/regress/expected/alter_table.out  |   4 +-
 src/test/regress/expected/misc_sanity.out  |   3 +-
 src/test/regress/expected/sanity_check.out |   1 +
 36 files changed, 907 insertions(+), 447 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 0803c30a48..beb867d613 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -4522,7 +4522,9 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
 		deparseStringLiteral(&buf, stmt->remote_schema);
 
 		/* Partitions are supported since Postgres 10 */
-		if (PQserverVersion(conn) >= 100000)
+		if (PQserverVersion(conn) >= 120000)
+			appendStringInfoString(&buf, " AND c.relpartitionparent <> 0 ");
+		else if (PQserverVersion(conn) >= 100000)
 			appendStringInfoString(&buf, " AND NOT c.relispartition ");
 
 		/* Apply restrictions for LIMIT TO and EXCEPT */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 0865240f11..43d9e2eaaa 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -46,7 +46,7 @@ CATALOG_HEADERS := \
 	pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
 	pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
-	pg_subscription_rel.h
+	pg_subscription_rel.h pg_partition.h
 
 GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h
 
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 4cfc0c8911..c6429a3785 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partition.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_subscription_rel.h"
@@ -810,7 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
-	values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
+	values[Anum_pg_class_relpartitionparent - 1] = ObjectIdGetDatum(rd_rel->relpartitionparent);
 	values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
@@ -823,9 +824,6 @@ InsertPgClassTuple(Relation pg_class_desc,
 	else
 		nulls[Anum_pg_class_reloptions - 1] = true;
 
-	/* relpartbound is set by updating this tuple, if necessary */
-	nulls[Anum_pg_class_relpartbound - 1] = true;
-
 	tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
 
 	/*
@@ -929,8 +927,8 @@ AddNewRelationTuple(Relation pg_class_desc,
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
 
-	/* relispartition is always set by updating this tuple later */
-	new_rel_reltup->relispartition = false;
+	/* relpartitionparent is always set by updating this tuple later */
+	new_rel_reltup->relpartitionparent = InvalidOid;
 
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
 
@@ -1807,11 +1805,17 @@ heap_drop_with_catalog(Oid relid)
 	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "cache lookup failed for relation %u", relid);
-	if (((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+
+	parentOid = ((Form_pg_class) GETSTRUCT(tuple))->relpartitionparent;
+	if (OidIsValid(parentOid))
 	{
-		parentOid = get_partition_parent(relid);
+		HeapTuple parttup;
+		Relation	pgpart;
+
 		LockRelationOid(parentOid, AccessExclusiveLock);
 
+		pgpart =  heap_open(PartitionRelationId, RowExclusiveLock);
+
 		/*
 		 * If this is not the default partition, dropping it will change the
 		 * default partition's partition constraint, so we must lock it.
@@ -1819,6 +1823,14 @@ heap_drop_with_catalog(Oid relid)
 		defaultPartOid = get_default_partition_oid(parentOid);
 		if (OidIsValid(defaultPartOid) && relid != defaultPartOid)
 			LockRelationOid(defaultPartOid, AccessExclusiveLock);
+
+		parttup = SearchSysCacheCopy1(PARTSRELID, ObjectIdGetDatum(relid));
+		if (!HeapTupleIsValid(parttup))
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		CatalogTupleDelete(pgpart, &parttup->t_self);
+
+		heap_close(pgpart, RowExclusiveLock);
 	}
 
 	ReleaseSysCache(tuple);
@@ -2759,7 +2771,8 @@ MergeWithExistingConstraint(Relation rel, const char *ccname, Node *expr,
 			 * constraints are always non-local, including those that were
 			 * merged.
 			 */
-			if (is_local && !con->conislocal && !rel->rd_rel->relispartition)
+			if (is_local && !con->conislocal &&
+				!OidIsValid(rel->rd_rel->relpartitionparent))
 				allow_merge = true;
 
 			if (!found || !allow_merge)
@@ -2809,7 +2822,7 @@ MergeWithExistingConstraint(Relation rel, const char *ccname, Node *expr,
 			 * inherited only once since it cannot have multiple parents and
 			 * it is never considered local.
 			 */
-			if (rel->rd_rel->relispartition)
+			if (OidIsValid(rel->rd_rel->relpartitionparent))
 			{
 				con->coninhcount = 1;
 				con->conislocal = false;
@@ -3481,9 +3494,9 @@ RemovePartitionKeyByRelId(Oid relid)
 }
 
 /*
- * StorePartitionBound
- *		Update pg_class tuple of rel to store the partition bound and set
- *		relispartition to true
+ * MarkRelationPartitioned
+ *		Update pg_class tuple of rel to set relpartitionparent to the parent's
+ *		Oid.
  *
  * If this is the default partition, also update the default partition OID in
  * pg_partitioned_table.
@@ -3493,14 +3506,10 @@ RemovePartitionKeyByRelId(Oid relid)
  * default partition, we must invalidate its relcache entry as well.
  */
 void
-StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
+MarkRelationPartitioned(Relation rel, Relation parent, bool is_default)
 {
 	Relation	classRel;
-	HeapTuple	tuple,
-				newtuple;
-	Datum		new_val[Natts_pg_class];
-	bool		new_null[Natts_pg_class],
-				new_repl[Natts_pg_class];
+	HeapTuple	tuple;
 	Oid			defaultPartOid;
 
 	/* Update pg_class tuple */
@@ -3514,36 +3523,23 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 #ifdef USE_ASSERT_CHECKING
 	{
 		Form_pg_class classForm;
-		bool		isnull;
 
 		classForm = (Form_pg_class) GETSTRUCT(tuple);
-		Assert(!classForm->relispartition);
-		(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
-							   &isnull);
-		Assert(isnull);
+		Assert(!OidIsValid(classForm->relpartitionparent));
 	}
 #endif
 
-	/* Fill in relpartbound value */
-	memset(new_val, 0, sizeof(new_val));
-	memset(new_null, false, sizeof(new_null));
-	memset(new_repl, false, sizeof(new_repl));
-	new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(bound));
-	new_null[Anum_pg_class_relpartbound - 1] = false;
-	new_repl[Anum_pg_class_relpartbound - 1] = true;
-	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
-								 new_val, new_null, new_repl);
-	/* Also set the flag */
-	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
-	CatalogTupleUpdate(classRel, &newtuple->t_self, newtuple);
-	heap_freetuple(newtuple);
+	/* Set the relpartitionparent */
+	((Form_pg_class) GETSTRUCT(tuple))->relpartitionparent = RelationGetRelid(parent);
+	CatalogTupleUpdate(classRel, &tuple->t_self, tuple);
+	heap_freetuple(tuple);
 	heap_close(classRel, RowExclusiveLock);
 
 	/*
 	 * If we're storing bounds for the default partition, update
 	 * pg_partitioned_table too.
 	 */
-	if (bound->is_default)
+	if (is_default)
 		update_default_partition_oid(RelationGetRelid(parent),
 									 RelationGetRelid(rel));
 
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8b276bc430..eda850edef 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -936,7 +936,7 @@ index_create(Relation heapRelation,
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
 	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relhasoids = false;
-	indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
+	indexRelation->rd_rel->relpartitionparent = parentIndexRelid;
 
 	/*
 	 * store index's pg_class entry
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 558022647c..d3e6787885 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -22,6 +22,7 @@
 #include "catalog/indexing.h"
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
+#include "catalog/pg_partition.h"
 #include "catalog/pg_partitioned_table.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
@@ -29,124 +30,121 @@
 #include "optimizer/var.h"
 #include "partitioning/partbounds.h"
 #include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
 #include "utils/fmgroids.h"
 #include "utils/partcache.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
-
-static Oid	get_partition_parent_worker(Relation inhRel, Oid relid);
-static void get_partition_ancestors_worker(Relation inhRel, Oid relid,
-							   List **ancestors);
+static void get_partition_descendants_worker(Oid relid, LOCKMODE lockmode,
+								 List **reloids);
 
 /*
  * get_partition_parent
  *		Obtain direct parent of given relation
  *
- * Returns inheritance parent of a partition by scanning pg_inherits
- *
- * Note: Because this function assumes that the relation whose OID is passed
- * as an argument will have precisely one parent, it should only be called
- * when it is known that the relation is a partition.
+ * Returns partition parent of a partition or InvalidOid if there is no parent
  */
 Oid
 get_partition_parent(Oid relid)
 {
-	Relation	catalogRelation;
 	Oid			result;
+	HeapTuple	tuple;
 
-	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
-
-	result = get_partition_parent_worker(catalogRelation, relid);
-
-	if (!OidIsValid(result))
-		elog(ERROR, "could not find tuple for parent of relation %u", relid);
+	tuple = SearchSysCache1(RELOID, relid);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", relid);
 
-	heap_close(catalogRelation, AccessShareLock);
+	result = ((Form_pg_class) GETSTRUCT(tuple))->relpartitionparent;
+	ReleaseSysCache(tuple);
 
 	return result;
 }
 
 /*
- * get_partition_parent_worker
- *		Scan the pg_inherits relation to return the OID of the parent of the
- *		given relation
+ * get_partition_ancestors
+ *		Obtain ancestors of a given partition or partitioned index.
+ *
+ * Follows pg_class.relpartitionparent links and returns a list of ancestors
+ * of the given partition or partitioned index starting with the parent and
+ * ending with the top-level partitioned table or index.
  */
-static Oid
-get_partition_parent_worker(Relation inhRel, Oid relid)
+List *
+get_partition_ancestors(Oid relid)
 {
-	SysScanDesc scan;
-	ScanKeyData key[2];
-	Oid			result = InvalidOid;
+	List	   *result = NIL;
 	HeapTuple	tuple;
 
-	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(inhRel, InheritsRelidSeqnoIndexId, true,
-							  NULL, 2, key);
-	tuple = systable_getnext(scan);
-	if (HeapTupleIsValid(tuple))
+	for (;;)
 	{
-		Form_pg_inherits form = (Form_pg_inherits) GETSTRUCT(tuple);
+		Oid parentOid;
 
-		result = form->inhparent;
-	}
+		tuple = SearchSysCache1(RELOID, relid);
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		parentOid = ((Form_pg_class) GETSTRUCT(tuple))->relpartitionparent;
+		ReleaseSysCache(tuple);
 
-	systable_endscan(scan);
+		if (!OidIsValid(parentOid))
+			break;
+
+		result = lappend_oid(result, parentOid);
+
+		relid = parentOid;
+	}
 
 	return result;
 }
 
 /*
- * get_partition_ancestors
- *		Obtain ancestors of given relation
- *
- * Returns a list of ancestors of the given relation.
- *
- * Note: Because this function assumes that the relation whose OID is passed
- * as an argument and each ancestor will have precisely one parent, it should
- * only be called when it is known that the relation is a partition.
+ * get_partition_descendants
+ *		Returns a list of Oids of all partitions which descend from 'relid'
+ *		including 'relid' itself.  Obtains a 'lockmode' level lock on each
+ *		item in the list.
  */
 List *
-get_partition_ancestors(Oid relid)
+get_partition_descendants(Oid relid, LOCKMODE lockmode)
 {
-	List	   *result = NIL;
-	Relation	inhRel;
-
-	inhRel = heap_open(InheritsRelationId, AccessShareLock);
-
-	get_partition_ancestors_worker(inhRel, relid, &result);
+	List *reloids = NIL;
 
-	heap_close(inhRel, AccessShareLock);
+	get_partition_descendants_worker(relid, lockmode, &reloids);
 
-	return result;
+	return reloids;
 }
 
-/*
- * get_partition_ancestors_worker
- *		recursive worker for get_partition_ancestors
- */
 static void
-get_partition_ancestors_worker(Relation inhRel, Oid relid, List **ancestors)
+get_partition_descendants_worker(Oid relid, LOCKMODE lockmode, List **reloids)
 {
-	Oid			parentOid;
+	Relation rel = relation_open(relid, lockmode);
+	PartitionDesc partdesc;
+	int i;
+
+	partdesc = RelationGetPartitionDesc(rel);
+
+	Assert(partdesc);
 
-	/* Recursion ends at the topmost level, ie., when there's no parent */
-	parentOid = get_partition_parent_worker(inhRel, relid);
-	if (parentOid == InvalidOid)
-		return;
+	*reloids = lappend_oid(*reloids, relid);
 
-	*ancestors = lappend_oid(*ancestors, parentOid);
-	get_partition_ancestors_worker(inhRel, parentOid, ancestors);
+	for (i = 0; i < partdesc->nparts; i++)
+	{
+		Oid partoid = partdesc->oids[i];
+
+		if (!partdesc->is_leaf[i])
+			get_partition_descendants_worker(partoid, lockmode, reloids);
+		else
+		{
+			if (lockmode != NoLock)
+				LockRelationOid(partoid, lockmode);
+
+			*reloids = lappend_oid(*reloids, partoid);
+		}
+	}
+
+	relation_close(rel, NoLock);
 }
 
+
 /*
  * map_partition_varattnos - maps varattno of any Vars in expr from the
  * attno's of 'from_rel' to the attno's of 'to_rel' partition, each of which
diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index 85baca54cc..87c8fd3266 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -28,6 +28,7 @@
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
+#include "utils/lsyscache.h" // get_rel_relkind
 #include "utils/memutils.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
@@ -172,6 +173,8 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents)
 			   *rel_numparents;
 	ListCell   *l;
 
+	Assert(get_rel_relkind(parentrelId) != RELKIND_PARTITIONED_TABLE);
+
 	memset(&ctl, 0, sizeof(ctl));
 	ctl.keysize = sizeof(Oid);
 	ctl.entrysize = sizeof(SeenRelsEntry);
@@ -267,6 +270,9 @@ has_subclass(Oid relationId)
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "cache lookup failed for relation %u", relationId);
 
+	Assert(!OidIsValid(((Form_pg_class) GETSTRUCT(tuple))->relpartitionparent));
+	Assert(((Form_pg_class) GETSTRUCT(tuple))->relkind != RELKIND_PARTITIONED_TABLE);
+
 	result = ((Form_pg_class) GETSTRUCT(tuple))->relhassubclass;
 	ReleaseSysCache(tuple);
 	return result;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 3e148f03d0..83f70fb165 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -26,6 +26,7 @@
 #include "catalog/catalog.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
+#include "catalog/partition.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
@@ -311,9 +312,10 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 					   relpages, false, in_outer_xact, elevel);
 
 	/*
-	 * If there are child tables, do recursive ANALYZE.
+	 * If there are child tables or it's a partitioned table, do recursive
+	 * ANALYZE.
 	 */
-	if (onerel->rd_rel->relhassubclass)
+	if (onerel->rd_rel->relhassubclass || onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		do_analyze_rel(onerel, options, params, va_cols, acquirefunc, relpages,
 					   true, in_outer_xact, elevel);
 
@@ -1334,8 +1336,10 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 	 * Find all members of inheritance set.  We only need AccessShareLock on
 	 * the children.
 	 */
-	tableOIDs =
-		find_all_inheritors(RelationGetRelid(onerel), AccessShareLock, NULL);
+	if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		tableOIDs = get_partition_descendants(RelationGetRelid(onerel), AccessShareLock);
+	else
+		tableOIDs = find_all_inheritors(RelationGetRelid(onerel), AccessShareLock, NULL);
 
 	/*
 	 * Check that there's at least one descendant, else fail.  This could
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 71278b38cf..4a583c1c8b 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "commands/lockcmds.h"
 #include "miscadmin.h"
@@ -29,6 +30,8 @@
 
 static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid);
 static AclResult LockTableAclCheck(Oid relid, LOCKMODE lockmode, Oid userid);
+static bool LockSingleTable(Oid relid, LOCKMODE lockmode, bool nowait,
+				Oid userid);
 static void RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid,
 							 Oid oldrelid, void *arg);
 static void LockViewRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, List *ancestor_views);
@@ -107,65 +110,109 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 }
 
 /*
- * Apply LOCK TABLE recursively over an inheritance tree
+ * LockSingleTable
+ *		Apply LOCK TABLE to a single table.
+ */
+static bool
+LockSingleTable(Oid relid, LOCKMODE lockmode, bool nowait, Oid userid)
+{
+	AclResult	aclresult;
+
+	/* Check permissions before acquiring the lock. */
+	aclresult = LockTableAclCheck(relid, lockmode, userid);
+	if (aclresult != ACLCHECK_OK)
+	{
+		char	   *relname = get_rel_name(relid);
+
+		if (!relname)
+			return false;		/* table concurrently dropped, just skip it */
+		aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(relid)), relname);
+	}
+
+	/* We have enough rights to lock the relation; do so. */
+	if (!nowait)
+		LockRelationOid(relid, lockmode);
+	else if (!ConditionalLockRelationOid(relid, lockmode))
+	{
+		/* try to throw error by name; relation could be deleted... */
+		char	   *relname = get_rel_name(relid);
+
+		if (!relname)
+			return false;		/* table concurrently dropped, just skip it */
+		ereport(ERROR,
+				(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+					errmsg("could not obtain lock on relation \"%s\"",
+						relname)));
+	}
+
+	/*
+	 * Even if we got the lock, child might have been concurrently
+	 * dropped. If so, we can skip it.
+	 */
+	if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)))
+	{
+		/* Release useless lock */
+		UnlockRelationOid(relid, lockmode);
+		return false;
+	}
+	return true;
+}
+
+/*
+ * Apply LOCK TABLE recursively over an inheritance tree or partitioned table
  *
  * We use find_inheritance_children not find_all_inheritors to avoid taking
  * locks far in advance of checking privileges.  This means we'll visit
- * multiply-inheriting children more than once, but that's no problem.
+ * multiply-inheriting children more than once, but that's no problem. For
+ * partitions, we simply loop through each partition checking if it's a
+ * sub-partitioned table or leaf partition, for the latter we needn't
+ * recurse, but for the former we must.
  */
 static void
 LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid)
 {
-	List	   *children;
-	ListCell   *lc;
+	Relation rel;
+
+	if (!LockSingleTable(reloid, lockmode, nowait, userid))
+		return;
 
-	children = find_inheritance_children(reloid, NoLock);
+	/* Lock taken above */
+	rel = relation_open(reloid, NoLock);
 
-	foreach(lc, children)
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Oid			childreloid = lfirst_oid(lc);
-		AclResult	aclresult;
+		PartitionDesc partdesc = RelationGetPartitionDesc(rel);
+		int			nparts = partdesc->nparts;
+		int			i;
 
-		/* Check permissions before acquiring the lock. */
-		aclresult = LockTableAclCheck(childreloid, lockmode, userid);
-		if (aclresult != ACLCHECK_OK)
+		for (i = 0; i < nparts; i++)
 		{
-			char	   *relname = get_rel_name(childreloid);
+			Oid partoid = partdesc->oids[i];
 
-			if (!relname)
-				continue;		/* child concurrently dropped, just skip it */
-			aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(childreloid)), relname);
+			if (partdesc->is_leaf[i])
+				LockSingleTable(partoid, lockmode, nowait, userid);
+			else
+				LockTableRecurse(partoid, lockmode, nowait, userid);
 		}
+	}
 
-		/* We have enough rights to lock the relation; do so. */
-		if (!nowait)
-			LockRelationOid(childreloid, lockmode);
-		else if (!ConditionalLockRelationOid(childreloid, lockmode))
-		{
-			/* try to throw error by name; relation could be deleted... */
-			char	   *relname = get_rel_name(childreloid);
-
-			if (!relname)
-				continue;		/* child concurrently dropped, just skip it */
-			ereport(ERROR,
-					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-					 errmsg("could not obtain lock on relation \"%s\"",
-							relname)));
-		}
+	/* leaf partitions won't have inheritance children, so skip those */
+	else if (!OidIsValid(rel->rd_rel->relpartitionparent))
+	{
+		List	   *children;
+		ListCell   *lc;
 
-		/*
-		 * Even if we got the lock, child might have been concurrently
-		 * dropped. If so, we can skip it.
-		 */
-		if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(childreloid)))
+		children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+
+		foreach(lc, children)
 		{
-			/* Release useless lock */
-			UnlockRelationOid(childreloid, lockmode);
-			continue;
-		}
+			Oid			childreloid = lfirst_oid(lc);
 
-		LockTableRecurse(childreloid, lockmode, nowait, userid);
+			LockTableRecurse(childreloid, lockmode, nowait, userid);
+		}
 	}
+
+	relation_close(rel, NoLock);
 }
 
 /*
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 6f7762a906..4622173e8a 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -28,6 +28,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/objectaddress.h"
+#include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_publication.h"
@@ -529,8 +530,13 @@ OpenTableList(List *tables)
 			ListCell   *child;
 			List	   *children;
 
-			children = find_all_inheritors(myrelid, ShareUpdateExclusiveLock,
-										   NULL);
+			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+				children = get_partition_descendants(myrelid,
+													 ShareUpdateExclusiveLock);
+			else
+				children = find_all_inheritors(myrelid,
+											   ShareUpdateExclusiveLock,
+											   NULL);
 
 			foreach(child, children)
 			{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index eb2d33dd86..8a0fcd7ece 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partition.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -477,6 +478,8 @@ static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 					  List **partexprs, Oid *partopclass, Oid *partcollation, char strategy);
+static void AttachPartition(Relation attachrel, Relation rel,
+				PartitionBoundSpec *bound);
 static void CreateInheritance(Relation child_rel, Relation parent_rel);
 static void RemoveInheritance(Relation child_rel, Relation parent_rel);
 static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
@@ -492,8 +495,8 @@ static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation rel,
 static void validatePartitionedIndex(Relation partedIdx, Relation partedTbl);
 static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 					  Relation partitionTbl);
-static void update_relispartition(Relation classRel, Oid relationId,
-					  bool newval);
+static void update_relpartitionparent(Relation classRel, Oid relationId,
+					  Oid newparent);
 
 
 /* ----------------------------------------------------------------
@@ -771,7 +774,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										  typaddress);
 
 	/* Store inheritance information for new rel. */
-	StoreCatalogInheritance(relationId, inheritOids, stmt->partbound != NULL);
+	if (stmt->partbound == NULL)
+		StoreCatalogInheritance(relationId, inheritOids, false);
 
 	/*
 	 * We must bump the command counter to make the newly-created relation
@@ -860,8 +864,11 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			heap_close(defaultRel, NoLock);
 		}
 
+		/* Add the pg_partition record */
+		AttachPartition(rel, parent, bound);
+
 		/* Update the pg_class entry. */
-		StorePartitionBound(rel, parent, bound);
+		MarkRelationPartitioned(rel, parent, bound->is_default);
 
 		heap_close(parent, NoLock);
 	}
@@ -1204,7 +1211,7 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 	struct DropRelationCallbackState *state;
 	char		relkind;
 	char		expected_relkind;
-	bool		is_partition;
+	Oid			parentoid;
 	Form_pg_class classform;
 	LOCKMODE	heap_lockmode;
 
@@ -1243,7 +1250,7 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 	if (!HeapTupleIsValid(tuple))
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
-	is_partition = classform->relispartition;
+	parentoid = classform->relpartitionparent;
 
 	/*
 	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
@@ -1298,11 +1305,10 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 	 * parent before its partitions, so we risk deadlock it we do it the other
 	 * way around.
 	 */
-	if (is_partition && relOid != oldRelOid)
+	if (OidIsValid(parentoid) && relOid != oldRelOid)
 	{
-		state->partParentOid = get_partition_parent(relOid);
-		if (OidIsValid(state->partParentOid))
-			LockRelationOid(state->partParentOid, AccessExclusiveLock);
+		state->partParentOid = parentoid;
+		LockRelationOid(parentoid, AccessExclusiveLock);
 	}
 }
 
@@ -1356,7 +1362,11 @@ ExecuteTruncate(TruncateStmt *stmt)
 			ListCell   *child;
 			List	   *children;
 
-			children = find_all_inheritors(myrelid, AccessExclusiveLock, NULL);
+			/* partitioned tables cannot have any inheritors */
+			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+				children = get_partition_descendants(myrelid, AccessExclusiveLock);
+			else
+				children = find_all_inheritors(myrelid, AccessExclusiveLock, NULL);
 
 			foreach(child, children)
 			{
@@ -1972,7 +1982,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from partitioned table \"%s\"",
 							parent->relname)));
-		if (relation->rd_rel->relispartition && !is_partition)
+		if (OidIsValid(relation->rd_rel->relpartitionparent) && !is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from partition \"%s\"",
@@ -2761,28 +2771,45 @@ renameatt_internal(Oid myrelid,
 		ListCell   *lo,
 				   *li;
 
-		/*
-		 * we need the number of parents for each child so that the recursive
-		 * calls to renameatt() can determine whether there are any parents
-		 * outside the inheritance hierarchy being processed.
-		 */
-		child_oids = find_all_inheritors(myrelid, AccessExclusiveLock,
-										 &child_numparents);
+		if (targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			child_oids = get_partition_descendants(myrelid, AccessExclusiveLock);
 
-		/*
-		 * find_all_inheritors does the recursive search of the inheritance
-		 * hierarchy, so all we have to do is process all of the relids in the
-		 * list that it returns.
-		 */
-		forboth(lo, child_oids, li, child_numparents)
+			foreach(lo, child_oids)
+			{
+				Oid			childrelid = lfirst_oid(lo);
+
+				if (childrelid == myrelid)
+					continue;
+				/* note we need not recurse again */
+				renameatt_internal(childrelid, oldattname, newattname, false, true, 1, behavior);
+			}
+		}
+		else if (!OidIsValid(targetrelation->rd_rel->relpartitionparent))
 		{
-			Oid			childrelid = lfirst_oid(lo);
-			int			numparents = lfirst_int(li);
+			/*
+			 * we need the number of parents for each child so that the recursive
+			 * calls to renameatt() can determine whether there are any parents
+			 * outside the inheritance hierarchy being processed.
+			 */
+			child_oids = find_all_inheritors(myrelid, AccessExclusiveLock,
+											 &child_numparents);
 
-			if (childrelid == myrelid)
-				continue;
-			/* note we need not recurse again */
-			renameatt_internal(childrelid, oldattname, newattname, false, true, numparents, behavior);
+			/*
+			 * find_all_inheritors does the recursive search of the inheritance
+			 * hierarchy, so all we have to do is process all of the relids in the
+			 * list that it returns.
+			 */
+			forboth(lo, child_oids, li, child_numparents)
+			{
+				Oid			childrelid = lfirst_oid(lo);
+				int			numparents = lfirst_int(li);
+
+				if (childrelid == myrelid)
+					continue;
+				/* note we need not recurse again */
+				renameatt_internal(childrelid, oldattname, newattname, false, true, numparents, behavior);
+			}
 		}
 	}
 	else
@@ -5043,12 +5070,17 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 		ListCell   *child;
 		List	   *children;
 
-		children = find_all_inheritors(relid, lockmode, NULL);
+		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			children = get_partition_descendants(relid, lockmode);
+		else if (!OidIsValid(rel->rd_rel->relpartitionparent))
+			children = find_all_inheritors(relid, lockmode, NULL);
+		else
+			children = NIL;
 
 		/*
-		 * find_all_inheritors does the recursive search of the inheritance
-		 * hierarchy, so all we have to do is process all of the relids in the
-		 * list that it returns.
+		 * find_all_inheritors and get_partition_descendants performs the
+		 * recursive search for all descendant tables, so all we have to do is
+		 * process all of the relids in the list that it returns.
 		 */
 		foreach(child, children)
 		{
@@ -5057,7 +5089,7 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 
 			if (childrelid == relid)
 				continue;
-			/* find_all_inheritors already got lock */
+			/* lock already obtained above */
 			childrel = relation_open(childrelid, NoLock);
 			CheckTableNotInUse(childrel, "ALTER TABLE");
 			ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode);
@@ -5374,7 +5406,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
-	if (rel->rd_rel->relispartition && !recursing)
+	if (OidIsValid(rel->rd_rel->relpartitionparent) && !recursing)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot add column to a partition")));
@@ -5697,7 +5729,18 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 * 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.
 	 */
-	children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionDesc partdesc = RelationGetPartitionDesc(rel);
+		int i;
+		children = NIL;
+		for (i = 0; i < partdesc->nparts; i++)
+			children = lappend_oid(children, partdesc->oids[i]);
+	}
+	else if (OidIsValid(rel->rd_rel->relpartitionparent))
+		children = NIL;
+	else
+		children = find_inheritance_children(RelationGetRelid(rel), lockmode);
 
 	/*
 	 * If we are told not to recurse, there had better not be any child
@@ -5971,10 +6014,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 
 	list_free(indexoidlist);
 
-	/* If rel is partition, shouldn't drop NOT NULL if parent has the same */
-	if (rel->rd_rel->relispartition)
+	/*
+	 * Disallow dropping of partitions NOT NULL constraints when the
+	 * constraint is present on the parent.
+	 */
+	if (OidIsValid(RelationGetParentRelid(rel)))
 	{
-		Oid			parentId = get_partition_parent(RelationGetRelid(rel));
+		Oid			parentId = RelationGetParentRelid(rel);
 		Relation	parent = heap_open(parentId, AccessShareLock);
 		TupleDesc	tupDesc = RelationGetDescr(parent);
 		AttrNumber	parent_attnum;
@@ -6800,7 +6846,21 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	 * 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.
 	 */
-	children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionDesc partdesc = RelationGetPartitionDesc(rel);
+		int i;
+		children = NIL;
+		for (i = 0; i < partdesc->nparts; i++)
+		{
+			LockRelationOid(partdesc->oids[i], lockmode);
+			children = lappend_int(children, partdesc->oids[i]);
+		}
+	}
+	else if (!OidIsValid(rel->rd_rel->relpartitionparent))
+		children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+	else
+		children = NIL;
 
 	if (children)
 	{
@@ -7251,7 +7311,21 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 * 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.
 	 */
-	children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionDesc partdesc = RelationGetPartitionDesc(rel);
+		int i;
+		children = NIL;
+		for (i = 0; i < partdesc->nparts; i++)
+		{
+			LockRelationOid(partdesc->oids[i], lockmode);
+			children = lappend_int(children, partdesc->oids[i]);
+		}
+	}
+	else if (!OidIsValid(rel->rd_rel->relpartitionparent))
+		children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+	else
+		children = NIL;
 
 	/*
 	 * Check if ONLY was specified with ALTER TABLE.  If so, allow the
@@ -8025,8 +8099,14 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 			 * shouldn't try to look for it in the children.
 			 */
 			if (!recursing && !con->connoinherit)
-				children = find_all_inheritors(RelationGetRelid(rel),
+			{
+				if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+					children = get_partition_descendants(RelationGetRelid(rel),
+														 lockmode);
+				else if (!OidIsValid(rel->rd_rel->relpartitionparent))
+					children = find_all_inheritors(RelationGetRelid(rel),
 											   lockmode, NULL);
+			}
 
 			/*
 			 * For CHECK constraints, we must ensure that we only mark the
@@ -8939,7 +9019,23 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 	 * use find_all_inheritors to do it in one pass.
 	 */
 	if (!is_no_inherit_constraint)
-		children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+	{
+		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			PartitionDesc partdesc = RelationGetPartitionDesc(rel);
+			int i;
+			children = NIL;
+			for (i = 0; i < partdesc->nparts; i++)
+			{
+				LockRelationOid(partdesc->oids[i], lockmode);
+				children = lappend_int(children, partdesc->oids[i]);
+			}
+		}
+		else if (!OidIsValid(rel->rd_rel->relpartitionparent))
+			children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+		else
+			children = NIL;
+	}
 	else
 		children = NIL;
 
@@ -9230,12 +9326,15 @@ ATPrepAlterColumnType(List **wqueue,
 		ListCell   *child;
 		List	   *children;
 
-		children = find_all_inheritors(relid, lockmode, NULL);
+		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			children = get_partition_descendants(relid, lockmode);
+		else
+			children = find_all_inheritors(relid, lockmode, NULL);
 
 		/*
-		 * find_all_inheritors does the recursive search of the inheritance
-		 * hierarchy, so all we have to do is process all of the relids in the
-		 * list that it returns.
+		 * find_all_inheritors and get_partition_descendants does the
+		 * recursive search of all descendants tables, so all we have to do is
+		 * process all of the relids in the list that it returns.
 		 */
 		foreach(child, children)
 		{
@@ -9245,7 +9344,7 @@ ATPrepAlterColumnType(List **wqueue,
 			if (childrelid == relid)
 				continue;
 
-			/* find_all_inheritors already got lock */
+			/* lock already obtained above */
 			childrel = relation_open(childrelid, NoLock);
 			CheckTableNotInUse(childrel, "ALTER TABLE");
 
@@ -11379,7 +11478,7 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
-	if (child_rel->rd_rel->relispartition)
+	if (OidIsValid(child_rel->rd_rel->relpartitionparent))
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of a partition")));
@@ -11443,7 +11542,7 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 						parent->relname)));
 
 	/* Likewise for partitions */
-	if (parent_rel->rd_rel->relispartition)
+	if (OidIsValid(parent_rel->rd_rel->relpartitionparent))
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot inherit from a partition")));
@@ -11506,6 +11605,59 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 	return address;
 }
 
+static void
+AttachPartition(Relation attachrel, Relation rel, PartitionBoundSpec *bound)
+{
+	Datum		values[Natts_pg_partition];
+	bool		nulls[Natts_pg_partition];
+	HeapTuple	tuple;
+	Relation	partRelation;
+	Oid			attachrelid = RelationGetRelid(attachrel);
+	Oid			partedrelid = RelationGetRelid(rel);
+	ObjectAddress childobject,
+				parentobject;
+
+	partRelation = heap_open(PartitionRelationId, RowExclusiveLock);
+
+	/*
+	 * Make the pg_partition entry
+	 */
+	values[Anum_pg_partition_partrelid - 1] = ObjectIdGetDatum(attachrelid);
+	values[Anum_pg_partition_parentrelid - 1] = ObjectIdGetDatum(partedrelid);
+	values[Anum_pg_partition_partbound - 1] = CStringGetTextDatum(nodeToString(bound));
+
+	memset(nulls, 0, sizeof(nulls));
+
+	tuple = heap_form_tuple(RelationGetDescr(partRelation), values, nulls);
+
+	CatalogTupleInsert(partRelation, tuple);
+
+	heap_freetuple(tuple);
+
+	heap_close(partRelation, RowExclusiveLock);
+
+	/*
+	 * Store a dependency too
+	 */
+	parentobject.classId = RelationRelationId;
+	parentobject.objectId = partedrelid;
+	parentobject.objectSubId = 0;
+	childobject.classId = RelationRelationId;
+	childobject.objectId = attachrelid;
+	childobject.objectSubId = 0;
+
+	recordDependencyOn(&childobject, &parentobject, DEPENDENCY_AUTO);
+
+	/*
+	 * Post creation hook of this partition. Since object_access_hook
+	 * doesn't take multiple object identifiers, we relay oid of parent
+	 * relation using auxiliary_id argument.
+	 */
+	InvokeObjectPostAlterHookArg(PartitionRelationId,
+								 attachrelid, 0,
+								 partedrelid, false);
+}
+
 /*
  * CreateInheritance
  *		Catalog manipulation portion of creating inheritance between a child
@@ -11922,7 +12074,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 	ObjectAddress address;
 	Relation	parent_rel;
 
-	if (rel->rd_rel->relispartition)
+	if (OidIsValid(rel->rd_rel->relpartitionparent))
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of a partition")));
@@ -11967,7 +12119,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
  * coninhcount and conislocal for inherited constraints are adjusted in
  * exactly the same way.
  *
- * Common to ATExecDropInherit() and ATExecDetachPartition().
+ * Used in ATExecDropInherit()
  */
 static void
 RemoveInheritance(Relation child_rel, Relation parent_rel)
@@ -11979,28 +12131,16 @@ RemoveInheritance(Relation child_rel, Relation parent_rel)
 				constraintTuple;
 	List	   *connames;
 	bool		found;
-	bool		child_is_partition = false;
-
-	/* If parent_rel is a partitioned table, child_rel must be a partition */
-	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-		child_is_partition = true;
 
 	found = DeleteInheritsTuple(RelationGetRelid(child_rel),
 								RelationGetRelid(parent_rel));
 	if (!found)
 	{
-		if (child_is_partition)
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_TABLE),
-					 errmsg("relation \"%s\" is not a partition of relation \"%s\"",
-							RelationGetRelationName(child_rel),
-							RelationGetRelationName(parent_rel))));
-		else
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_TABLE),
-					 errmsg("relation \"%s\" is not a parent of relation \"%s\"",
-							RelationGetRelationName(parent_rel),
-							RelationGetRelationName(child_rel))));
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_TABLE),
+					errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+						RelationGetRelationName(parent_rel),
+						RelationGetRelationName(child_rel))));
 	}
 
 	/*
@@ -12119,7 +12259,7 @@ RemoveInheritance(Relation child_rel, Relation parent_rel)
 	drop_parent_dependency(RelationGetRelid(child_rel),
 						   RelationRelationId,
 						   RelationGetRelid(parent_rel),
-						   child_dependency_type(child_is_partition));
+						   child_dependency_type(false));
 
 	/*
 	 * Post alter hook of this inherits. Since object_access_hook doesn't take
@@ -14039,7 +14179,6 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 {
 	Relation	attachrel,
 				catalog;
-	List	   *attachrel_children;
 	List	   *partConstraint;
 	SysScanDesc scan;
 	ScanKeyData skey;
@@ -14072,7 +14211,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	ATSimplePermissions(attachrel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
 	/* A partition can only have one parent */
-	if (attachrel->rd_rel->relispartition)
+	if (OidIsValid(attachrel->rd_rel->relpartitionparent))
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is already a partition",
@@ -14084,8 +14223,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 				 errmsg("cannot attach a typed table as partition")));
 
 	/*
-	 * Table being attached should not already be part of inheritance; either
-	 * as a child table...
+	 * The table being attached should not be part of any inheritance
+	 * hierarchy as a child or as a parent.
 	 */
 	catalog = heap_open(InheritsRelationId, AccessShareLock);
 	ScanKeyInit(&skey,
@@ -14100,15 +14239,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 				 errmsg("cannot attach inheritance child as partition")));
 	systable_endscan(scan);
 
-	/* ...or as a parent table (except the case when it is partitioned) */
 	ScanKeyInit(&skey,
 				Anum_pg_inherits_inhparent,
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(RelationGetRelid(attachrel)));
 	scan = systable_beginscan(catalog, InheritsParentIndexId, true, NULL,
 							  1, &skey);
-	if (HeapTupleIsValid(systable_getnext(scan)) &&
-		attachrel->rd_rel->relkind == RELKIND_RELATION)
+	if (HeapTupleIsValid(systable_getnext(scan)))
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach inheritance parent as partition")));
@@ -14129,16 +14266,24 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * definition is present in all the partitions, we need not scan the
 	 * table, nor its partitions.  But we cannot risk a deadlock by taking a
 	 * weaker lock now and the stronger one only when needed.
+	 *
+	 * The the attachrel is a leaf partition, then it can have no partitions
+	 * so we needn't bother checking this.
 	 */
-	attachrel_children = find_all_inheritors(RelationGetRelid(attachrel),
-											 AccessExclusiveLock, NULL);
-	if (list_member_oid(attachrel_children, RelationGetRelid(rel)))
-		ereport(ERROR,
-				(errcode(ERRCODE_DUPLICATE_TABLE),
-				 errmsg("circular inheritance not allowed"),
-				 errdetail("\"%s\" is already a child of \"%s\".",
-						   RelationGetRelationName(rel),
-						   RelationGetRelationName(attachrel))));
+	if (attachrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		List	   *attachrel_children;
+
+		attachrel_children = get_partition_descendants(RelationGetRelid(attachrel),
+													   AccessExclusiveLock);
+		if (list_member_oid(attachrel_children, RelationGetRelid(rel)))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_TABLE),
+					 errmsg("circular partitioning is not allowed"),
+					 errdetail("\"%s\" is already a child of \"%s\".",
+							   RelationGetRelationName(rel),
+							   RelationGetRelationName(attachrel))));
+	}
 
 	/* If the parent is permanent, so must be all of its partitions. */
 	if (rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
@@ -14223,8 +14368,11 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 						trigger_name, RelationGetRelationName(attachrel)),
 				 errdetail("ROW triggers with transition tables are not supported on partitions")));
 
-	/* OK to create inheritance.  Rest of the checks performed there */
-	CreateInheritance(attachrel, rel);
+	/* Match up the columns and bump attinhcount as needed */
+	MergeAttributesIntoExisting(attachrel, rel);
+
+	/* Match up the constraints and bump coninhcount as needed */
+	MergeConstraintsIntoExisting(attachrel, rel);
 
 	/*
 	 * Check that the new partition's bound is valid and does not overlap any
@@ -14234,8 +14382,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	check_new_partition_bound(RelationGetRelationName(attachrel), rel,
 							  cmd->bound);
 
+	AttachPartition(attachrel, rel, cmd->bound);
+
 	/* Update the pg_class entry. */
-	StorePartitionBound(attachrel, rel, cmd->bound);
+	MarkRelationPartitioned(attachrel, rel, cmd->bound->is_default);
 
 	/* Ensure there exists a correct set of indexes in the partition. */
 	AttachPartitionEnsureIndexes(rel, attachrel);
@@ -14427,7 +14577,7 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 			Oid			cldConstrOid = InvalidOid;
 
 			/* does this index have a parent?  if so, can't use it */
-			if (attachrelIdxRels[i]->rd_rel->relispartition)
+			if (OidIsValid(attachrelIdxRels[i]->rd_rel->relpartitionparent))
 				continue;
 
 			if (CompareIndexInfo(attachInfos[i], info,
@@ -14458,7 +14608,7 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 				IndexSetParentIndex(attachrelIdxRels[i], idx);
 				if (OidIsValid(constraintOid))
 					ConstraintSetParentConstraint(cldConstrOid, constraintOid);
-				update_relispartition(NULL, cldIdxId, true);
+				update_relpartitionparent(NULL, cldIdxId, idx);
 				found = true;
 				break;
 			}
@@ -14628,18 +14778,20 @@ static ObjectAddress
 ATExecDetachPartition(Relation rel, RangeVar *name)
 {
 	Relation	partRel,
-				classRel;
-	HeapTuple	tuple,
-				newtuple;
-	Datum		new_val[Natts_pg_class];
-	bool		isnull,
-				new_null[Natts_pg_class],
-				new_repl[Natts_pg_class];
+				pgclass,
+				pgattr,
+				pgpart;
+	HeapTuple	tuple;
+	HeapTuple	parttup;
+	bool		isnull;
+	SysScanDesc scan;
+	ScanKeyData key[3];
 	ObjectAddress address;
 	Oid			defaultPartOid;
 	List	   *indexes;
 	ListCell   *cell;
 
+
 	/*
 	 * We must lock the default partition, because detaching this partition
 	 * will change its partition constraint.
@@ -14651,35 +14803,85 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
 	partRel = heap_openrv(name, AccessShareLock);
 
-	/* All inheritance related checks are performed within the function */
-	RemoveInheritance(partRel, rel);
-
 	/* Update pg_class tuple */
-	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	pgclass = heap_open(RelationRelationId, RowExclusiveLock);
 	tuple = SearchSysCacheCopy1(RELOID,
 								ObjectIdGetDatum(RelationGetRelid(partRel)));
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "cache lookup failed for relation %u",
 			 RelationGetRelid(partRel));
-	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
 
-	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+	if (!OidIsValid(((Form_pg_class) GETSTRUCT(tuple))->relpartitionparent))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_TABLE),
+					errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+						RelationGetRelationName(partRel),
+						RelationGetRelationName(rel))));
+
+	pgpart = heap_open(PartitionRelationId, RowExclusiveLock);
+
+	parttup = SearchSysCacheCopy1(PARTSRELID,
+								  ObjectIdGetDatum(RelationGetRelid(partRel)));
+	if (!HeapTupleIsValid(parttup))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(partRel));
+	/* XXX or use relpartitionparent to check? */
+	if (((Form_pg_partition) GETSTRUCT(parttup))->parentrelid != RelationGetRelid(rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_TABLE),
+					errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+						RelationGetRelationName(partRel),
+						RelationGetRelationName(rel))));
+
+	(void) SysCacheGetAttr(PARTSRELID, parttup, Anum_pg_partition_partbound,
 						   &isnull);
 	Assert(!isnull);
 
-	/* Clear relpartbound and reset relispartition */
-	memset(new_val, 0, sizeof(new_val));
-	memset(new_null, false, sizeof(new_null));
-	memset(new_repl, false, sizeof(new_repl));
-	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
-	new_null[Anum_pg_class_relpartbound - 1] = true;
-	new_repl[Anum_pg_class_relpartbound - 1] = true;
-	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
-								 new_val, new_null, new_repl);
-
-	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
-	CatalogTupleUpdate(classRel, &newtuple->t_self, newtuple);
-	heap_freetuple(newtuple);
+	/* unset relpartitionparent */
+	((Form_pg_class) GETSTRUCT(tuple))->relpartitionparent = InvalidOid;
+	CatalogTupleUpdate(pgclass, &tuple->t_self, tuple);
+	heap_freetuple(tuple);
+
+	CatalogTupleDelete(pgpart, &parttup->t_self);
+	heap_freetuple(parttup);
+
+	/*
+	 * Search through child columns looking for ones matching parent partition
+	 */
+	pgattr = heap_open(AttributeRelationId, RowExclusiveLock);
+	ScanKeyInit(&key[0],
+				Anum_pg_attribute_attrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(partRel)));
+	scan = systable_beginscan(pgattr, AttributeRelidNumIndexId,
+							  true, NULL, 1, key);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		/* Ignore if dropped or not inherited */
+		if (att->attisdropped)
+			continue;
+		if (att->attinhcount <= 0)
+			continue;
+
+		if (SearchSysCacheExistsAttName(RelationGetRelid(rel),
+										NameStr(att->attname)))
+		{
+			/* Decrement inhcount and possibly set islocal to true */
+			HeapTuple	copyTuple = heap_copytuple(tuple);
+			Form_pg_attribute copy_att = (Form_pg_attribute) GETSTRUCT(copyTuple);
+
+			copy_att->attinhcount--;
+			if (copy_att->attinhcount == 0)
+				copy_att->attislocal = true;
+
+			CatalogTupleUpdate(pgattr, &copyTuple->t_self, copyTuple);
+			heap_freetuple(copyTuple);
+		}
+	}
+	systable_endscan(scan);
+	heap_close(pgattr, RowExclusiveLock);
 
 	if (OidIsValid(defaultPartOid))
 	{
@@ -14712,10 +14914,11 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
 		idx = index_open(idxid, AccessExclusiveLock);
 		IndexSetParentIndex(idx, InvalidOid);
-		update_relispartition(classRel, idxid, false);
+		update_relpartitionparent(pgclass, idxid, InvalidOid);
 		relation_close(idx, AccessExclusiveLock);
 	}
-	heap_close(classRel, RowExclusiveLock);
+	heap_close(pgclass, RowExclusiveLock);
+	heap_close(pgpart, RowExclusiveLock);
 
 	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
@@ -14728,6 +14931,20 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	/* keep our lock until commit */
 	heap_close(partRel, NoLock);
 
+	drop_parent_dependency(RelationGetRelid(partRel),
+						   RelationRelationId,
+						   RelationGetRelid(rel),
+						   child_dependency_type(true));
+
+	/*
+	 * Post alter hook of this inherits. Since object_access_hook doesn't take
+	 * multiple object identifiers, we relay oid of parent relation using
+	 * auxiliary_id argument.
+	 */
+	InvokeObjectPostAlterHookArg(PartitionRelationId,
+								 RelationGetRelid(partRel), 0,
+								 RelationGetRelid(rel), false);
+
 	return address;
 }
 
@@ -14837,8 +15054,7 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partIdx));
 
 	/* Silently do nothing if already in the right state */
-	currParent = partIdx->rd_rel->relispartition ?
-		get_partition_parent(partIdxId) : InvalidOid;
+	currParent = partIdx->rd_rel->relpartitionparent;
 	if (currParent != RelationGetRelid(parentIdx))
 	{
 		IndexInfo  *childInfo;
@@ -14932,7 +15148,7 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		IndexSetParentIndex(partIdx, RelationGetRelid(parentIdx));
 		if (OidIsValid(constraintOid))
 			ConstraintSetParentConstraint(cldConstrId, constraintOid);
-		update_relispartition(NULL, partIdxId, true);
+		update_relpartitionparent(NULL, partIdxId, RelationGetRelid(parentIdx));
 
 		pfree(attmap);
 
@@ -15060,7 +15276,7 @@ validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
 	 * If this index is in turn a partition of a larger index, validating it
 	 * might cause the parent to become valid also.  Try that.
 	 */
-	if (updated && partedIdx->rd_rel->relispartition)
+	if (updated && OidIsValid(RelationGetParentRelid(partedIdx)))
 	{
 		Oid			parentIdxId,
 					parentTblId;
@@ -15070,8 +15286,8 @@ validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
 		/* make sure we see the validation we just did */
 		CommandCounterIncrement();
 
-		parentIdxId = get_partition_parent(RelationGetRelid(partedIdx));
-		parentTblId = get_partition_parent(RelationGetRelid(partedTbl));
+		parentIdxId = RelationGetParentRelid(partedIdx);
+		parentTblId = RelationGetParentRelid(partedTbl);
 		parentIdx = relation_open(parentIdxId, AccessExclusiveLock);
 		parentTbl = relation_open(parentTblId, AccessExclusiveLock);
 		Assert(!parentIdx->rd_index->indisvalid);
@@ -15084,13 +15300,14 @@ validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
 }
 
 /*
- * Update the relispartition flag of the given relation to the given value.
+ * Update the relpartitionparent value of the given relation to the given
+ * value.
  *
  * classRel is the pg_class relation, already open and suitably locked.
  * It can be passed as NULL, in which case it's opened and closed locally.
  */
 static void
-update_relispartition(Relation classRel, Oid relationId, bool newval)
+update_relpartitionparent(Relation classRel, Oid relationId, Oid newparent)
 {
 	HeapTuple	tup;
 	HeapTuple	newtup;
@@ -15106,7 +15323,7 @@ update_relispartition(Relation classRel, Oid relationId, bool newval)
 	tup = SearchSysCache1(RELOID, ObjectIdGetDatum(relationId));
 	newtup = heap_copytuple(tup);
 	classForm = (Form_pg_class) GETSTRUCT(newtup);
-	classForm->relispartition = newval;
+	classForm->relpartitionparent = newparent;
 	CatalogTupleUpdate(classRel, &tup->t_self, newtup);
 	heap_freetuple(newtup);
 	ReleaseSysCache(tup);
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 2436692eb8..8c712b97cc 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -363,8 +363,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	partition_recurse = !isInternal && stmt->row &&
 		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 	if (partition_recurse)
-		list_free(find_all_inheritors(RelationGetRelid(rel),
-									  ShareRowExclusiveLock, NULL));
+		list_free(get_partition_descendants(RelationGetRelid(rel),
+											ShareRowExclusiveLock));
 
 	/* Compute tgtype */
 	TRIGGER_CLEAR_TYPE(tgtype);
@@ -453,14 +453,14 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 			 * tuples each child should see.  See also the prohibitions in
 			 * ATExecAttachPartition() and ATExecAddInherit().
 			 */
-			if (TRIGGER_FOR_ROW(tgtype) && has_superclass(rel->rd_id))
+			if (TRIGGER_FOR_ROW(tgtype))
 			{
 				/* Use appropriate error message. */
-				if (rel->rd_rel->relispartition)
+				if (OidIsValid(rel->rd_rel->relpartitionparent))
 					ereport(ERROR,
 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 							 errmsg("ROW triggers with transition tables are not supported on partitions")));
-				else
+				else if (has_superclass(rel->rd_id))
 					ereport(ERROR,
 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 							 errmsg("ROW triggers with transition tables are not supported on inheritance children")));
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index ee32fe8871..6ec8cee33f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,6 +31,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
@@ -485,7 +486,7 @@ expand_vacuum_rel(VacuumRelation *vrel)
 		 */
 		if (include_parts)
 		{
-			List	   *part_oids = find_all_inheritors(relid, NoLock, NULL);
+			List	   *part_oids = get_partition_descendants(relid, NoLock);
 			ListCell   *part_lc;
 
 			foreach(part_lc, part_oids)
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 7849e04bdb..448526d9f2 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -14,7 +14,6 @@
 #include "postgres.h"
 
 #include "catalog/partition.h"
-#include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -80,7 +79,7 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
 	ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
 
 	/* Lock all the partitions. */
-	(void) find_all_inheritors(RelationGetRelid(rel), RowExclusiveLock, NULL);
+	(void) get_partition_descendants(RelationGetRelid(rel), RowExclusiveLock);
 
 	/*
 	 * Here we attempt to expend as little effort as possible in setting up
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 690b6bbab7..3896617760 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -99,9 +99,11 @@ static List *generate_append_tlist(List *colTypes, List *colCollations,
 					  List *input_tlists,
 					  List *refnames_tlist);
 static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist);
+static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *rte,
+						   Index rti);
 static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte,
 						 Index rti);
-static void expand_partitioned_rtentry(PlannerInfo *root,
+static void expand_partitioned_rtentry_recurse(PlannerInfo *root,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode,
@@ -1487,7 +1489,20 @@ expand_inherited_tables(PlannerInfo *root)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl);
 
-		expand_inherited_rtentry(root, rte, rti);
+		if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+			expand_partitioned_rtentry(root, rte, rti);
+		else if (OidIsValid(rte->relid))
+		{
+			/*
+			 * Partitioned tables cannot be part of an inheritance hierarchy,
+			 * so we needn't bother trying to expand those.
+			 */
+			if (OidIsValid(get_partition_parent(rte->relid)))
+				rte->inh = false;
+			else
+				expand_inherited_rtentry(root, rte, rti);
+		}
+		/* no need to attempt to expand anything else */
 		rl = lnext(rl);
 	}
 }
@@ -1522,6 +1537,11 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	LOCKMODE	lockmode;
 	List	   *inhOIDs;
 	ListCell   *l;
+	List	   *appinfos = NIL;
+	RangeTblEntry *childrte;
+	Index		childRTindex;
+
+	Assert(rte->relkind != RELKIND_PARTITIONED_TABLE);
 
 	/* Does RT entry allow inheritance? */
 	if (!rte->inh)
@@ -1534,7 +1554,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	}
 	/* Fast path for common case of childless table */
 	parentOID = rte->relid;
-	if (!has_subclass(parentOID))
+	if (rte->relkind != RELKIND_PARTITIONED_TABLE && !has_subclass(parentOID))
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
@@ -1591,93 +1611,137 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	 */
 	oldrelation = heap_open(parentOID, NoLock);
 
-	/* Scan the inheritance set and expand it */
-	if (RelationGetPartitionDesc(oldrelation) != NULL)
+	Assert(!RelationGetPartitionDesc(oldrelation));
+
+	/*
+		* This table has no partitions.  Expand any plain inheritance
+		* children in the order the OIDs were returned by
+		* find_all_inheritors.
+		*/
+	foreach(l, inhOIDs)
 	{
-		Assert(rte->relkind == RELKIND_PARTITIONED_TABLE);
+		Oid			childOID = lfirst_oid(l);
+		Relation	newrelation;
+
+		/* Open rel if needed; we already have required locks */
+		if (childOID != parentOID)
+			newrelation = heap_open(childOID, NoLock);
+		else
+			newrelation = oldrelation;
 
 		/*
-		 * If this table has partitions, recursively expand them in the order
-		 * in which they appear in the PartitionDesc.  While at it, also
-		 * extract the partition key columns of all the partitioned tables.
-		 */
-		expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc,
-								   lockmode, &root->append_rel_list);
+			* It is possible that the parent table has children that are temp
+			* tables of other backends.  We cannot safely access such tables
+			* (because of buffering issues), and the best thing to do seems
+			* to be to silently ignore them.
+			*/
+		if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation))
+		{
+			heap_close(newrelation, lockmode);
+			continue;
+		}
+
+		expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc,
+										newrelation,
+										&appinfos, &childrte,
+										&childRTindex);
+
+		/* Close child relations, but keep locks */
+		if (childOID != parentOID)
+			heap_close(newrelation, NoLock);
 	}
+
+	/*
+		* If all the children were temp tables, pretend it's a
+		* non-inheritance situation; we don't need Append node in that case.
+		* The duplicate RTE we added for the parent table is harmless, so we
+		* don't bother to get rid of it; ditto for the useless PlanRowMark
+		* node.
+		*/
+	if (list_length(appinfos) < 2)
+		rte->inh = false;
 	else
-	{
-		List	   *appinfos = NIL;
-		RangeTblEntry *childrte;
-		Index		childRTindex;
+		root->append_rel_list = list_concat(root->append_rel_list,
+											appinfos);
 
-		/*
-		 * This table has no partitions.  Expand any plain inheritance
-		 * children in the order the OIDs were returned by
-		 * find_all_inheritors.
-		 */
-		foreach(l, inhOIDs)
-		{
-			Oid			childOID = lfirst_oid(l);
-			Relation	newrelation;
+	heap_close(oldrelation, NoLock);
+}
 
-			/* Open rel if needed; we already have required locks */
-			if (childOID != parentOID)
-				newrelation = heap_open(childOID, NoLock);
-			else
-				newrelation = oldrelation;
+static void
+expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
+{
+	Query	   *parse = root->parse;
+	PlanRowMark *partrc;
+	Relation	partrel;
+	LOCKMODE	lockmode;
+	PartitionDesc partdesc;
 
-			/*
-			 * It is possible that the parent table has children that are temp
-			 * tables of other backends.  We cannot safely access such tables
-			 * (because of buffering issues), and the best thing to do seems
-			 * to be to silently ignore them.
-			 */
-			if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation))
-			{
-				heap_close(newrelation, lockmode);
-				continue;
-			}
+	Assert(rte->relkind == RELKIND_PARTITIONED_TABLE);
+
+	/* Support legacy ONLY syntax for partitions */
+	if (!rte->inh)
+		return;
 
-			expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc,
-											newrelation,
-											&appinfos, &childrte,
-											&childRTindex);
+	/*
+	 * The rewriter should already have obtained an appropriate lock on each
+	 * relation named in the query.  However, for each child relation we add
+	 * to the query, we must obtain an appropriate lock, because this will be
+	 * the first use of those relations in the parse/rewrite/plan pipeline.
+	 *
+	 * If the parent relation is the query's result relation, then we need
+	 * RowExclusiveLock.  Otherwise, if it's accessed FOR UPDATE/SHARE, we
+	 * need RowShareLock; otherwise AccessShareLock.  We can't just grab
+	 * AccessShareLock because then the executor would be trying to upgrade
+	 * the lock, leading to possible deadlocks.  (This code should match the
+	 * parser and rewriter.)
+	 */
+	partrc = get_plan_rowmark(root->rowMarks, rti);
+	if (rti == parse->resultRelation)
+		lockmode = RowExclusiveLock;
+	else if (partrc && RowMarkRequiresRowShareLock(partrc->markType))
+		lockmode = RowShareLock;
+	else
+		lockmode = AccessShareLock;
 
-			/* Close child relations, but keep locks */
-			if (childOID != parentOID)
-				heap_close(newrelation, NoLock);
-		}
+	/*
+	 * If parent relation is selected FOR UPDATE/SHARE, we need to mark its
+	 * PlanRowMark as isParent = true, and generate a new PlanRowMark for each
+	 * child.
+	 */
+	if (partrc)
+		partrc->isParent = true;
 
-		/*
-		 * If all the children were temp tables, pretend it's a
-		 * non-inheritance situation; we don't need Append node in that case.
-		 * The duplicate RTE we added for the parent table is harmless, so we
-		 * don't bother to get rid of it; ditto for the useless PlanRowMark
-		 * node.
-		 */
-		if (list_length(appinfos) < 2)
-			rte->inh = false;
-		else
-			root->append_rel_list = list_concat(root->append_rel_list,
-												appinfos);
+	/*
+	 * Must open the partitioned relation to examine its tupdesc.  We need not
+	 * lock it; we assume the rewriter already did.
+	 */
+	partrel = heap_open(rte->relid, NoLock);
 
-	}
+	partdesc = RelationGetPartitionDesc(partrel);
 
-	heap_close(oldrelation, NoLock);
+	/*
+	 * If this table has partitions, recursively expand them in the order in
+	 * which they appear in the PartitionDesc.  While at it, also extract the
+	 * partition key columns of all the partitioned tables.
+	 */
+	expand_partitioned_rtentry_recurse(root, rte, rti, partrel, partrc,
+									   lockmode, &root->append_rel_list);
+
+	heap_close(partrel, NoLock);
 }
 
 /*
- * expand_partitioned_rtentry
+ * expand_partitioned_rtentry_recurse
  *		Recursively expand an RTE for a partitioned table.
  *
  * Note that RelationGetPartitionDispatchInfo will expand partitions in the
  * same order as this code.
  */
 static void
-expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte,
-						   Index parentRTindex, Relation parentrel,
-						   PlanRowMark *top_parentrc, LOCKMODE lockmode,
-						   List **appinfos)
+expand_partitioned_rtentry_recurse(PlannerInfo *root, RangeTblEntry *parentrte,
+								   Index parentRTindex, Relation parentrel,
+								   PlanRowMark *top_parentrc, LOCKMODE lockmode,
+								   List **appinfos)
 {
 	int			i;
 	RangeTblEntry *childrte;
@@ -1689,8 +1753,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned table should always have a partition descriptor. */
 	Assert(partdesc);
 
-	Assert(parentrte->inh);
-
 	/*
 	 * Note down whether any partition key cols are being updated. Though it's
 	 * the root partitioned table's updatedCols we are interested in, we
@@ -1722,16 +1784,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte,
 		Oid			childOID = partdesc->oids[i];
 		Relation	childrel;
 
-		/* Open rel; we already have required locks */
-		childrel = heap_open(childOID, NoLock);
-
-		/*
-		 * Temporary partitions belonging to other sessions should have been
-		 * disallowed at definition, but for paranoia's sake, let's double
-		 * check.
-		 */
-		if (RELATION_IS_OTHER_TEMP(childrel))
-			elog(ERROR, "temporary relation from another session found as partition");
+		childrel = heap_open(childOID, lockmode);
 
 		expand_single_inheritance_child(root, parentrte, parentRTindex,
 										parentrel, top_parentrc, childrel,
@@ -1739,7 +1792,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-			expand_partitioned_rtentry(root, childrte, childRTindex,
+			expand_partitioned_rtentry_recurse(root, childrte, childRTindex,
 									   childrel, top_parentrc, lockmode,
 									   appinfos);
 
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index 9015a05d32..360d4e08cd 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -15,6 +15,7 @@
 
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
+#include "catalog/pg_partition.h"
 #include "catalog/pg_type.h"
 #include "commands/tablecmds.h"
 #include "executor/executor.h"
@@ -628,8 +629,8 @@ check_default_partition_contents(Relation parent, Relation default_rel,
 	 * that do not satisfy the revised partition constraints.
 	 */
 	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
-										AccessExclusiveLock, NULL);
+		all_parts = get_partition_descendants(RelationGetRelid(default_rel),
+											  AccessExclusiveLock);
 	else
 		all_parts = list_make1_oid(RelationGetRelid(default_rel));
 
@@ -1634,12 +1635,12 @@ get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
 			bool		isnull;
 			PartitionBoundSpec *bspec;
 
-			tuple = SearchSysCache1(RELOID, inhrelid);
+			tuple = SearchSysCache1(PARTSRELID, inhrelid);
 			if (!HeapTupleIsValid(tuple))
 				elog(ERROR, "cache lookup failed for relation %u", inhrelid);
 
-			datum = SysCacheGetAttr(RELOID, tuple,
-									Anum_pg_class_relpartbound,
+			datum = SysCacheGetAttr(PARTSRELID, tuple,
+									Anum_pg_partition_partbound,
 									&isnull);
 
 			Assert(!isnull);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index d81a2ea342..3c5e8e75d6 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -428,7 +428,7 @@ DefineQueryRewrite(const char *rulename,
 						 errmsg("cannot convert partitioned table \"%s\" to a view",
 								RelationGetRelationName(event_relation))));
 
-			if (event_relation->rd_rel->relispartition)
+			if (OidIsValid(event_relation->rd_rel->relpartitionparent))
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("cannot convert partition \"%s\" to a view",
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index b5804f64ad..51e0e789f3 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -25,6 +25,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/toasting.h"
+#include "catalog/partition.h"
 #include "commands/alter.h"
 #include "commands/async.h"
 #include "commands/cluster.h"
@@ -1323,10 +1324,10 @@ ProcessUtilitySlow(ParseState *pstate,
 						get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE)
 					{
 						ListCell   *lc;
-						List	   *inheritors = NIL;
+						List	   *partoids = NIL;
 
-						inheritors = find_all_inheritors(relid, lockmode, NULL);
-						foreach(lc, inheritors)
+						partoids = get_partition_descendants(relid, lockmode);
+						foreach(lc, partoids)
 						{
 							char		relkind = get_rel_relkind(lfirst_oid(lc));
 
@@ -1340,7 +1341,7 @@ ProcessUtilitySlow(ParseState *pstate,
 										 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 												   stmt->relation->relname)));
 						}
-						list_free(inheritors);
+						list_free(partoids);
 					}
 
 					/* Run parse analysis ... */
diff --git a/src/backend/utils/cache/partcache.c b/src/backend/utils/cache/partcache.c
index 82acfeb460..51a21c4793 100644
--- a/src/backend/utils/cache/partcache.c
+++ b/src/backend/utils/cache/partcache.c
@@ -18,9 +18,10 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/nbtree.h"
+#include "catalog/indexing.h"
 #include "catalog/partition.h"
-#include "catalog/pg_inherits.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partition.h"
 #include "catalog/pg_partitioned_table.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -30,6 +31,7 @@
 #include "partitioning/partbounds.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/partcache.h"
@@ -260,8 +262,7 @@ RelationBuildPartitionKey(Relation relation)
 void
 RelationBuildPartitionDesc(Relation rel)
 {
-	List	   *inhoids,
-			   *partoids;
+	List	   *partoids;
 	Oid		   *oids = NULL;
 	List	   *boundspecs = NIL;
 	ListCell   *cell;
@@ -270,6 +271,10 @@ RelationBuildPartitionDesc(Relation rel)
 	PartitionKey key = RelationGetPartitionKey(rel);
 	PartitionDesc result;
 	MemoryContext oldcxt;
+	SysScanDesc scan;
+	ScanKeyData scankey[1];
+	Relation	pgpart;
+	HeapTuple	partTuple;
 
 	int			ndatums = 0;
 	int			default_index = -1;
@@ -285,39 +290,47 @@ RelationBuildPartitionDesc(Relation rel)
 	PartitionRangeBound **rbounds = NULL;
 
 	/* Get partition oids from pg_inherits */
-	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+	pgpart = heap_open(PartitionRelationId, AccessShareLock);
+
+	ScanKeyInit(&scankey[0],
+				Anum_pg_partition_parentrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(rel)));
+
+	scan = systable_beginscan(pgpart, PartitionParentrelidIndexId, true,
+							  NULL, 1, scankey);
 
 	/* Collect bound spec nodes in a list */
-	i = 0;
 	partoids = NIL;
-	foreach(cell, inhoids)
+	while ((partTuple = systable_getnext(scan)) != NULL)
 	{
-		Oid			inhrelid = lfirst_oid(cell);
+		Oid			partrelid = ((Form_pg_partition) GETSTRUCT(partTuple))->partrelid;
 		HeapTuple	tuple;
 		Datum		datum;
 		bool		isnull;
 		Node	   *boundspec;
 
-		tuple = SearchSysCache1(RELOID, inhrelid);
+		tuple = SearchSysCache1(RELOID, partrelid);
 		if (!HeapTupleIsValid(tuple))
-			elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+			elog(ERROR, "cache lookup failed for relation %u", partrelid);
 
 		/*
 		 * It is possible that the pg_class tuple of a partition has not been
 		 * updated yet to set its relpartbound field.  The only case where
 		 * this happens is when we open the parent relation to check using its
 		 * partition descriptor that a new partition's bound does not overlap
-		 * some existing partition.
+		 * some existing partition. XXX needs updated! Figure out if this is
+		 * still required.
 		 */
-		if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+		if (!OidIsValid(((Form_pg_class) GETSTRUCT(tuple))->relpartitionparent))
 		{
 			ReleaseSysCache(tuple);
 			continue;
 		}
 
-		datum = SysCacheGetAttr(RELOID, tuple,
-								Anum_pg_class_relpartbound,
-								&isnull);
+		datum = fastgetattr(partTuple, Anum_pg_partition_partbound,
+							pgpart->rd_att, &isnull);
+
 		Assert(!isnull);
 		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
 
@@ -331,16 +344,20 @@ RelationBuildPartitionDesc(Relation rel)
 			Oid			partdefid;
 
 			partdefid = get_default_partition_oid(RelationGetRelid(rel));
-			if (partdefid != inhrelid)
+			if (partdefid != partrelid)
 				elog(ERROR, "expected partdefid %u, but got %u",
-					 inhrelid, partdefid);
+					 partrelid, partdefid);
 		}
 
 		boundspecs = lappend(boundspecs, boundspec);
-		partoids = lappend_oid(partoids, inhrelid);
+		partoids = lappend_oid(partoids, partrelid);
 		ReleaseSysCache(tuple);
 	}
 
+	systable_endscan(scan);
+
+	heap_close(pgpart, AccessShareLock);
+
 	nparts = list_length(partoids);
 
 	if (nparts > 0)
@@ -808,7 +825,7 @@ List *
 RelationGetPartitionQual(Relation rel)
 {
 	/* Quick exit */
-	if (!rel->rd_rel->relispartition)
+	if (!OidIsValid(rel->rd_rel->relpartitionparent))
 		return NIL;
 
 	return generate_partition_qual(rel);
@@ -829,7 +846,7 @@ get_partition_qual_relid(Oid relid)
 	List	   *and_args;
 
 	/* Do the work only if this relation is a partition. */
-	if (rel->rd_rel->relispartition)
+	if (OidIsValid(rel->rd_rel->relpartitionparent))
 	{
 		and_args = generate_partition_qual(rel);
 
@@ -880,20 +897,19 @@ generate_partition_qual(Relation rel)
 		return copyObject(rel->rd_partcheck);
 
 	/* Grab at least an AccessShareLock on the parent table */
-	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
-					   AccessShareLock);
+	parent = heap_open(RelationGetParentRelid(rel), AccessShareLock);
 
-	/* Get pg_class.relpartbound */
-	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	/* Get pg_partition.partbound */
+	tuple = SearchSysCache1(PARTSRELID, RelationGetRelid(rel));
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "cache lookup failed for relation %u",
 			 RelationGetRelid(rel));
 
-	boundDatum = SysCacheGetAttr(RELOID, tuple,
-								 Anum_pg_class_relpartbound,
+	boundDatum = SysCacheGetAttr(PARTSRELID, tuple,
+								 Anum_pg_partition_partbound,
 								 &isnull);
 	if (isnull)					/* should not happen */
-		elog(ERROR, "relation \"%s\" has relpartbound = null",
+		elog(ERROR, "relation \"%s\" has partbound = null",
 			 RelationGetRelationName(rel));
 	bound = castNode(PartitionBoundSpec,
 					 stringToNode(TextDatumGetCString(boundDatum)));
@@ -902,7 +918,7 @@ generate_partition_qual(Relation rel)
 	my_qual = get_qual_from_partbound(rel, parent, bound);
 
 	/* Add the parent's quals to the list (if any) */
-	if (parent->rd_rel->relispartition)
+	if (OidIsValid(parent->rd_rel->relpartitionparent))
 		result = list_concat(generate_partition_qual(parent), my_qual);
 	else
 		result = my_qual;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 2b381782a3..8ad9d2fa3b 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partition.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_publication.h"
@@ -584,6 +585,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		32
 	},
+	{PartitionRelationId,		/* PARTSRELID */
+		PartitionRelidIndexId,
+		1,
+		{
+			Anum_pg_partition_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
@@ -1238,7 +1250,7 @@ GetSysCacheOid(int cacheId,
 
 /*
  * SearchSysCacheAttName
- *
+ *subqu
  * This routine is equivalent to SearchSysCache on the ATTNAME cache,
  * except that it will return NULL if the found attribute is marked
  * attisdropped.  This is convenient for callers that want to act as
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 0d147cb08d..4f6c68e844 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -1022,6 +1022,28 @@ findParentsByOid(TableInfo *self,
 				j;
 	int			numParents;
 
+	/*
+	 * For PG12 and above pg_class has a relpartitionparent column that allows
+	 * us to determine the parent without looking at the inheritance data.
+	 */
+	if (self->partitionparent != 0)
+	{
+		self->parents = (TableInfo **) pg_malloc(sizeof(TableInfo *));
+
+		self->parents[0] = findTableByOid(self->partitionparent);
+
+		if (self->parents[0] == NULL)
+		{
+			write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						self->partitionparent,
+						self->dobj.name,
+						oid);
+			exit_nicely(1);
+		}
+		self->numParents = 1;
+		return;
+	}
+
 	numParents = 0;
 	for (i = 0; i < numInherits; i++)
 	{
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 20e8aedb19..af7e2bd813 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5878,6 +5878,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_changed_acl;
 	int			i_partkeydef;
 	int			i_ispartition;
+	int			i_partitionparent;
 	int			i_partbound;
 
 	/*
@@ -5904,7 +5905,9 @@ getTables(Archive *fout, int *numTables)
 	{
 		char	   *partkeydef = "NULL";
 		char	   *ispartition = "false";
+		char	   *partitionparent = "0";
 		char	   *partbound = "NULL";
+		char	   *partjoin = "";
 
 		PQExpBuffer acl_subquery = createPQExpBuffer();
 		PQExpBuffer racl_subquery = createPQExpBuffer();
@@ -5920,14 +5923,23 @@ getTables(Archive *fout, int *numTables)
 		 * Collect the information about any partitioned tables, which were
 		 * added in PG10.
 		 */
-
-		if (fout->remoteVersion >= 100000)
+		/* XXX how much do we add here before we split this out completely? */
+		if (fout->remoteVersion >= 120000)
+		{
+			partjoin = "LEFT JOIN pg_partition p ON (c.oid = p.partrelid) ";
+			partkeydef = "pg_get_partkeydef(c.oid)";
+			ispartition = "c.relpartitionparent <> 0";
+			partitionparent = "c.relpartitionparent";
+			partbound = "pg_get_expr(p.partbound, c.oid)";
+		}
+		else if (fout->remoteVersion >= 100000)
 		{
 			partkeydef = "pg_get_partkeydef(c.oid)";
 			ispartition = "c.relispartition";
 			partbound = "pg_get_expr(c.relpartbound, c.oid)";
 		}
 
+
 		/*
 		 * Left join to pick up dependency info linking sequences to their
 		 * owning column, if any (note this dependency is AUTO as of 8.2)
@@ -5982,6 +5994,7 @@ getTables(Archive *fout, int *numTables)
 						  "AS changed_acl, "
 						  "%s AS partkeydef, "
 						  "%s AS ispartition, "
+						  "%s AS partitionparent, "
 						  "%s AS partbound "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
@@ -5994,6 +6007,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
+						  "%s"
 						  "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
@@ -6008,8 +6022,10 @@ getTables(Archive *fout, int *numTables)
 						  attinitracl_subquery->data,
 						  partkeydef,
 						  ispartition,
+						  partitionparent,
 						  partbound,
 						  RELKIND_SEQUENCE,
+						  partjoin,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
 						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
@@ -6057,6 +6073,7 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
+						  "0 AS partitionparent, "
 						  "NULL AS partbound "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
@@ -6106,6 +6123,7 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
+						  "0 AS partitionparent, "
 						  "NULL AS partbound "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
@@ -6155,6 +6173,7 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
+						  "0 AS partitionparent, "
 						  "NULL AS partbound "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
@@ -6202,6 +6221,7 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
+						  "0 AS partitionparent, "
 						  "NULL AS partbound "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
@@ -6249,6 +6269,7 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
+						  "0 AS partitionparent, "
 						  "NULL AS partbound "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
@@ -6295,6 +6316,7 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
+						  "0 AS partitionparent, "
 						  "NULL AS partbound "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
@@ -6341,6 +6363,7 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
+						  "0 AS partitionparent, "
 						  "NULL AS partbound "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
@@ -6386,6 +6409,7 @@ getTables(Archive *fout, int *numTables)
 						  "NULL AS changed_acl, "
 						  "NULL AS partkeydef, "
 						  "false AS ispartition, "
+						  "0 AS partitionparent, "
 						  "NULL AS partbound "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
@@ -6455,6 +6479,7 @@ getTables(Archive *fout, int *numTables)
 	i_changed_acl = PQfnumber(res, "changed_acl");
 	i_partkeydef = PQfnumber(res, "partkeydef");
 	i_ispartition = PQfnumber(res, "ispartition");
+	i_partitionparent = PQfnumber(res, "partitionparent");
 	i_partbound = PQfnumber(res, "partbound");
 
 	if (dopt->lockWaitTimeout)
@@ -6561,6 +6586,7 @@ getTables(Archive *fout, int *numTables)
 		/* Partition key string or NULL */
 		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 		tblinfo[i].ispartition = (strcmp(PQgetvalue(res, i, i_ispartition), "t") == 0);
+		tblinfo[i].partitionparent = atooid(PQgetvalue(res, i, i_partitionparent));
 		tblinfo[i].partbound = pg_strdup(PQgetvalue(res, i, i_partbound));
 
 		/*
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 1448005f30..e5155f760e 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -296,6 +296,7 @@ typedef struct _tableInfo
 	bool		dummy_view;		/* view's real definition must be postponed */
 	bool		postponed_def;	/* matview must be postponed into post-data */
 	bool		ispartition;	/* is table a partition? */
+	Oid			partitionparent; /* owning partitioned table, or 0 */
 
 	/*
 	 * These fields are computed only if we decide the table is interesting
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 80d8338b96..7fde1114a0 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2044,18 +2044,36 @@ describeOneTableDetails(const char *schemaname,
 		char	   *partdef;
 		char	   *partconstraintdef = NULL;
 
-		printfPQExpBuffer(&buf,
-						  "SELECT inhparent::pg_catalog.regclass,\n"
-						  "  pg_catalog.pg_get_expr(c.relpartbound, inhrelid)");
-		/* If verbose, also request the partition constraint definition */
-		if (verbose)
+		if (pset.sversion >= 120000)
+		{
+			printfPQExpBuffer(&buf,
+							  "SELECT p.parentrelid::pg_catalog.regclass,\n"
+							  "  pg_catalog.pg_get_expr(p.partbound, p.partrelid)");
+			/* If verbose, also request the partition constraint definition */
+			if (verbose)
+				appendPQExpBuffer(&buf,
+								  ",\n  pg_catalog.pg_get_partition_constraintdef(p.partrelid)");
 			appendPQExpBuffer(&buf,
-							  ",\n  pg_catalog.pg_get_partition_constraintdef(inhrelid)");
-		appendPQExpBuffer(&buf,
-						  "\nFROM pg_catalog.pg_class c"
-						  " JOIN pg_catalog.pg_inherits i"
-						  " ON c.oid = inhrelid"
-						  "\nWHERE c.oid = '%s' AND c.relispartition;", oid);
+							  "\nFROM pg_catalog.pg_class c"
+							  " JOIN pg_catalog.pg_partition p"
+							  " ON c.oid = p.partrelid"
+							  "\nWHERE c.oid = '%s' AND c.relpartitionparent <> 0;", oid);
+		}
+		else
+		{
+			printfPQExpBuffer(&buf,
+							  "SELECT inhparent::pg_catalog.regclass,\n"
+							  "  pg_catalog.pg_get_expr(c.relpartbound, inhrelid)");
+			/* If verbose, also request the partition constraint definition */
+			if (verbose)
+				appendPQExpBuffer(&buf,
+								  ",\n  pg_catalog.pg_get_partition_constraintdef(inhrelid)");
+			appendPQExpBuffer(&buf,
+							  "\nFROM pg_catalog.pg_class c"
+							  " JOIN pg_catalog.pg_inherits i"
+							  " ON c.oid = inhrelid"
+							  "\nWHERE c.oid = '%s' AND c.relispartition;", oid);
+		}
 		result = PSQLexec(buf.data);
 		if (!result)
 			goto error_return;
@@ -2984,7 +3002,25 @@ describeOneTableDetails(const char *schemaname,
 		}
 
 		/* print child tables (with additional info if partitions) */
-		if (pset.sversion >= 100000)
+		if (pset.sversion >= 120000)
+		{
+			if (tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
+				printfPQExpBuffer(&buf,
+								  "SELECT c.oid::pg_catalog.regclass,"
+								  "       pg_catalog.pg_get_expr(p.partbound, p.partrelid),"
+								  "       c.relkind"
+								  " FROM pg_catalog.pg_class c, pg_catalog.pg_partition p"
+								  " WHERE c.oid=p.partrelid AND p.parentrelid = '%s'"
+								  " ORDER BY pg_catalog.pg_get_expr(p.partbound, p.partrelid) = 'DEFAULT',"
+								  "          c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+			else
+				printfPQExpBuffer(&buf,
+								  "SELECT c.oid::pg_catalog.regclass,NULL,c.relkind"
+								  " 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);
+		}
+		else if (pset.sversion >= 100000)
 			printfPQExpBuffer(&buf,
 							  "SELECT c.oid::pg_catalog.regclass,"
 							  "       pg_catalog.pg_get_expr(c.relpartbound, c.oid),"
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index bb696f8ee9..b8dbc5b539 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1134,6 +1134,7 @@ static const SchemaQuery Query_for_list_of_statistics = {
 "   SELECT 'DEFAULT' ) ss "\
 "  WHERE pg_catalog.substring(name,1,%%d)='%%s'"
 
+/* XXX fix this */
 /* the silly-looking length condition is just to eat up the current word */
 #define Query_for_partition_of_table \
 "SELECT pg_catalog.quote_ident(c2.relname) "\
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index c5e40ff017..d66a2f0b0d 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -149,7 +149,7 @@ extern void StorePartitionKey(Relation rel,
 				  Oid *partopclass,
 				  Oid *partcollation);
 extern void RemovePartitionKeyByRelId(Oid relid);
-extern void StorePartitionBound(Relation rel, Relation parent,
-					PartitionBoundSpec *bound);
+extern void MarkRelationPartitioned(Relation rel, Relation parent,
+					bool is_default);
 
 #endif							/* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 24915824ca..a42c7041d5 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -339,6 +339,12 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication
 DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
 #define PartitionedRelidIndexId			 3351
 
+DECLARE_UNIQUE_INDEX(pg_partition_partrelid_index, 3998, on pg_partition using btree(partrelid oid_ops));
+#define PartitionRelidIndexId			 3998
+
+DECLARE_INDEX(pg_partition_parentrelid_index, 4001, on pg_partition using btree(parentrelid oid_ops));
+#define PartitionParentrelidIndexId			 4001
+
 DECLARE_UNIQUE_INDEX(pg_publication_oid_index, 6110, on pg_publication using btree(oid oid_ops));
 #define PublicationObjectIndexId 6110
 
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 4b3b5ae770..8cbbd227f4 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -15,6 +15,7 @@
 
 #include "fmgr.h"
 #include "partitioning/partdefs.h"
+#include "storage/lock.h"
 #include "utils/relcache.h"
 
 /* Seed for the extended hash function */
@@ -35,6 +36,7 @@ typedef struct PartitionDescData
 
 extern Oid	get_partition_parent(Oid relid);
 extern List *get_partition_ancestors(Oid relid);
+extern List *get_partition_descendants(Oid relid, LOCKMODE lockmode);
 extern List *map_partition_varattnos(List *expr, int fromrel_varno,
 						Relation to_rel, Relation from_rel,
 						bool *found_whole_row);
diff --git a/src/include/catalog/pg_class.dat b/src/include/catalog/pg_class.dat
index 9fffdef379..d0c5ab466e 100644
--- a/src/include/catalog/pg_class.dat
+++ b/src/include/catalog/pg_class.dat
@@ -28,9 +28,9 @@
   relpersistence => 'p', relkind => 'r', relnatts => '30', relchecks => '0',
   relhasoids => 't', relhasrules => 'f', relhastriggers => 'f',
   relhassubclass => 'f', relrowsecurity => 'f', relforcerowsecurity => 'f',
-  relispopulated => 't', relreplident => 'n', relispartition => 'f',
+  relispopulated => 't', relreplident => 'n', relpartitionparent => '0',
   relrewrite => '0', relfrozenxid => '3', relminmxid => '1', relacl => '_null_',
-  reloptions => '_null_', relpartbound => '_null_' },
+  reloptions => '_null_' },
 { oid => '1249',
   relname => 'pg_attribute', relnamespace => 'PGNSP', reltype => '75',
   reloftype => '0', relowner => 'PGUID', relam => '0', relfilenode => '0',
@@ -39,9 +39,9 @@
   relpersistence => 'p', relkind => 'r', relnatts => '24', relchecks => '0',
   relhasoids => 'f', relhasrules => 'f', relhastriggers => 'f',
   relhassubclass => 'f', relrowsecurity => 'f', relforcerowsecurity => 'f',
-  relispopulated => 't', relreplident => 'n', relispartition => 'f',
+  relispopulated => 't', relreplident => 'n', relpartitionparent => '0',
   relrewrite => '0', relfrozenxid => '3', relminmxid => '1', relacl => '_null_',
-  reloptions => '_null_', relpartbound => '_null_' },
+  reloptions => '_null_' },
 { oid => '1255',
   relname => 'pg_proc', relnamespace => 'PGNSP', reltype => '81',
   reloftype => '0', relowner => 'PGUID', relam => '0', relfilenode => '0',
@@ -50,19 +50,19 @@
   relpersistence => 'p', relkind => 'r', relnatts => '28', relchecks => '0',
   relhasoids => 't', relhasrules => 'f', relhastriggers => 'f',
   relhassubclass => 'f', relrowsecurity => 'f', relforcerowsecurity => 'f',
-  relispopulated => 't', relreplident => 'n', relispartition => 'f',
+  relispopulated => 't', relreplident => 'n', relpartitionparent => '0',
   relrewrite => '0', relfrozenxid => '3', relminmxid => '1', relacl => '_null_',
-  reloptions => '_null_', relpartbound => '_null_' },
+  reloptions => '_null_' },
 { oid => '1259',
   relname => 'pg_class', relnamespace => 'PGNSP', reltype => '83',
   reloftype => '0', relowner => 'PGUID', relam => '0', relfilenode => '0',
   reltablespace => '0', relpages => '0', reltuples => '0', relallvisible => '0',
   reltoastrelid => '0', relhasindex => 'f', relisshared => 'f',
-  relpersistence => 'p', relkind => 'r', relnatts => '33', relchecks => '0',
+  relpersistence => 'p', relkind => 'r', relnatts => '32', relchecks => '0',
   relhasoids => 't', relhasrules => 'f', relhastriggers => 'f',
   relhassubclass => 'f', relrowsecurity => 'f', relforcerowsecurity => 'f',
-  relispopulated => 't', relreplident => 'n', relispartition => 'f',
+  relispopulated => 't', relreplident => 'n', relpartitionparent => '0',
   relrewrite => '0', relfrozenxid => '3', relminmxid => '1', relacl => '_null_',
-  reloptions => '_null_', relpartbound => '_null_' },
+  reloptions => '_null_' },
 
 ]
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index dc6c415c58..a8f2cadcf6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -66,7 +66,7 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
 										 * not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
-	bool		relispartition; /* is relation a partition? */
+	Oid			relpartitionparent; /* Oid of parent if partition, or 0 */
 	Oid			relrewrite;		/* heap for rewrite during DDL, link to
 								 * original rel */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
@@ -77,7 +77,6 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
 	/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
 	aclitem		relacl[1];		/* access permissions */
 	text		reloptions[1];	/* access-method-specific options */
-	pg_node_tree relpartbound;	/* partition bound node tree */
 #endif
 } FormData_pg_class;
 
diff --git a/src/include/catalog/toasting.h b/src/include/catalog/toasting.h
index f259890e43..7d3934865c 100644
--- a/src/include/catalog/toasting.h
+++ b/src/include/catalog/toasting.h
@@ -60,6 +60,7 @@ DECLARE_TOAST(pg_foreign_table, 4153, 4154);
 DECLARE_TOAST(pg_init_privs, 4155, 4156);
 DECLARE_TOAST(pg_language, 4157, 4158);
 DECLARE_TOAST(pg_namespace, 4163, 4164);
+DECLARE_TOAST(pg_partition, 3424, 3425);
 DECLARE_TOAST(pg_partitioned_table, 4165, 4166);
 DECLARE_TOAST(pg_policy, 4167, 4168);
 DECLARE_TOAST(pg_proc, 2836, 2837);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 07ab1a3dde..61b31fb2bb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -803,7 +803,7 @@ typedef struct PartitionSpec
  * PartitionBoundSpec - a partition bound specification
  *
  * This represents the portion of the partition key space assigned to a
- * particular partition.  These are stored on disk in pg_class.relpartbound.
+ * particular partition.  These are stored on disk in pg_partition.partbound.
  */
 struct PartitionBoundSpec
 {
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 6ecbdb6294..eb1858aa92 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -406,6 +406,12 @@ typedef struct ViewOptions
  */
 #define RelationGetRelid(relation) ((relation)->rd_id)
 
+/*
+ * RelationGetParentRelid
+ *		Returns the OID of the relations parent, or InvalidOid if the
+ *		relation has no parent
+ */
+#define RelationGetParentRelid(relation) ((relation)->rd_rel->relpartitionparent)
 /*
  * RelationGetNumberOfAttributes
  *		Returns the total number of attributes in a relation.
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 4f333586ee..44d94f001a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -73,6 +73,7 @@ enum SysCacheIdentifier
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
 	PARTRELID,
+	PARTSRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	PUBLICATIONNAME,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0218c2c362..bf62b042ac 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3654,10 +3654,10 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
 -- check that circular inheritance is not allowed
 ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b');
-ERROR:  circular inheritance not allowed
+ERROR:  circular partitioning is not allowed
 DETAIL:  "part_5" is already a child of "list_parted2".
 ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0);
-ERROR:  circular inheritance not allowed
+ERROR:  circular partitioning is not allowed
 DETAIL:  "list_parted2" is already a child of "list_parted2".
 -- If a partitioned table being created or an existing table being attached
 -- as a partition does not have a constraint that would allow validation scan
diff --git a/src/test/regress/expected/misc_sanity.out b/src/test/regress/expected/misc_sanity.out
index 2d3522b500..6285da1ee1 100644
--- a/src/test/regress/expected/misc_sanity.out
+++ b/src/test/regress/expected/misc_sanity.out
@@ -100,10 +100,9 @@ ORDER BY 1, 2;
  pg_attribute            | attoptions    | text[]
  pg_class                | relacl        | aclitem[]
  pg_class                | reloptions    | text[]
- pg_class                | relpartbound  | pg_node_tree
  pg_index                | indexprs      | pg_node_tree
  pg_index                | indpred       | pg_node_tree
  pg_largeobject          | data          | bytea
  pg_largeobject_metadata | lomacl        | aclitem[]
-(11 rows)
+(10 rows)
 
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 0aa5357917..44ad08e6e5 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -135,6 +135,7 @@ pg_namespace|t
 pg_opclass|t
 pg_operator|t
 pg_opfamily|t
+pg_partition|t
 pg_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
-- 
2.16.2.windows.1

