]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Ensure correct lock level is used in ALTER ... RENAME
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 19 Oct 2021 22:08:45 +0000 (19:08 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 19 Oct 2021 22:08:45 +0000 (19:08 -0300)
Commit 1b5d797cd4f7 intended to relax the lock level used to rename
indexes, but inadvertently allowed *any* relation to be renamed with a
lowered lock level, as long as the command is spelled ALTER INDEX.
That's undesirable for other relation types, so retry the operation with
the higher lock if the relation turns out not to be an index.

After this fix, ALTER INDEX <sometable> RENAME will require access
exclusive lock, which it didn't before.

Author: Nathan Bossart <bossartn@amazon.com>
Author: Álvaro Herrera <alvherre@alvh.no-ip.org>
Reported-by: Onder Kalaci <onderk@microsoft.com>
Discussion: https://postgr.es/m/PH0PR21MB1328189E2821CDEC646F8178D8AE9@PH0PR21MB1328.namprd21.prod.outlook.com

src/backend/commands/tablecmds.c
src/test/regress/expected/alter_table.out
src/test/regress/sql/alter_table.sql

index 8d3bd458ed5746e245118b687f2fec76f5320921..d492f27d5daf96a847502913dfa6e0d59d0b2779 100644 (file)
@@ -3448,7 +3448,7 @@ RenameConstraint(RenameStmt *stmt)
 ObjectAddress
 RenameRelation(RenameStmt *stmt)
 {
-       bool            is_index = stmt->renameType == OBJECT_INDEX;
+       bool            is_index_stmt = stmt->renameType == OBJECT_INDEX;
        Oid                     relid;
        ObjectAddress address;
 
@@ -3458,24 +3458,48 @@ RenameRelation(RenameStmt *stmt)
         * end of transaction.
         *
         * Lock level used here should match RenameRelationInternal, to avoid lock
-        * escalation.
+        * escalation.  However, because ALTER INDEX can be used with any relation
+        * type, we mustn't believe without verification.
         */
-       relid = RangeVarGetRelidExtended(stmt->relation,
-                                                                        is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock,
-                                                                        stmt->missing_ok ? RVR_MISSING_OK : 0,
-                                                                        RangeVarCallbackForAlterRelation,
-                                                                        (void *) stmt);
-
-       if (!OidIsValid(relid))
+       for (;;)
        {
-               ereport(NOTICE,
-                               (errmsg("relation \"%s\" does not exist, skipping",
-                                               stmt->relation->relname)));
-               return InvalidObjectAddress;
+               LOCKMODE        lockmode;
+               char            relkind;
+               bool            obj_is_index;
+
+               lockmode = is_index_stmt ? ShareUpdateExclusiveLock : AccessExclusiveLock;
+
+               relid = RangeVarGetRelidExtended(stmt->relation, lockmode,
+                                                                                stmt->missing_ok ? RVR_MISSING_OK : 0,
+                                                                                RangeVarCallbackForAlterRelation,
+                                                                                (void *) stmt);
+
+               if (!OidIsValid(relid))
+               {
+                       ereport(NOTICE,
+                                       (errmsg("relation \"%s\" does not exist, skipping",
+                                                       stmt->relation->relname)));
+                       return InvalidObjectAddress;
+               }
+
+               /*
+                * We allow mismatched statement and object types (e.g., ALTER INDEX
+                * to rename a table), but we might've used the wrong lock level.  If
+                * that happens, retry with the correct lock level.  We don't bother
+                * if we already acquired AccessExclusiveLock with an index, however.
+                */
+               relkind = get_rel_relkind(relid);
+               obj_is_index = (relkind == RELKIND_INDEX ||
+                                               relkind == RELKIND_PARTITIONED_INDEX);
+               if (obj_is_index || is_index_stmt == obj_is_index)
+                       break;
+
+               UnlockRelationOid(relid, lockmode);
+               is_index_stmt = obj_is_index;
        }
 
        /* Do the work */
-       RenameRelationInternal(relid, stmt->newname, false, is_index);
+       RenameRelationInternal(relid, stmt->newname, false, is_index_stmt);
 
        ObjectAddressSet(address, RelationRelationId, relid);
 
@@ -3523,6 +3547,16 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
                                 errmsg("relation \"%s\" already exists",
                                                newrelname)));
 
