From: PostgreSQL relcache v3 Linux review
Subject: [PATCH 3/4] Keep allocations outside relcache descriptor swaps

Allocate RelationPartitionInfo before reparenting a completed partition-key
context and before beginning RelationRebuildRelation's descriptor swap.  An
allocation ERROR inside that swap can otherwise leave a valid relcache entry
with a descriptor from an aborted catalog change.

Guard the table-side union access by relation kind.  Depends on patch 1 and
applies after David Geier's v3 patches 0001 through 0006, based on
9dbc53591a2de03e0cfdab61a78726f8b2c29eef.
---
diff --git a/src/backend/utils/cache/partcache.c b/src/backend/utils/cache/partcache.c
index 959ac904bf7..05964951cd8 100644
--- a/src/backend/utils/cache/partcache.c
+++ b/src/backend/utils/cache/partcache.c
@@ -263,10 +263,10 @@ RelationBuildPartitionKey(Relation relation)
 	 * Success --- reparent our context and make the relcache point to the
 	 * newly constructed key
 	 */
-	MemoryContextSetParent(partkeycxt, CacheMemoryContext);
 	if (relation->rd_partinfo == NULL)
 		relation->rd_partinfo = MemoryContextAllocZero(CacheMemoryContext,
 													   sizeof(RelationPartitionInfo));
+	MemoryContextSetParent(partkeycxt, CacheMemoryContext);
 	relation->rd_partinfo->partkeycxt = partkeycxt;
 	relation->rd_partinfo->partkey = key;
 }
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index c4b7e01503b..f736543aa78 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2754,6 +2754,19 @@ RelationRebuildRelation(Relation relation)
 							relation->rd_partinfo->partkey != NULL);
 		}
 
+		/*
+		 * Ensure that the destination has storage for partition metadata
+		 * before starting the descriptor swap.  Nothing in the swap region
+		 * may fail, because relation is transiently inconsistent there.
+		 */
+		if (!RelationIsIndex(relation) && newrel->rd_partinfo == NULL &&
+			(keep_partkey ||
+			 (relation->rd_partinfo != NULL &&
+			  (relation->rd_partinfo->pdcxt != NULL ||
+			   relation->rd_partinfo->pddcxt != NULL))))
+			newrel->rd_partinfo = MemoryContextAllocZero(CacheMemoryContext,
+													 sizeof(RelationPartitionInfo));
+
 		/*
 		 * Perform swapping of the relcache entry contents.  Within this
 		 * process the old entry is momentarily invalid, so there *must* be no
@@ -2819,9 +2832,7 @@ RelationRebuildRelation(Relation relation)
 		if (keep_partkey)
 		{
 			Assert(newrel->rd_partinfo != NULL);
-			if (relation->rd_partinfo == NULL)
-				relation->rd_partinfo = MemoryContextAllocZero(CacheMemoryContext,
-																sizeof(RelationPartitionInfo));
+			Assert(relation->rd_partinfo != NULL);
 			relation->rd_partinfo->partkey = newrel->rd_partinfo->partkey;
 			relation->rd_partinfo->partkeycxt = newrel->rd_partinfo->partkeycxt;
 			newrel->rd_partinfo->partkey = NULL;
@@ -2855,9 +2866,7 @@ RelationRebuildRelation(Relation relation)
 			 * "old" partition descriptor is actually the one hanging off of
 			 * newrel.
 			 */
-			if (relation->rd_partinfo == NULL)
-				relation->rd_partinfo = MemoryContextAllocZero(CacheMemoryContext,
-																sizeof(RelationPartitionInfo));
+			Assert(relation->rd_partinfo != NULL);
 			relation->rd_partinfo->partdesc = NULL; /* ensure partdesc is invalid */
 			relation->rd_partinfo->partdesc_nodetached = NULL;
 			relation->rd_partinfo->partdesc_nodetached_xmin = InvalidTransactionId;
