Change the way ATExecMergePartitions() handles the name collision
authorAlexander Korotkov <akorotkov@postgresql.org>
Tue, 30 Apr 2024 08:54:42 +0000 (11:54 +0300)
committerAlexander Korotkov <akorotkov@postgresql.org>
Tue, 30 Apr 2024 08:54:42 +0000 (11:54 +0300)
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, Alexander Korotkov
Reviewed-by: Robert Haas, Justin Pryzby, Pavel Borisov
src/backend/commands/tablecmds.c
src/test/regress/expected/partition_merge.out
src/test/regress/sql/partition_merge.sql

index f1725c9da8cdfc4f5fa71dbc0dc89c74a9d37401..8fa09afdc59a6e4c1bf4ef79eee738dc1dfc4a66 100644 (file)
@@ -21503,9 +21503,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
@@ -21527,8 +21524,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,
@@ -21548,15 +21565,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);
@@ -21567,18 +21576,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;
@@ -21596,18 +21599,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);
 }
index 373d32948cac2c2c7f54e3fea2ad7f79792b734c..2e0bfdc705d4971b4c49498dcd2289c1658a23a0 100644 (file)
@@ -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;
index 6a0b35b1799accb4067121dcbc8f8e2c5ee73eb0..72b1cb0b35e1d5512f9055f06df0646147b2a8ad 100644 (file)
@@ -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;