|
50 | 50 | #include "commands/tablespace.h"
|
51 | 51 | #include "commands/trigger.h"
|
52 | 52 | #include "commands/typecmds.h"
|
| 53 | +#include "commands/user.h" |
53 | 54 | #include "executor/executor.h"
|
54 | 55 | #include "foreign/foreign.h"
|
55 | 56 | #include "miscadmin.h"
|
@@ -9203,6 +9204,176 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
|
9203 | 9204 | list_free(reltoastidxids);
|
9204 | 9205 | }
|
9205 | 9206 |
|
| 9207 | +/* |
| 9208 | + * Alter Table ALL ... SET TABLESPACE |
| 9209 | + * |
| 9210 | + * Allows a user to move all objects of some type in a given tablespace in the |
| 9211 | + * current database to another tablespace. Objects can be chosen based on the |
| 9212 | + * owner of the object also, to allow users to move only their objects. |
| 9213 | + * The user must have CREATE rights on the new tablespace, as usual. The main |
| 9214 | + * permissions handling is done by the lower-level table move function. |
| 9215 | + * |
| 9216 | + * All to-be-moved objects are locked first. If NOWAIT is specified and the |
| 9217 | + * lock can't be acquired then we ereport(ERROR). |
| 9218 | + */ |
| 9219 | +Oid |
| 9220 | +AlterTableMoveAll(AlterTableMoveAllStmt *stmt) |
| 9221 | +{ |
| 9222 | + List *relations = NIL; |
| 9223 | + ListCell *l; |
| 9224 | + ScanKeyData key[1]; |
| 9225 | + Relation rel; |
| 9226 | + HeapScanDesc scan; |
| 9227 | + HeapTuple tuple; |
| 9228 | + Oid orig_tablespaceoid; |
| 9229 | + Oid new_tablespaceoid; |
| 9230 | + List *role_oids = roleNamesToIds(stmt->roles); |
| 9231 | + |
| 9232 | + /* Ensure we were not asked to move something we can't */ |
| 9233 | + if (stmt->objtype != OBJECT_TABLE && stmt->objtype != OBJECT_INDEX && |
| 9234 | + stmt->objtype != OBJECT_MATVIEW) |
| 9235 | + ereport(ERROR, |
| 9236 | + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 9237 | + errmsg("only tables, indexes, and materialized views exist in tablespaces"))); |
| 9238 | + |
| 9239 | + /* Get the orig and new tablespace OIDs */ |
| 9240 | + orig_tablespaceoid = get_tablespace_oid(stmt->orig_tablespacename, false); |
| 9241 | + new_tablespaceoid = get_tablespace_oid(stmt->new_tablespacename, false); |
| 9242 | + |
| 9243 | + /* Can't move shared relations in to or out of pg_global */ |
| 9244 | + /* This is also checked by ATExecSetTableSpace, but nice to stop earlier */ |
| 9245 | + if (orig_tablespaceoid == GLOBALTABLESPACE_OID || |
| 9246 | + new_tablespaceoid == GLOBALTABLESPACE_OID) |
| 9247 | + ereport(ERROR, |
| 9248 | + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 9249 | + errmsg("cannot move relations in to or out of pg_global tablespace"))); |
| 9250 | + |
| 9251 | + /* |
| 9252 | + * Must have CREATE rights on the new tablespace, unless it is the |
| 9253 | + * database default tablespace (which all users implicitly have CREATE |
| 9254 | + * rights on). |
| 9255 | + */ |
| 9256 | + if (OidIsValid(new_tablespaceoid) && new_tablespaceoid != MyDatabaseTableSpace) |
| 9257 | + { |
| 9258 | + AclResult aclresult; |
| 9259 | + |
| 9260 | + aclresult = pg_tablespace_aclcheck(new_tablespaceoid, GetUserId(), |
| 9261 | + ACL_CREATE); |
| 9262 | + if (aclresult != ACLCHECK_OK) |
| 9263 | + aclcheck_error(aclresult, ACL_KIND_TABLESPACE, |
| 9264 | + get_tablespace_name(new_tablespaceoid)); |
| 9265 | + } |
| 9266 | + |
| 9267 | + /* |
| 9268 | + * Now that the checks are done, check if we should set either to |
| 9269 | + * InvalidOid because it is our database's default tablespace. |
| 9270 | + */ |
| 9271 | + if (orig_tablespaceoid == MyDatabaseTableSpace) |
| 9272 | + orig_tablespaceoid = InvalidOid; |
| 9273 | + |
| 9274 | + if (new_tablespaceoid == MyDatabaseTableSpace) |
| 9275 | + new_tablespaceoid = InvalidOid; |
| 9276 | + |
| 9277 | + /* no-op */ |
| 9278 | + if (orig_tablespaceoid == new_tablespaceoid) |
| 9279 | + return new_tablespaceoid; |
| 9280 | + |
| 9281 | + /* |
| 9282 | + * Walk the list of objects in the tablespace and move them. This will |
| 9283 | + * only find objects in our database, of course. |
| 9284 | + */ |
| 9285 | + ScanKeyInit(&key[0], |
| 9286 | + Anum_pg_class_reltablespace, |
| 9287 | + BTEqualStrategyNumber, F_OIDEQ, |
| 9288 | + ObjectIdGetDatum(orig_tablespaceoid)); |
| 9289 | + |
| 9290 | + rel = heap_open(RelationRelationId, AccessShareLock); |
| 9291 | + scan = heap_beginscan_catalog(rel, 1, key); |
| 9292 | + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) |
| 9293 | + { |
| 9294 | + Oid relOid = HeapTupleGetOid(tuple); |
| 9295 | + Form_pg_class relForm; |
| 9296 | + |
| 9297 | + relForm = (Form_pg_class) GETSTRUCT(tuple); |
| 9298 | + |
| 9299 | + /* |
| 9300 | + * Do not move objects in pg_catalog as part of this, if an admin |
| 9301 | + * really wishes to do so, they can issue the individual ALTER |
| 9302 | + * commands directly. |
| 9303 | + * |
| 9304 | + * Also, explicitly avoid any shared tables, temp tables, or TOAST |
| 9305 | + * (TOAST will be moved with the main table). |
| 9306 | + */ |
| 9307 | + if (IsSystemNamespace(relForm->relnamespace) || relForm->relisshared || |
| 9308 | + isAnyTempNamespace(relForm->relnamespace) || |
| 9309 | + relForm->relnamespace == PG_TOAST_NAMESPACE) |
| 9310 | + continue; |
| 9311 | + |
| 9312 | + /* Only move the object type requested */ |
| 9313 | + if ((stmt->objtype == OBJECT_TABLE && |
| 9314 | + relForm->relkind != RELKIND_RELATION) || |
| 9315 | + (stmt->objtype == OBJECT_INDEX && |
| 9316 | + relForm->relkind != RELKIND_INDEX) || |
| 9317 | + (stmt->objtype == OBJECT_MATVIEW && |
| 9318 | + relForm->relkind != RELKIND_MATVIEW)) |
| 9319 | + continue; |
| 9320 | + |
| 9321 | + /* Check if we are only moving objects owned by certain roles */ |
| 9322 | + if (role_oids != NIL && !list_member_oid(role_oids, relForm->relowner)) |
| 9323 | + continue; |
| 9324 | + |
| 9325 | + /* |
| 9326 | + * Handle permissions-checking here since we are locking the tables |
| 9327 | + * and also to avoid doing a bunch of work only to fail part-way. Note |
| 9328 | + * that permissions will also be checked by AlterTableInternal(). |
| 9329 | + * |
| 9330 | + * Caller must be considered an owner on the table to move it. |
| 9331 | + */ |
| 9332 | + if (!pg_class_ownercheck(relOid, GetUserId())) |
| 9333 | + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, |
| 9334 | + NameStr(relForm->relname)); |
| 9335 | + |
| 9336 | + if (stmt->nowait && |
| 9337 | + !ConditionalLockRelationOid(relOid, AccessExclusiveLock)) |
| 9338 | + ereport(ERROR, |
| 9339 | + (errcode(ERRCODE_OBJECT_IN_USE), |
| 9340 | + errmsg("aborting due to \"%s\".\"%s\" --- lock not available", |
| 9341 | + get_namespace_name(relForm->relnamespace), |
| 9342 | + NameStr(relForm->relname)))); |
| 9343 | + else |
| 9344 | + LockRelationOid(relOid, AccessExclusiveLock); |
| 9345 | + |
| 9346 | + /* Add to our list of objects to move */ |
| 9347 | + relations = lappend_oid(relations, relOid); |
| 9348 | + } |
| 9349 | + |
| 9350 | + heap_endscan(scan); |
| 9351 | + heap_close(rel, AccessShareLock); |
| 9352 | + |
| 9353 | + if (relations == NIL) |
| 9354 | + ereport(NOTICE, |
| 9355 | + (errcode(ERRCODE_NO_DATA_FOUND), |
| 9356 | + errmsg("no matching relations in tablespace \"%s\" found", |
| 9357 | + orig_tablespaceoid == InvalidOid ? "(database default)" : |
| 9358 | + get_tablespace_name(orig_tablespaceoid)))); |
| 9359 | + |
| 9360 | + /* Everything is locked, loop through and move all of the relations. */ |
| 9361 | + foreach(l, relations) |
| 9362 | + { |
| 9363 | + List *cmds = NIL; |
| 9364 | + AlterTableCmd *cmd = makeNode(AlterTableCmd); |
| 9365 | + |
| 9366 | + cmd->subtype = AT_SetTableSpace; |
| 9367 | + cmd->name = stmt->new_tablespacename; |
| 9368 | + |
| 9369 | + cmds = lappend(cmds, cmd); |
| 9370 | + |
| 9371 | + AlterTableInternal(lfirst_oid(l), cmds, false); |
| 9372 | + } |
| 9373 | + |
| 9374 | + return new_tablespaceoid; |
| 9375 | +} |
| 9376 | + |
9206 | 9377 | /*
|
9207 | 9378 | * Copy data, block by block
|
9208 | 9379 | */
|
|
0 commit comments