diff options
Diffstat (limited to 'src/backend/commands/tablecmds.c')
-rw-r--r-- | src/backend/commands/tablecmds.c | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index c86b9990e3d..feceed7f3ac 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -50,6 +50,7 @@ #include "commands/tablespace.h" #include "commands/trigger.h" #include "commands/typecmds.h" +#include "commands/user.h" #include "executor/executor.h" #include "foreign/foreign.h" #include "miscadmin.h" @@ -9204,6 +9205,176 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode) } /* + * Alter Table ALL ... SET TABLESPACE + * + * Allows a user to move all objects of some type in a given tablespace in the + * current database to another tablespace. Objects can be chosen based on the + * owner of the object also, to allow users to move only their objects. + * The user must have CREATE rights on the new tablespace, as usual. The main + * permissions handling is done by the lower-level table move function. + * + * All to-be-moved objects are locked first. If NOWAIT is specified and the + * lock can't be acquired then we ereport(ERROR). + */ +Oid +AlterTableMoveAll(AlterTableMoveAllStmt *stmt) +{ + List *relations = NIL; + ListCell *l; + ScanKeyData key[1]; + Relation rel; + HeapScanDesc scan; + HeapTuple tuple; + Oid orig_tablespaceoid; + Oid new_tablespaceoid; + List *role_oids = roleNamesToIds(stmt->roles); + + /* Ensure we were not asked to move something we can't */ + if (stmt->objtype != OBJECT_TABLE && stmt->objtype != OBJECT_INDEX && + stmt->objtype != OBJECT_MATVIEW) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("only tables, indexes, and materialized views exist in tablespaces"))); + + /* Get the orig and new tablespace OIDs */ + orig_tablespaceoid = get_tablespace_oid(stmt->orig_tablespacename, false); + new_tablespaceoid = get_tablespace_oid(stmt->new_tablespacename, false); + + /* Can't move shared relations in to or out of pg_global */ + /* This is also checked by ATExecSetTableSpace, but nice to stop earlier */ + if (orig_tablespaceoid == GLOBALTABLESPACE_OID || + new_tablespaceoid == GLOBALTABLESPACE_OID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot move relations in to or out of pg_global tablespace"))); + + /* + * Must have CREATE rights on the new tablespace, unless it is the + * database default tablespace (which all users implicitly have CREATE + * rights on). + */ + if (OidIsValid(new_tablespaceoid) && new_tablespaceoid != MyDatabaseTableSpace) + { + AclResult aclresult; + + aclresult = pg_tablespace_aclcheck(new_tablespaceoid, GetUserId(), + ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_TABLESPACE, + get_tablespace_name(new_tablespaceoid)); + } + + /* + * Now that the checks are done, check if we should set either to + * InvalidOid because it is our database's default tablespace. + */ + if (orig_tablespaceoid == MyDatabaseTableSpace) + orig_tablespaceoid = InvalidOid; + + if (new_tablespaceoid == MyDatabaseTableSpace) + new_tablespaceoid = InvalidOid; + + /* no-op */ + if (orig_tablespaceoid == new_tablespaceoid) + return new_tablespaceoid; + + /* + * Walk the list of objects in the tablespace and move them. This will + * only find objects in our database, of course. + */ + ScanKeyInit(&key[0], + Anum_pg_class_reltablespace, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(orig_tablespaceoid)); + + rel = heap_open(RelationRelationId, AccessShareLock); + scan = heap_beginscan_catalog(rel, 1, key); + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + Oid relOid = HeapTupleGetOid(tuple); + Form_pg_class relForm; + + relForm = (Form_pg_class) GETSTRUCT(tuple); + + /* + * Do not move objects in pg_catalog as part of this, if an admin + * really wishes to do so, they can issue the individual ALTER + * commands directly. + * + * Also, explicitly avoid any shared tables, temp tables, or TOAST + * (TOAST will be moved with the main table). + */ + if (IsSystemNamespace(relForm->relnamespace) || relForm->relisshared || + isAnyTempNamespace(relForm->relnamespace) || + relForm->relnamespace == PG_TOAST_NAMESPACE) + continue; + + /* Only move the object type requested */ + if ((stmt->objtype == OBJECT_TABLE && + relForm->relkind != RELKIND_RELATION) || + (stmt->objtype == OBJECT_INDEX && + relForm->relkind != RELKIND_INDEX) || + (stmt->objtype == OBJECT_MATVIEW && + relForm->relkind != RELKIND_MATVIEW)) + continue; + + /* Check if we are only moving objects owned by certain roles */ + if (role_oids != NIL && !list_member_oid(role_oids, relForm->relowner)) + continue; + + /* + * Handle permissions-checking here since we are locking the tables + * and also to avoid doing a bunch of work only to fail part-way. Note + * that permissions will also be checked by AlterTableInternal(). + * + * Caller must be considered an owner on the table to move it. + */ + if (!pg_class_ownercheck(relOid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, + NameStr(relForm->relname)); + + if (stmt->nowait && + !ConditionalLockRelationOid(relOid, AccessExclusiveLock)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_IN_USE), + errmsg("aborting due to \"%s\".\"%s\" --- lock not available", + get_namespace_name(relForm->relnamespace), + NameStr(relForm->relname)))); + else + LockRelationOid(relOid, AccessExclusiveLock); + + /* Add to our list of objects to move */ + relations = lappend_oid(relations, relOid); + } + + heap_endscan(scan); + heap_close(rel, AccessShareLock); + + if (relations == NIL) + ereport(NOTICE, + (errcode(ERRCODE_NO_DATA_FOUND), + errmsg("no matching relations in tablespace \"%s\" found", + orig_tablespaceoid == InvalidOid ? "(database default)" : + get_tablespace_name(orig_tablespaceoid)))); + + /* Everything is locked, loop through and move all of the relations. */ + foreach(l, relations) + { + List *cmds = NIL; + AlterTableCmd *cmd = makeNode(AlterTableCmd); + + cmd->subtype = AT_SetTableSpace; + cmd->name = stmt->new_tablespacename; + + cmds = lappend(cmds, cmd); + + AlterTableInternal(lfirst_oid(l), cmds, false); + } + + return new_tablespaceoid; +} + +/* * Copy data, block by block */ static void |