From f960543a0d1c10f9ccbb9bb19b994b47d5929365 Mon Sep 17 00:00:00 2001
From: David Zhang <david.zhang@highgo.ca>
Date: Thu, 17 Nov 2022 12:24:35 -0800
Subject: [PATCH 3/4] support global unique index attach and detach

---
 src/backend/commands/tablecmds.c       | 214 +++++++++++++++++++++++++
 src/test/regress/expected/indexing.out |  55 +++++++
 src/test/regress/sql/indexing.sql      |  33 ++++
 3 files changed, 302 insertions(+)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b16a1180d8..5aca295d29 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -645,6 +645,7 @@ static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
 static char GetAttributeStorage(Oid atttypid, const char *storagemode);
 
+static bool HasGlobalChildIndex(Relation idxRel);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -1217,6 +1218,11 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
+			if (HasGlobalChildIndex(idxRel))
+			{
+				elog(DEBUG2, "create global index for the new child partition table");
+				idxstmt->global_index = true;
+			}
 			DefineIndex(RelationGetRelid(rel),
 						idxstmt,
 						InvalidOid,
@@ -17992,6 +17998,7 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 	IndexInfo **attachInfos;
 	int			i;
 	ListCell   *cell;
+	ListCell   *cell2;
 	MemoryContext cxt;
 	MemoryContext oldcxt;
 
@@ -18130,15 +18137,185 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		{
 			IndexStmt  *stmt;
 			Oid			conOid;
+			bool 		isGlobal = false;
 
+			isGlobal = HasGlobalChildIndex(idxRel);
 			stmt = generateClonedIndexStmt(NULL,
 										   idxRel, attmap,
 										   &conOid);
+
+			/*
+			 * Perform cross partition uniqueness check if it is a global
+			 * unique index
+			 */
+			if (isGlobal && idxRel->rd_index->indisunique)
+			{
+				PartitionDesc partdesc;
+				Relation		hRel;
+				Relation 		iRel;
+				int 			j = 0;
+				int 			nparts;
+				Oid 			*part_oids;
+
+				List       *childIndexList = find_inheritance_children(idx, ShareLock);
+
+				partdesc = RelationGetPartitionDesc(rel, true);
+				nparts = partdesc->nparts;
+				part_oids = palloc(sizeof(Oid) * nparts);
+
+				memcpy(part_oids, partdesc->oids, sizeof(Oid) * nparts);
+				for (j = 0; j < nparts; j++)
+				{
+					Oid 		childRelid = part_oids[j];
+					List		*childidxs;
+
+					if (childRelid == RelationGetRelid(attachrel))
+					{
+						elog(DEBUG2, "skip the partition-to-be from building global spool: %d", childRelid);
+						continue;
+					}
+					hRel = table_open(childRelid, AccessShareLock);
+
+					childidxs = RelationGetIndexList(hRel);
+					foreach(cell2, childidxs)
+					{
+						Oid 	cldidxid = lfirst_oid(cell2);
+
+						/*
+						 * only take a child index that is directly inherited
+						 * to parent index oid
+						 */
+						if (list_member_oid(childIndexList, cldidxid))
+						{
+							iRel = index_open(cldidxid, AccessShareLock);
+							elog(DEBUG2, "found a matching child index OID to build global spool %d", cldidxid);
+
+							/*
+							 * We need to construct a global spool structure
+							 * in nbtsort.c in order to determine global
+							 * uniqueness. Marking partitions now
+							 */
+							if (j == 0)
+							{
+								elog(DEBUG2, "mark as first partitioned to build global spool");
+								stmt->globalIndexPart = -1;
+							}
+							else
+								stmt->globalIndexPart = 0;
+
+							stmt->global_index = true;
+							stmt->nparts = nparts;
+
+							PopulateGlobalSpool(iRel, hRel, stmt);
+							index_close(iRel, NoLock);
+							break;
+						}
+					}
+					table_close(hRel, NoLock);
+				}
+				elog(DEBUG2, "mark as the last partitioned to utilize global spool");
+				stmt->globalIndexPart = 1;
+			}
+			else
+			{
+				elog(DEBUG2, "partitioned index %d is not a unique index, build it now...",
+						 RelationGetRelid(idxRel));
+			}
+
 			DefineIndex(RelationGetRelid(attachrel), stmt, InvalidOid,
 						RelationGetRelid(idxRel),
 						conOid,
 						true, false, false, false, false);
 		}
+		else
+		{
+			IndexStmt  *stmt;
+			Oid 		conOid;
+			bool 		isGlobal = false;
+
+			stmt = generateClonedIndexStmt(NULL,
+										   idxRel, attmap,
+										   &conOid);
+			isGlobal = HasGlobalChildIndex(idxRel);
+			if (isGlobal && idxRel->rd_index->indisunique)
+			{
+				PartitionDesc partdesc;
+				Relation        hRel;
+				Relation        iRel;
+				int 			j = 0;
+				int 			nparts;
+				Oid 			*part_oids;
+
+				List 			*childIndexList = find_inheritance_children(idx, ShareLock);
+
+				partdesc = RelationGetPartitionDesc(rel, true);
+				nparts = partdesc->nparts;
+				part_oids = palloc(sizeof(Oid) * nparts);
+
+				memcpy(part_oids, partdesc->oids, sizeof(Oid) * nparts);
+				for (j = 0; j < nparts; j++)
+				{
+					Oid 		childRelid = part_oids[j];
+					List 		*childidxs;
+
+					hRel = table_open(childRelid, AccessShareLock);
+
+					childidxs = RelationGetIndexList(hRel);
+					foreach(cell2, childidxs)
+					{
+						Oid 	cldidxid = lfirst_oid(cell2);
+
+						/*
+						 * only take a child index that is directly inherited
+						 * to parent index oid
+						 */
+						if (list_member_oid(childIndexList, cldidxid))
+						{
+							iRel = index_open(cldidxid, AccessShareLock);
+							elog(DEBUG2, "found a matching child index OID type %c to build global spool %d",
+									 iRel->rd_rel->relkind, cldidxid);
+
+							/*
+							 * change partition-to-be's duplicate unique index
+							 * relkind to RELKIND_GLOBAL_INDEX
+							 */
+							if (iRel->rd_rel->relkind != RELKIND_GLOBAL_INDEX)
+							{
+								elog(DEBUG2, "Update index relation %d to have relkind = RELKIND_GLOBAL_INDEX",
+										 RelationGetRelid(iRel));
+								ChangeRelKind(iRel, RELKIND_GLOBAL_INDEX);
+							}
+
+							/*
+							 * We need to construct a global spool structure
+							 * in nbtsort.c in order to determine global
+							 * uniqueness. Marking partitions now
+							 */
+							if (j == 0)
+							{
+								elog(DEBUG2, "mark as first partition to build global spool");
+								stmt->globalIndexPart = -1;
+							}
+							else if (j == nparts - 1)
+							{
+								elog(DEBUG2, "mark as last partition to build global spool");
+								stmt->globalIndexPart = 1;
+							}
+							else
+								stmt->globalIndexPart = 0;
+
+							stmt->global_index = true;
+							stmt->nparts = nparts;
+
+							PopulateGlobalSpool(iRel, hRel, stmt);
+							index_close(iRel, NoLock);
+							break;
+						}
+					}
+					table_close(hRel, NoLock);
+				}
+			}
+		}
 
 		index_close(idxRel, AccessShareLock);
 	}
