diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index d61f8d526f..fe6ee63f6a 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -3406,7 +3406,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; @@ -3416,24 +3416,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); @@ -3481,6 +3505,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...) diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index a3e27efd8a..268720168e 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -232,6 +232,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; diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index 1e94b30872..45cb960db0 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -227,6 +227,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;