From f17c461ea4e054950ee4e4f423ea223d20382477 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Fri, 19 Apr 2024 13:39:45 +0300
Subject: [PATCH v8 1/7] Change the way ATExecMergePartitions() handles the
 name collision

The name collision happens when the name of the new partition is the same as
the name of one of the merging partitions.  Currently, ATExecMergePartitions()
first gives the new partition a temporary name and then renames it when old
partitions are deleted.  That negatively influences the naming of related
objects like indexes and constrains, which could inherit a temporary name.

This commit changes the implementation in the following way.  A merging
partition gets renamed first, then the new partition is created with the
right name immediately.  This resolves the issue of the naming of related
objects.

Reported-by: Alexander Lakhin
Discussion: https://postgr.es/m/edfbd846-dcc1-42d1-ac26-715691b687d3%40postgrespro.ru
Author: Dmitry Koval
Reviewed-by: Alexander Korotkov, Robert Haas
---
 src/backend/commands/tablecmds.c              | 63 +++++++++----------
 src/test/regress/expected/partition_merge.out | 25 ++++++++
 src/test/regress/sql/partition_merge.sql      | 18 ++++++
 3 files changed, 73 insertions(+), 33 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3556240c8ed..dbb6ee356bb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -21477,9 +21477,6 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	ListCell   *listptr;
 	List	   *mergingPartitionsList = NIL;
 	Oid			defaultPartOid;
-	char		tmpRelName[NAMEDATALEN];
-	RangeVar   *mergePartName = cmd->name;
-	bool		isSameName = false;
 
 	/*
 	 * Lock all merged partitions, check them and create list with partitions
@@ -21501,8 +21498,28 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		 * function transformPartitionCmdForMerge().
 		 */
 		if (equal(name, cmd->name))
+		{
 			/* One new partition can have the same name as merged partition. */
-			isSameName = true;
+			char		tmpRelName[NAMEDATALEN];
+
+			/* Generate temporary name. */
+			sprintf(tmpRelName, "merge-%u-%X-tmp", RelationGetRelid(rel), MyProcPid);
+
+			/*
+			 * Rename the existing partition with a temporary name, leaving it
+			 * free for the new partition.  We don't need to care about this
+			 * in the future because we're going to eventually drop the
+			 * existing partition anyway.
+			 */
+			RenameRelationInternal(RelationGetRelid(mergingPartition),
+								   tmpRelName, false, false);
+
+			/*
+			 * We must bump the command counter to make the new partition
+			 * tuple visible for rename.
+			 */
+			CommandCounterIncrement();
+		}
 
 		/* Store a next merging partition into the list. */
 		mergingPartitionsList = lappend(mergingPartitionsList,
@@ -21522,15 +21539,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		DetachPartitionFinalize(rel, mergingPartition, false, defaultPartOid);
 	}
 
-	/* Create table for new partition, use partitioned table as model. */
-	if (isSameName)
-	{
-		/* Create partition table with generated temporary name. */
-		sprintf(tmpRelName, "merge-%u-%X-tmp", RelationGetRelid(rel), MyProcPid);
-		mergePartName = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
-									 tmpRelName, -1);
-	}
-	createPartitionTable(mergePartName,
+	createPartitionTable(cmd->name,
 						 makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
 									  RelationGetRelationName(rel), -1),
 						 context);
@@ -21541,18 +21550,12 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 * excessive, but this is the way we make sure nobody is planning queries
 	 * involving merging partitions.
 	 */
-	newPartRel = table_openrv(mergePartName, AccessExclusiveLock);
+	newPartRel = table_openrv(cmd->name, AccessExclusiveLock);
 
 	/* Copy data from merged partitions to new partition. */
 	moveMergedTablesRows(rel, mergingPartitionsList, newPartRel);
 
-	/*
-	 * Attach a new partition to the partitioned table. wqueue = NULL:
-	 * verification for each cloned constraint is not need.
-	 */
-	attachPartitionTable(NULL, rel, newPartRel, cmd->bound);
-
-	/* Unlock and drop merged partitions. */
+	/* Drop the current partitions before attaching the new one. */
 	foreach(listptr, mergingPartitionsList)
 	{
 		ObjectAddress object;
@@ -21570,18 +21573,12 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	}
 	list_free(mergingPartitionsList);
 
-	/* Rename new partition if it is needed. */
-	if (isSameName)
-	{
-		/*
-		 * We must bump the command counter to make the new partition tuple
-		 * visible for rename.
-		 */
-		CommandCounterIncrement();
-		/* Rename partition. */
-		RenameRelationInternal(RelationGetRelid(newPartRel),
-							   cmd->name->relname, false, false);
-	}
+	/*
+	 * Attach a new partition to the partitioned table. wqueue = NULL:
+	 * verification for each cloned constraint is not needed.
+	 */
+	attachPartitionTable(NULL, rel, newPartRel, cmd->bound);
+
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 }
diff --git a/src/test/regress/expected/partition_merge.out b/src/test/regress/expected/partition_merge.out
index 373d32948ca..2e0bfdc705d 100644
--- a/src/test/regress/expected/partition_merge.out
+++ b/src/test/regress/expected/partition_merge.out
@@ -746,4 +746,29 @@ DROP TABLE t3;
 DROP TABLE t2;
 DROP TABLE t1;
 --
+-- Check the partition index name if the partition name is the same as one
+-- of the merged partitions.
+--
+CREATE TABLE t (i int, PRIMARY KEY(i)) PARTITION BY RANGE (i);
+CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1);
+CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2);
+CREATE INDEX tidx ON t(i);
+ALTER TABLE t MERGE PARTITIONS (tp_1_2, tp_0_1) INTO tp_1_2;
+-- Indexname values should be 'tp_1_2_pkey' and 'tp_1_2_i_idx'.
+-- Not-null constraint name should be 'tp_1_2_i_not_null'.
+\d+ tp_1_2
+                          Table "partitions_merge_schema.tp_1_2"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ i      | integer |           | not null |         | plain   |              | 
+Partition of: t FOR VALUES FROM (0) TO (2)
+Partition constraint: ((i IS NOT NULL) AND (i >= 0) AND (i < 2))
+Indexes:
+    "tp_1_2_pkey" PRIMARY KEY, btree (i)
+    "tp_1_2_i_idx" btree (i)
+Not-null constraints:
+    "tp_1_2_i_not_null" NOT NULL "i"
+
+DROP TABLE t;
+--
 DROP SCHEMA partitions_merge_schema;
diff --git a/src/test/regress/sql/partition_merge.sql b/src/test/regress/sql/partition_merge.sql
index 6a0b35b1799..72b1cb0b35e 100644
--- a/src/test/regress/sql/partition_merge.sql
+++ b/src/test/regress/sql/partition_merge.sql
@@ -444,5 +444,23 @@ DROP TABLE t3;
 DROP TABLE t2;
 DROP TABLE t1;
 
+--
+-- Check the partition index name if the partition name is the same as one
+-- of the merged partitions.
+--
+CREATE TABLE t (i int, PRIMARY KEY(i)) PARTITION BY RANGE (i);
+
+CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1);
+CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2);
+
+CREATE INDEX tidx ON t(i);
+ALTER TABLE t MERGE PARTITIONS (tp_1_2, tp_0_1) INTO tp_1_2;
+
+-- Indexname values should be 'tp_1_2_pkey' and 'tp_1_2_i_idx'.
+-- Not-null constraint name should be 'tp_1_2_i_not_null'.
+\d+ tp_1_2
+
+DROP TABLE t;
+
 --
 DROP SCHEMA partitions_merge_schema;
-- 
2.39.3 (Apple Git-145)