@@ -18636,6 +18813,15 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
 		if (OidIsValid(constrOid))
 			ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid);
 
+		/*
+		 * if it has any global index, make it a regular index relkind after
+		 * detach
+		 */
+		if (idx->rd_rel->relkind == RELKIND_GLOBAL_INDEX)
+		{
+			elog(DEBUG2, "found a global index, transform it to RELKIND_INDEX at detach...");
+			ChangeRelKind(idx, RELKIND_INDEX);
+		}
 		index_close(idx, NoLock);
 	}
 
@@ -19357,3 +19543,31 @@ GetAttributeStorage(Oid atttypid, const char *storagemode)
 
 	return cstorage;
 }
+
+static bool
+HasGlobalChildIndex(Relation idxRel)
+{
+	/* find out if current index contains child indexes that are global */
+	List	   *childIndexOidList;
+	ListCell   *cell;
+	bool		isGlobal = false;;
+
+	childIndexOidList = find_all_inheritors(RelationGetRelid(idxRel),
+											AccessExclusiveLock, NULL);
+
+	foreach(cell, childIndexOidList)
+	{
+		Oid			childIndexOid = lfirst_oid(cell);
+		Relation	idxChildRel;
+
+		idxChildRel = index_open(childIndexOid, AccessShareLock);
+		if (idxChildRel->rd_rel->relkind == RELKIND_GLOBAL_INDEX)
+		{
+			isGlobal = true;
+			index_close(idxChildRel, NoLock);
+			break;
+		}
+		index_close(idxChildRel, NoLock);
+	}
+	return isGlobal;
+}
diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.out
index 30a3ce0f20..58de14c037 100644
--- a/src/test/regress/expected/indexing.out
+++ b/src/test/regress/expected/indexing.out
@@ -1473,3 +1473,58 @@ DETAIL:  Key (b)=(572814) is duplicated.
 delete from gidxpart where a = 150000 and b = 572814;
 create unique index on gidxpart (b) global;
 drop table gidxpart;
