Use SnapshotDirty when checking for conflicting index names.
authorTom Lane <[email protected]>
Fri, 20 Jun 2025 17:41:11 +0000 (13:41 -0400)
committerTom Lane <[email protected]>
Fri, 20 Jun 2025 17:41:11 +0000 (13:41 -0400)
While choosing an autogenerated name for an index, look for
pre-existing relations using a SnapshotDirty snapshot, instead of the
previous behavior that considered only committed-good pg_class rows.
This allows us to detect and avoid conflicts against indexes that are
still being built.

It's still possible to fail due to a race condition, but the window
is now just the amount of time that it takes DefineIndex to validate
all its parameters, call smgrcreate(), and enter the index's pg_class
row.  Formerly the race window covered the entire time needed to
create and fill an index, which could be very long if the table is
large.  Worse, if the conflicting index creation is part of a larger
transaction, it wouldn't be visible till COMMIT.

So this isn't a complete solution, but it should greatly ameliorate
the problem, and the patch is simple enough to be back-patchable.

It might at some point be useful to do the same for pg_constraint
entries (cf. ChooseConstraintName, ConstraintNameExists, and related
functions).  However, in the absence of field complaints, I'll leave
that alone for now.  The relation-name test should be good enough for
index-based constraints, while foreign-key constraints seem to be okay
since they require exclusive locks to create.

Bug: #18959
Reported-by: Maximilian Chrzan <[email protected]>
Author: Tom Lane <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Discussion: https://postgr.es/m/18959-f63b53b864bb1417@postgresql.org
Backpatch-through: 13

src/backend/commands/indexcmds.c

index c3ec2076a52ef7fc1b7f4ddc3ea482d236e4a5e4..f2898fee5fcd5d06b30ca9d5575782136b34c74d 100644 (file)
@@ -2592,7 +2592,9 @@ makeObjectName(const char *name1, const char *name2, const char *label)
  * constraint names.)
  *
  * Note: it is theoretically possible to get a collision anyway, if someone
- * else chooses the same name concurrently.  This is fairly unlikely to be
+ * else chooses the same name concurrently.  We shorten the race condition
+ * window by checking for conflicting relations using SnapshotDirty, but
+ * that doesn't close the window entirely.  This is fairly unlikely to be
  * a problem in practice, especially if one is holding an exclusive lock on
  * the relation identified by name1.  However, if choosing multiple names
  * within a single command, you'd better create the new object and do
@@ -2608,15 +2610,45 @@ ChooseRelationName(const char *name1, const char *name2,
    int         pass = 0;
    char       *relname = NULL;
    char        modlabel[NAMEDATALEN];
+   SnapshotData SnapshotDirty;
+   Relation    pgclassrel;
+
+   /* prepare to search pg_class with a dirty snapshot */
+   InitDirtySnapshot(SnapshotDirty);
+   pgclassrel = table_open(RelationRelationId, AccessShareLock);
 
    /* try the unmodified label first */
    strlcpy(modlabel, label, sizeof(modlabel));
 
    for (;;)
    {
+       ScanKeyData key[2];
+       SysScanDesc scan;
+       bool        collides;
+
        relname = makeObjectName(name1, name2, modlabel);
 
-       if (!OidIsValid(get_relname_relid(relname, namespaceid)))
+       /* is there any conflicting relation name? */
+       ScanKeyInit(&key[0],
+                   Anum_pg_class_relname,
+                   BTEqualStrategyNumber, F_NAMEEQ,
+                   CStringGetDatum(relname));
+       ScanKeyInit(&key[1],
+                   Anum_pg_class_relnamespace,
+                   BTEqualStrategyNumber, F_OIDEQ,
+                   ObjectIdGetDatum(namespaceid));
+
+       scan = systable_beginscan(pgclassrel, ClassNameNspIndexId,
+                                 true /* indexOK */ ,
+                                 &SnapshotDirty,
+                                 2, key);
+
+       collides = HeapTupleIsValid(systable_getnext(scan));
+
+       systable_endscan(scan);
+
+       /* break out of loop if no conflict */
+       if (!collides)
        {
            if (!isconstraint ||
                !ConstraintNameExists(relname, namespaceid))
@@ -2628,6 +2660,8 @@ ChooseRelationName(const char *name1, const char *name2,
        snprintf(modlabel, sizeof(modlabel), "%s%d", label, ++pass);
    }
 
+   table_close(pgclassrel, AccessShareLock);
+
    return relname;
 }