From 67ee039fa064bc8e74e93ac8229c9481422a5317 Mon Sep 17 00:00:00 2001
From: Peter Smith <peter.b.smith@fujitsu.com>
Date: Mon, 3 Aug 2026 18:39:00 +1000
Subject: [PATCH v6] Add C function get_partition_root

Adds a C equivalent of the existing SQL function pg_partition_root.

Author: Peter Smith <smithpb2250@gmail.com>
Reviewed-by: shveta malik <shveta.malik@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>

Discussion: https://www.postgresql.org/message-id/flat/CAJpy0uDrs7ag3QwU7QgDNXOo9trLxgq881Qu1TgzuJVwsNpnHA%40mail.gmail.com#2438fc93ce2aa09eabdda0b16af3f5c9
---
 src/backend/catalog/partition.c        | 80 +++++++++++++++++++++++---
 src/backend/catalog/pg_depend.c        |  4 +-
 src/backend/utils/adt/partitionfuncs.c | 14 +----
 src/include/catalog/partition.h        |  2 +
 4 files changed, 77 insertions(+), 23 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 28f3cade6ff..86e7fb40933 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -29,12 +29,14 @@
 #include "utils/fmgroids.h"
 #include "utils/partcache.h"
 #include "utils/rel.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
 static Oid	get_partition_parent_worker(Relation inhRel, Oid relid,
 										bool *detach_pending);
 static void get_partition_ancestors_worker(Relation inhRel, Oid relid,
-										   List **ancestors);
+										   List **ancestors,
+										   bool *detach_pending);
 
 /*
  * get_partition_parent
@@ -118,6 +120,69 @@ get_partition_parent_worker(Relation inhRel, Oid relid, bool *detach_pending)
 	return result;
 }
 
+/*
+ * get_partition_root
+ *		Obtain root partitioned table OID of the specified relation
+ *
+ * If the partition is in the process of being detached, return InvalidOid,
+ * unless even_if_detached is passed as true.
+ *
+ * Note: This should only be called when it is known that the relation is a
+ * partition or partitioned table.
+ */
+Oid
+get_partition_root(Oid relid, bool even_if_detached)
+{
+	/* Validate relid is member of a partition tree */
+	Assert(get_rel_relispartition(relid) ||
+		   RELKIND_HAS_PARTITIONS(get_rel_relkind(relid)));
+
+	return get_partition_root_guts(relid, even_if_detached);
+}
+
+Oid
+get_partition_root_guts(Oid relid, bool even_if_detached)
+{
+	Oid root_relid;
+	List *ancestors = NIL;
+	Relation	inhRel;
+	bool detach_pending = false;
+
+	/*
+	* Fetch the list of ancestors. This is same as get_partition_ancestors,
+	* but calling directly to get_partition_ancestors_worker exposes the
+	* `detach_pending` flag.
+	*/
+	inhRel = table_open(InheritsRelationId, AccessShareLock);
+	get_partition_ancestors_worker(inhRel, relid, &ancestors, &detach_pending);
+	table_close(inhRel, AccessShareLock);
+
+	if (ancestors)
+	{
+		/* By definition, the last ancestor is the topmost parent */
+		root_relid = llast_oid(ancestors);
+		list_free(ancestors);
+	}
+	else
+	{
+		/*
+		 * NIL ancestors can mean either:
+		 * 1. a detach is pending.
+		 * 2. relid was already the topmost parent.
+		 */
+		elog(DEBUG1, "get_partition_root found ancestors=NIL with "
+			"detach_pending=%s for relid %u",
+			detach_pending ? "true" : "false", relid);
+
+		if (detach_pending)
+			return even_if_detached ? relid : InvalidOid;
+		else
+			root_relid = relid;
+	}
+
+	return root_relid;
+}
+
 /*
  * get_partition_ancestors
  *		Obtain ancestors of given relation
@@ -135,10 +200,11 @@ get_partition_ancestors(Oid relid)
 {
 	List	   *result = NIL;
 	Relation	inhRel;
+	bool		detach_pending = false;
 
 	inhRel = table_open(InheritsRelationId, AccessShareLock);
 
-	get_partition_ancestors_worker(inhRel, relid, &result);
+	get_partition_ancestors_worker(inhRel, relid, &result, &detach_pending);
 
 	table_close(inhRel, AccessShareLock);
 
@@ -150,21 +216,21 @@ get_partition_ancestors(Oid relid)
  *		recursive worker for get_partition_ancestors
  */
 static void