+-- Test partition attach and detach with global unique index (no existing index)
+create table gidxpart (a int, b int, c text) partition by range (a);
+create table gidxpart1 partition of gidxpart for values from (0) to (100000);
+insert into gidxpart (a, b, c) values (42, 572814, 'inserted first on gidxpart1');
+create unique index on gidxpart (b) global;
+create table gidxpart2 (a int, b int, c text);
+insert into gidxpart2 (a, b, c) values (150000, 572814, 'dup inserted on gidxpart2');
+alter table gidxpart attach partition gidxpart2 for values from (100000) to (199999); -- should fail
+ERROR:  could not create unique index "gidxpart1_b_idx"
+DETAIL:  Key (b)=(572814) is duplicated.
+update gidxpart2 set b = 5000;
+alter table gidxpart attach partition gidxpart2 for values from (100000) to (199999);
+select relname, relkind from pg_class where relname = 'gidxpart2_b_idx'; -- should be g
+     relname     | relkind 
+-----------------+---------
+ gidxpart2_b_idx | g
+(1 row)
+
+alter table gidxpart detach partition gidxpart2;
+select relname, relkind from pg_class where relname = 'gidxpart2_b_idx'; -- should be i
+     relname     | relkind 
+-----------------+---------
+ gidxpart2_b_idx | i
+(1 row)
+
+drop table gidxpart;
+drop table gidxpart2;
+-- Test partition attach and detach with global unique index (with duplicate index)
+create table gidxpart (a int, b int, c text) partition by range (a);
+create table gidxpart1 partition of gidxpart for values from (0) to (100000);
+insert into gidxpart (a, b, c) values (42, 572814, 'inserted first on gidxpart1');
+create unique index on gidxpart (b) global;
+create table gidxpart2 (a int, b int, c text);
+create unique index on gidxpart2 (b);
+insert into gidxpart2 (a, b, c) values (150000, 572814, 'dup inserted on gidxpart2');
+select relname, relkind from pg_class where relname = 'gidxpart2_b_idx'; -- should be i
+     relname     | relkind 
+-----------------+---------
+ gidxpart2_b_idx | i
+(1 row)
+
+alter table gidxpart attach partition gidxpart2 for values from (100000) to (199999); -- should fail
+ERROR:  could not create unique index "gidxpart1_b_idx"
+DETAIL:  Key (b)=(572814) is duplicated.
+update gidxpart2 set b = 5000;
+alter table gidxpart attach partition gidxpart2 for values from (100000) to (199999);
+select relname, relkind from pg_class where relname = 'gidxpart2_b_idx'; -- should be g
+     relname     | relkind 
+-----------------+---------
+ gidxpart2_b_idx | g
+(1 row)
+
+alter table gidxpart detach partition gidxpart2;
+drop table gidxpart;
+drop table gidxpart2;
diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sql
index 84dde4df93..78649bb5ca 100644
--- a/src/test/regress/sql/indexing.sql
+++ b/src/test/regress/sql/indexing.sql
@@ -783,3 +783,36 @@ delete from gidxpart where a = 150000 and b = 572814;
 create unique index on gidxpart (b) global;
 drop table gidxpart;
 
+-- Test partition attach and detach with global unique index (no existing index)
+create table gidxpart (a int, b int, c text) partition by range (a);
+create table gidxpart1 partition of gidxpart for values from (0) to (100000);
+insert into gidxpart (a, b, c) values (42, 572814, 'inserted first on gidxpart1');
+create unique index on gidxpart (b) global;
+create table gidxpart2 (a int, b int, c text);
+insert into gidxpart2 (a, b, c) values (150000, 572814, 'dup inserted on gidxpart2');
+alter table gidxpart attach partition gidxpart2 for values from (100000) to (199999); -- should fail
+update gidxpart2 set b = 5000;
+alter table gidxpart attach partition gidxpart2 for values from (100000) to (199999);
+select relname, relkind from pg_class where relname = 'gidxpart2_b_idx'; -- should be g
+alter table gidxpart detach partition gidxpart2;
+select relname, relkind from pg_class where relname = 'gidxpart2_b_idx'; -- should be i
+drop table gidxpart;
+drop table gidxpart2;
+
+-- Test partition attach and detach with global unique index (with duplicate index)
+create table gidxpart (a int, b int, c text) partition by range (a);
+create table gidxpart1 partition of gidxpart for values from (0) to (100000);
+insert into gidxpart (a, b, c) values (42, 572814, 'inserted first on gidxpart1');
+create unique index on gidxpart (b) global;
+create table gidxpart2 (a int, b int, c text);
+create unique index on gidxpart2 (b);
+insert into gidxpart2 (a, b, c) values (150000, 572814, 'dup inserted on gidxpart2');
+select relname, relkind from pg_class where relname = 'gidxpart2_b_idx'; -- should be i
+alter table gidxpart attach partition gidxpart2 for values from (100000) to (199999); -- should fail
+update gidxpart2 set b = 5000;
+alter table gidxpart attach partition gidxpart2 for values from (100000) to (199999);
+select relname, relkind from pg_class where relname = 'gidxpart2_b_idx'; -- should be g
+alter table gidxpart detach partition gidxpart2;
+drop table gidxpart;
+drop table gidxpart2;
+
-- 
2.17.1

