From 62e43848dcde7f007cb34bea9f2010353a6f75b1 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 20 May 2024 17:11:23 +0200
Subject: [PATCH 1/2] Fix detach bug

---
 src/backend/executor/execPartition.c | 56 ++++++++++++++++++++++++----
 src/backend/partitioning/partdesc.c  | 18 +++++++++
 2 files changed, 67 insertions(+), 7 deletions(-)

diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index bb14dcbe6f..637e5bac5d 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -1942,13 +1942,10 @@ CreatePartitionPruneState(PlanState *planstate, PartitionPruneInfo *pruneinfo)
 			/*
 			 * Initialize the subplan_map and subpart_map.
 			 *
-			 * Because we request detached partitions to be included, and
-			 * detaching waits for old transactions, it is safe to assume that
-			 * no partitions have disappeared since this query was planned.
-			 *
-			 * However, new partitions may have been added.
+			 * The set of partitions that exist now might not be the same that
+			 * existed when the plan was made, so we have to cope.
 			 */
-			Assert(partdesc->nparts >= pinfo->nparts);
+
 			pprune->nparts = partdesc->nparts;
 			pprune->subplan_map = palloc(sizeof(int) * partdesc->nparts);
 			if (partdesc->nparts == pinfo->nparts)
@@ -1974,7 +1971,7 @@ CreatePartitionPruneState(PlanState *planstate, PartitionPruneInfo *pruneinfo)
 				}
 #endif
 			}
-			else
+			else if (partdesc->nparts > pinfo->nparts)
 			{
 				int			pd_idx = 0;
 				int			pp_idx;
@@ -2031,6 +2028,51 @@ CreatePartitionPruneState(PlanState *planstate, PartitionPruneInfo *pruneinfo)
 				if (pd_idx != pinfo->nparts)
 					elog(ERROR, "could not match partition child tables to plan elements");
 			}
+			else if (partdesc->nparts == pinfo->nparts - 1)
+			{
+				int			pd_idx = 0;
+				int			pp_idx;
+
+				/*
+				 * In this case we know that the plan-time descriptor had more
+				 * partitions than the current descriptor, which means that
+				 * one partition was detached in concurrent mode.  (It's not
+				 * possible for multiple partitions to be detached in this
+				 * way).  Cope with this by setting them as pruned, because
+				 * the query cannot possibly care about results coming from
+				 * them anyway.
+				 */
+
+				pprune->subpart_map = palloc(sizeof(int) * pinfo->nparts);
+				for (pp_idx = 0; pp_idx < partdesc->nparts; pp_idx++)
+				{
+					if (pinfo->relid_map[pd_idx] != partdesc->oids[pp_idx])
+					{
+						pprune->subplan_map[pp_idx] = -1;
+						pprune->subpart_map[pp_idx] = -1;
+						pd_idx++;
+					}
+
+					if (pd_idx < pinfo->nparts &&
+						pinfo->relid_map[pd_idx] == partdesc->oids[pp_idx])
+					{
+						pprune->subplan_map[pp_idx] =
+							pinfo->subplan_map[pd_idx];
+						pprune->subpart_map[pp_idx] =
+							pinfo->subpart_map[pd_idx];
+						pd_idx++;
+					}
+					else
+					{
+						pprune->subplan_map[pp_idx] = -1;
+						pprune->subpart_map[pp_idx] = -1;
+					}
+				}
+			}
+			else
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						errmsg("too many detached partitions"));
 
 			/* present_parts is also subject to later modification */
 			pprune->present_parts = bms_copy(pinfo->present_parts);
diff --git a/src/backend/partitioning/partdesc.c b/src/backend/partitioning/partdesc.c
index 47c97566e6..16ce956d03 100644
--- a/src/backend/partitioning/partdesc.c
+++ b/src/backend/partitioning/partdesc.c
@@ -24,6 +24,7 @@
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/hsearch.h"
+#include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/partcache.h"
@@ -149,6 +150,8 @@ RelationBuildPartitionDesc(Relation rel, bool omit_detached)
 	MemoryContext oldcxt;
 	int		   *mapping;
 
+retry:
+
 	/*
 	 * Get partition oids from pg_inherits.  This uses a single snapshot to
 	 * fetch the list of children, so while more children may be getting added
@@ -179,6 +182,7 @@ RelationBuildPartitionDesc(Relation rel, bool omit_detached)
 		Oid			inhrelid = lfirst_oid(cell);
 		HeapTuple	tuple;
 		PartitionBoundSpec *boundspec = NULL;
+		bool		retried = false;
 
 		/* Try fetching the tuple from the catcache, for speed. */
 		tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(inhrelid));
@@ -233,6 +237,20 @@ RelationBuildPartitionDesc(Relation rel, bool omit_detached)
 			table_close(pg_class, AccessShareLock);
 		}
 
+		/*
+		 * If any of our partitions is still seen with a NULL relpartbound
+		 * after rereading pg_class directly, it might be that it's no longer
+		 * in the set of partitions anymore because of DETACH CONCURRENTLY.
+		 * We can dodge this by reading invalidation messages and fetching a
+		 * fresh list of partitions.  We only do this once, because ... XXX
+		 * ???
+		 */
+		if (!boundspec && !retried)
+		{
+			AcceptInvalidationMessages();
+			retried = true;
+			goto retry;
+		}
 		/* Sanity checks. */
 		if (!boundspec)
 			elog(ERROR, "missing relpartbound for relation %u", inhrelid);
-- 
2.39.2