-get_partition_ancestors_worker(Relation inhRel, Oid relid, List **ancestors)
+get_partition_ancestors_worker(Relation inhRel, Oid relid, List **ancestors,
+	bool *detach_pending)
 {
 	Oid			parentOid;
-	bool		detach_pending;
 
 	/*
 	 * Recursion ends at the topmost level, ie., when there's no parent; also
 	 * when the partition is being detached.
 	 */
-	parentOid = get_partition_parent_worker(inhRel, relid, &detach_pending);
-	if (parentOid == InvalidOid || detach_pending)
+	parentOid = get_partition_parent_worker(inhRel, relid, detach_pending);
+	if (parentOid == InvalidOid || *detach_pending)
 		return;
 
 	*ancestors = lappend_oid(*ancestors, parentOid);
-	get_partition_ancestors_worker(inhRel, parentOid, ancestors);
+	get_partition_ancestors_worker(inhRel, parentOid, ancestors, detach_pending);
 }
 
 /*
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 9a7a401aced..a42c4f33387 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -1157,15 +1157,13 @@ getIdentitySequence(Relation rel, AttrNumber attnum, bool missing_ok)
 	 */
 	if (RelationGetForm(rel)->relispartition)
 	{
-		List	   *ancestors = get_partition_ancestors(relid);
 		const char *attname = get_attname(relid, attnum, false);
 
-		relid = llast_oid(ancestors);
+		relid = get_partition_root(relid, true);
 		attnum = get_attnum(relid, attname);
 		if (attnum == InvalidAttrNumber)
 			elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
 				 attname, relid);
-		list_free(ancestors);
 	}
 
 	seqlist = getOwnedSequences_internal(relid, attnum, DEPENDENCY_INTERNAL);
diff --git a/src/backend/utils/adt/partitionfuncs.c b/src/backend/utils/adt/partitionfuncs.c
index e9db027aa2e..2e38f0bd44d 100644
--- a/src/backend/utils/adt/partitionfuncs.c
+++ b/src/backend/utils/adt/partitionfuncs.c
@@ -165,23 +165,11 @@ pg_partition_root(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	Oid			rootrelid;
-	List	   *ancestors;
 
 	if (!check_rel_can_be_partition(relid))
 		PG_RETURN_NULL();
 
-	/* fetch the list of ancestors */
-	ancestors = get_partition_ancestors(relid);
-
-	/*
-	 * If the input relation is already the top-most parent, just return
-	 * itself.
-	 */
-	if (ancestors == NIL)
-		PG_RETURN_OID(relid);
-
-	rootrelid = llast_oid(ancestors);
-	list_free(ancestors);
+	rootrelid = get_partition_root_guts(relid, true);
 
 	/*
 	 * "rootrelid" must contain a valid OID, given that the input relation is
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index a93cf081dd2..ef7c7e582df 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -21,6 +21,8 @@
 
 extern Oid	get_partition_parent(Oid relid, bool even_if_detached);
 extern List *get_partition_ancestors(Oid relid);
+extern Oid	get_partition_root(Oid relid, bool even_if_detached);
+extern Oid	get_partition_root_guts(Oid relid, bool even_if_detached);
 extern Oid	index_get_partition(Relation partition, Oid indexId);
 extern List *map_partition_varattnos(List *expr, int fromrel_varno,
 									 Relation to_rel, Relation from_rel);
-- 
2.47.3