+       /*
+        * RenameRelation is careful not to believe the caller's idea of the
+        * relation kind being handled.  We don't have to worry about this, but
+        * let's not be totally oblivious to it.  We can process an index as
+        * not-an-index, but not the other way around.
+        */
+       Assert(!is_index ||
+                  is_index == (targetrelation->rd_rel->relkind == RELKIND_INDEX ||
+                                               targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX));
+
        /*
         * Update pg_class tuple with new relname.  (Scribbling on reltup is OK
         * because it's a copy...)
index d507b7efd5783bda4f0d9f726791c236cf6b0250..11693c9e2ab989e66168da3fe01c649422daa924 100644 (file)
@@ -237,6 +237,54 @@ SET ROLE regress_alter_table_user1;
 ALTER INDEX onek_unique1 RENAME TO fail;  -- permission denied
 ERROR:  must be owner of index onek_unique1
 RESET ROLE;
+-- rename statements with mismatching statement and object types
+CREATE TABLE alter_idx_rename_test (a INT);
+CREATE INDEX alter_idx_rename_test_idx ON alter_idx_rename_test (a);
+CREATE TABLE alter_idx_rename_test_parted (a INT) PARTITION BY LIST (a);
+CREATE INDEX alter_idx_rename_test_parted_idx ON alter_idx_rename_test_parted (a);
+BEGIN;
+ALTER INDEX alter_idx_rename_test RENAME TO alter_idx_rename_test_2;
+ALTER INDEX alter_idx_rename_test_parted RENAME TO alter_idx_rename_test_parted_2;
+SELECT relation::regclass, mode FROM pg_locks
+WHERE pid = pg_backend_pid() AND locktype = 'relation'
+  AND relation::regclass::text LIKE 'alter\_idx%'
+ORDER BY relation::regclass::text;
+            relation            |        mode         
+--------------------------------+---------------------
+ alter_idx_rename_test_2        | AccessExclusiveLock
+ alter_idx_rename_test_parted_2 | AccessExclusiveLock
+(2 rows)
+
+COMMIT;
+BEGIN;
+ALTER INDEX alter_idx_rename_test_idx RENAME TO alter_idx_rename_test_idx_2;
+ALTER INDEX alter_idx_rename_test_parted_idx RENAME TO alter_idx_rename_test_parted_idx_2;
+SELECT relation::regclass, mode FROM pg_locks
+WHERE pid = pg_backend_pid() AND locktype = 'relation'
+  AND relation::regclass::text LIKE 'alter\_idx%'
+ORDER BY relation::regclass::text;
+              relation              |           mode           
+------------------------------------+--------------------------
+ alter_idx_rename_test_idx_2        | ShareUpdateExclusiveLock
+ alter_idx_rename_test_parted_idx_2 | ShareUpdateExclusiveLock
+(2 rows)
+
+COMMIT;
+BEGIN;
+ALTER TABLE alter_idx_rename_test_idx_2 RENAME TO alter_idx_rename_test_idx_3;
+ALTER TABLE alter_idx_rename_test_parted_idx_2 RENAME TO alter_idx_rename_test_parted_idx_3;
+SELECT relation::regclass, mode FROM pg_locks
+WHERE pid = pg_backend_pid() AND locktype = 'relation'
+  AND relation::regclass::text LIKE 'alter\_idx%'
+ORDER BY relation::regclass::text;
+              relation              |        mode         
+------------------------------------+---------------------
+ alter_idx_rename_test_idx_3        | AccessExclusiveLock
+ alter_idx_rename_test_parted_idx_3 | AccessExclusiveLock
+(2 rows)
+
+COMMIT;
+DROP TABLE alter_idx_rename_test_2;
 -- renaming views
 CREATE VIEW attmp_view (unique1) AS SELECT unique1 FROM tenk1;
 ALTER TABLE attmp_view RENAME TO attmp_view_new;
index 1e94117fc01578e8037e9734bae7b7b0a2d28029..690873732cec53543090e3f746c19698288ec1eb 100644 (file)
@@ -231,6 +231,37 @@ SET ROLE regress_alter_table_user1;
 ALTER INDEX onek_unique1 RENAME TO fail;  -- permission denied
 RESET ROLE;
 
+-- rename statements with mismatching statement and object types
+CREATE TABLE alter_idx_rename_test (a INT);
+CREATE INDEX alter_idx_rename_test_idx ON alter_idx_rename_test (a);
+CREATE TABLE alter_idx_rename_test_parted (a INT) PARTITION BY LIST (a);
+CREATE INDEX alter_idx_rename_test_parted_idx ON alter_idx_rename_test_parted (a);
+BEGIN;
+ALTER INDEX alter_idx_rename_test RENAME TO alter_idx_rename_test_2;
+ALTER INDEX alter_idx_rename_test_parted RENAME TO alter_idx_rename_test_parted_2;
+SELECT relation::regclass, mode FROM pg_locks
+WHERE pid = pg_backend_pid() AND locktype = 'relation'
+  AND relation::regclass::text LIKE 'alter\_idx%'
+ORDER BY relation::regclass::text;
+COMMIT;
+BEGIN;
+ALTER INDEX alter_idx_rename_test_idx RENAME TO alter_idx_rename_test_idx_2;
+ALTER INDEX alter_idx_rename_test_parted_idx RENAME TO alter_idx_rename_test_parted_idx_2;
+SELECT relation::regclass, mode FROM pg_locks
+WHERE pid = pg_backend_pid() AND locktype = 'relation'
+  AND relation::regclass::text LIKE 'alter\_idx%'
+ORDER BY relation::regclass::text;
+COMMIT;
+BEGIN;
+ALTER TABLE alter_idx_rename_test_idx_2 RENAME TO alter_idx_rename_test_idx_3;
+ALTER TABLE alter_idx_rename_test_parted_idx_2 RENAME TO alter_idx_rename_test_parted_idx_3;
+SELECT relation::regclass, mode FROM pg_locks
+WHERE pid = pg_backend_pid() AND locktype = 'relation'
+  AND relation::regclass::text LIKE 'alter\_idx%'
+ORDER BY relation::regclass::text;
+COMMIT;
+DROP TABLE alter_idx_rename_test_2;
+
 -- renaming views
 CREATE VIEW attmp_view (unique1) AS SELECT unique1 FROM tenk1;
 ALTER TABLE attmp_view RENAME TO attmp_view_new;