/* * Alter table space options */ Oid AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt) { Relation rel; ScanKeyData entry[1]; HeapScanDesc scandesc; HeapTuple tup; Oid tablespaceoid; Datum datum; Datum newOptions; Datum repl_val[Natts_pg_tablespace]; bool isnull; bool repl_null[Natts_pg_tablespace]; bool repl_repl[Natts_pg_tablespace]; HeapTuple newtuple; /* Search pg_tablespace */ rel = heap_open(TableSpaceRelationId, RowExclusiveLock); ScanKeyInit(&entry[0], Anum_pg_tablespace_spcname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(stmt->tablespacename)); scandesc = heap_beginscan_catalog(rel, 1, entry); tup = heap_getnext(scandesc, ForwardScanDirection); if (!HeapTupleIsValid(tup)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("tablespace \"%s\" does not exist", stmt->tablespacename))); tablespaceoid = HeapTupleGetOid(tup); /* Must be owner of the existing object */ if (!pg_tablespace_ownercheck(HeapTupleGetOid(tup), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE, stmt->tablespacename); /* Generate new proposed spcoptions (text array) */ datum = heap_getattr(tup, Anum_pg_tablespace_spcoptions, RelationGetDescr(rel), &isnull); newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, stmt->options, NULL, NULL, false, stmt->isReset); (void) tablespace_reloptions(newOptions, true); /* Build new tuple. */ memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); if (newOptions != (Datum) 0) repl_val[Anum_pg_tablespace_spcoptions - 1] = newOptions; else repl_null[Anum_pg_tablespace_spcoptions - 1] = true; repl_repl[Anum_pg_tablespace_spcoptions - 1] = true; newtuple = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null, repl_repl); /* Update system catalog. */ simple_heap_update(rel, &newtuple->t_self, newtuple); CatalogUpdateIndexes(rel, newtuple); InvokeObjectPostAlterHook(TableSpaceRelationId, HeapTupleGetOid(tup), 0); heap_freetuple(newtuple); /* Conclude heap scan. */ heap_endscan(scandesc); heap_close(rel, NoLock); return tablespaceoid; }
/* * Drop a table space * * Be careful to check that the tablespace is empty. */ void DropTableSpace(DropTableSpaceStmt *stmt) { #ifdef HAVE_SYMLINK char *tablespacename = stmt->tablespacename; HeapScanDesc scandesc; Relation rel; HeapTuple tuple; ScanKeyData entry[1]; Oid tablespaceoid; /* * Find the target tuple */ rel = heap_open(TableSpaceRelationId, RowExclusiveLock); ScanKeyInit(&entry[0], Anum_pg_tablespace_spcname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(tablespacename)); scandesc = heap_beginscan_catalog(rel, 1, entry); tuple = heap_getnext(scandesc, ForwardScanDirection); if (!HeapTupleIsValid(tuple)) { if (!stmt->missing_ok) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("tablespace \"%s\" does not exist", tablespacename))); } else { ereport(NOTICE, (errmsg("tablespace \"%s\" does not exist, skipping", tablespacename))); /* XXX I assume I need one or both of these next two calls */ heap_endscan(scandesc); heap_close(rel, NoLock); } return; } tablespaceoid = HeapTupleGetOid(tuple); /* Must be tablespace owner */ if (!pg_tablespace_ownercheck(tablespaceoid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE, tablespacename); /* Disallow drop of the standard tablespaces, even by superuser */ if (tablespaceoid == GLOBALTABLESPACE_OID || tablespaceoid == DEFAULTTABLESPACE_OID) aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_TABLESPACE, tablespacename); /* DROP hook for the tablespace being removed */ InvokeObjectDropHook(TableSpaceRelationId, tablespaceoid, 0); /* * Remove the pg_tablespace tuple (this will roll back if we fail below) */ simple_heap_delete(rel, &tuple->t_self); heap_endscan(scandesc); /* * Remove any comments or security labels on this tablespace. */ DeleteSharedComments(tablespaceoid, TableSpaceRelationId); DeleteSharedSecurityLabel(tablespaceoid, TableSpaceRelationId); /* * Remove dependency on owner. */ deleteSharedDependencyRecordsFor(TableSpaceRelationId, tablespaceoid, 0); /* * Acquire TablespaceCreateLock to ensure that no TablespaceCreateDbspace * is running concurrently. */ LWLockAcquire(TablespaceCreateLock, LW_EXCLUSIVE); /* * Try to remove the physical infrastructure. */ if (!destroy_tablespace_directories(tablespaceoid, false)) { /* * Not all files deleted? However, there can be lingering empty files * in the directories, left behind by for example DROP TABLE, that * have been scheduled for deletion at next checkpoint (see comments * in mdunlink() for details). We could just delete them immediately, * but we can't tell them apart from important data files that we * mustn't delete. So instead, we force a checkpoint which will clean * out any lingering files, and try again. * * XXX On Windows, an unlinked file persists in the directory listing * until no process retains an open handle for the file. The DDL * commands that schedule files for unlink send invalidation messages * directing other PostgreSQL processes to close the files. DROP * TABLESPACE should not give up on the tablespace becoming empty * until all relevant invalidation processing is complete. */ RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_FORCE | CHECKPOINT_WAIT); if (!destroy_tablespace_directories(tablespaceoid, false)) { /* Still not empty, the files must be important then */ ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("tablespace \"%s\" is not empty", tablespacename))); } } /* Record the filesystem change in XLOG */ { xl_tblspc_drop_rec xlrec; xlrec.ts_id = tablespaceoid; XLogBeginInsert(); XLogRegisterData((char *) &xlrec, sizeof(xl_tblspc_drop_rec)); (void) XLogInsert(RM_TBLSPC_ID, XLOG_TBLSPC_DROP); } /* * Note: because we checked that the tablespace was empty, there should be * no need to worry about flushing shared buffers or free space map * entries for relations in the tablespace. */ /* * Force synchronous commit, to minimize the window between removing the * files on-disk and marking the transaction committed. It's not great * that there is any window at all, but definitely we don't want to make * it larger than necessary. */ ForceSyncCommit(); /* * Allow TablespaceCreateDbspace again. */ LWLockRelease(TablespaceCreateLock); /* We keep the lock on pg_tablespace until commit */ heap_close(rel, NoLock); #else /* !HAVE_SYMLINK */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("tablespaces are not supported on this platform"))); #endif /* HAVE_SYMLINK */ }
/* * Rename a tablespace */ ObjectAddress RenameTableSpace(const char *oldname, const char *newname) { Oid tspId; Relation rel; ScanKeyData entry[1]; HeapScanDesc scan; HeapTuple tup; HeapTuple newtuple; Form_pg_tablespace newform; ObjectAddress address; /* Search pg_tablespace */ rel = heap_open(TableSpaceRelationId, RowExclusiveLock); ScanKeyInit(&entry[0], Anum_pg_tablespace_spcname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(oldname)); scan = heap_beginscan_catalog(rel, 1, entry); tup = heap_getnext(scan, ForwardScanDirection); if (!HeapTupleIsValid(tup)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("tablespace \"%s\" does not exist", oldname))); tspId = HeapTupleGetOid(tup); newtuple = heap_copytuple(tup); newform = (Form_pg_tablespace) GETSTRUCT(newtuple); heap_endscan(scan); /* Must be owner */ if (!pg_tablespace_ownercheck(HeapTupleGetOid(newtuple), GetUserId())) aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_TABLESPACE, oldname); /* Validate new name */ if (!allowSystemTableMods && IsReservedName(newname)) ereport(ERROR, (errcode(ERRCODE_RESERVED_NAME), errmsg("unacceptable tablespace name \"%s\"", newname), errdetail("The prefix \"pg_\" is reserved for system tablespaces."))); /* Make sure the new name doesn't exist */ ScanKeyInit(&entry[0], Anum_pg_tablespace_spcname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(newname)); scan = heap_beginscan_catalog(rel, 1, entry); tup = heap_getnext(scan, ForwardScanDirection); if (HeapTupleIsValid(tup)) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("tablespace \"%s\" already exists", newname))); heap_endscan(scan); /* OK, update the entry */ namestrcpy(&(newform->spcname), newname); simple_heap_update(rel, &newtuple->t_self, newtuple); CatalogUpdateIndexes(rel, newtuple); InvokeObjectPostAlterHook(TableSpaceRelationId, tspId, 0); ObjectAddressSet(address, TableSpaceRelationId, tspId); heap_close(rel, NoLock); return address; }
/* * Change tablespace owner */ void AlterTableSpaceOwner(const char *name, Oid newOwnerId) { Relation rel; ScanKeyData entry[1]; HeapScanDesc scandesc; Form_pg_tablespace spcForm; HeapTuple tup; /* Search pg_tablespace */ rel = heap_open(TableSpaceRelationId, RowExclusiveLock); ScanKeyInit(&entry[0], Anum_pg_tablespace_spcname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(name)); scandesc = heap_beginscan(rel, SnapshotNow, 1, entry); tup = heap_getnext(scandesc, ForwardScanDirection); if (!HeapTupleIsValid(tup)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("tablespace \"%s\" does not exist", name))); spcForm = (Form_pg_tablespace) GETSTRUCT(tup); /* * If the new owner is the same as the existing owner, consider the * command to have succeeded. This is for dump restoration purposes. */ if (spcForm->spcowner != newOwnerId) { Datum repl_val[Natts_pg_tablespace]; bool repl_null[Natts_pg_tablespace]; bool repl_repl[Natts_pg_tablespace]; Acl *newAcl; Datum aclDatum; bool isNull; HeapTuple newtuple; /* Otherwise, must be owner of the existing object */ if (!pg_tablespace_ownercheck(HeapTupleGetOid(tup), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE, name); /* Must be able to become new owner */ check_is_member_of_role(GetUserId(), newOwnerId); /* * Normally we would also check for create permissions here, but there * are none for tablespaces so we follow what rename tablespace does * and omit the create permissions check. * * NOTE: Only superusers may create tablespaces to begin with and so * initially only a superuser would be able to change its ownership * anyway. */ memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); repl_repl[Anum_pg_tablespace_spcowner - 1] = true; repl_val[Anum_pg_tablespace_spcowner - 1] = ObjectIdGetDatum(newOwnerId); /* * Determine the modified ACL for the new owner. This is only * necessary when the ACL is non-null. */ aclDatum = heap_getattr(tup, Anum_pg_tablespace_spcacl, RelationGetDescr(rel), &isNull); if (!isNull) { newAcl = aclnewowner(DatumGetAclP(aclDatum), spcForm->spcowner, newOwnerId); repl_repl[Anum_pg_tablespace_spcacl - 1] = true; repl_val[Anum_pg_tablespace_spcacl - 1] = PointerGetDatum(newAcl); } newtuple = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null, repl_repl); simple_heap_update(rel, &newtuple->t_self, newtuple); CatalogUpdateIndexes(rel, newtuple); heap_freetuple(newtuple); /* Update owner dependency reference */ changeDependencyOnOwner(TableSpaceRelationId, HeapTupleGetOid(tup), newOwnerId); } heap_endscan(scandesc); heap_close(rel, NoLock); }
/* * CommentObject -- * * This routine is used to add the associated comment into * pg_description for the object specified by the given SQL command. */ void CommentObject(CommentStmt *stmt) { ObjectAddress address; Relation relation; /* * When loading a dump, we may see a COMMENT ON DATABASE for the old name * of the database. Erroring out would prevent pg_restore from completing * (which is really pg_restore's fault, but for now we will work around * the problem here). Consensus is that the best fix is to treat wrong * database name as a WARNING not an ERROR; hence, the following special * case. (If the length of stmt->objname is not 1, get_object_address will * throw an error below; that's OK.) */ if (stmt->objtype == OBJECT_DATABASE && list_length(stmt->objname) == 1) { char *database = strVal(linitial(stmt->objname)); if (!OidIsValid(get_database_oid(database, true))) { ereport(WARNING, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%s\" does not exist", database))); return; } } /* * Translate the parser representation which identifies this object into * an ObjectAddress. get_object_address() will throw an error if the * object does not exist, and will also acquire a lock on the target * to guard against concurrent DROP operations. */ address = get_object_address(stmt->objtype, stmt->objname, stmt->objargs, &relation, ShareUpdateExclusiveLock); /* Privilege and integrity checks. */ switch (stmt->objtype) { case OBJECT_INDEX: case OBJECT_SEQUENCE: case OBJECT_TABLE: case OBJECT_VIEW: if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(relation)); break; case OBJECT_COLUMN: CheckAttributeComment(relation); break; case OBJECT_DATABASE: if (!pg_database_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, strVal(linitial(stmt->objname))); break; case OBJECT_TYPE: if (!pg_type_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE, format_type_be(address.objectId)); break; case OBJECT_AGGREGATE: case OBJECT_FUNCTION: if (!pg_proc_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->objname)); break; case OBJECT_OPERATOR: if (!pg_oper_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPER, NameListToString(stmt->objname)); break; case OBJECT_RULE: case OBJECT_TRIGGER: case OBJECT_CONSTRAINT: if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(relation)); break; case OBJECT_SCHEMA: if (!pg_namespace_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE, strVal(linitial(stmt->objname))); break; case OBJECT_CONVERSION: if (!pg_conversion_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CONVERSION, NameListToString(stmt->objname)); break; case OBJECT_LANGUAGE: if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to comment on procedural language"))); break; case OBJECT_OPCLASS: if (!pg_opclass_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS, NameListToString(stmt->objname)); break; case OBJECT_OPFAMILY: if (!pg_opfamily_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPFAMILY, NameListToString(stmt->objname)); break; case OBJECT_LARGEOBJECT: if (!lo_compat_privileges && !pg_largeobject_ownercheck(address.objectId, GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be owner of large object %u", address.objectId))); break; case OBJECT_CAST: CheckCastComment(stmt->objname, stmt->objargs); break; case OBJECT_TABLESPACE: if (!pg_tablespace_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE, strVal(linitial(stmt->objname))); break; case OBJECT_ROLE: if (!has_privs_of_role(GetUserId(), address.objectId)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be member of role \"%s\" to comment upon it", strVal(linitial(stmt->objname))))); break; case OBJECT_TSPARSER: if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to comment on text search parser"))); break; case OBJECT_TSDICTIONARY: if (!pg_ts_dict_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY, NameListToString(stmt->objname)); break; case OBJECT_TSTEMPLATE: if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to comment on text search template"))); break; case OBJECT_TSCONFIGURATION: if (!pg_ts_config_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION, NameListToString(stmt->objname)); break; default: elog(ERROR, "unrecognized object type: %d", (int) stmt->objtype); } /* * Databases, tablespaces, and roles are cluster-wide objects, so any * comments on those objects are recorded in the shared pg_shdescription * catalog. Comments on all other objects are recorded in pg_description. */ if (stmt->objtype == OBJECT_DATABASE || stmt->objtype == OBJECT_TABLESPACE || stmt->objtype == OBJECT_ROLE) CreateSharedComments(address.objectId, address.classId, stmt->comment); else CreateComments(address.objectId, address.classId, address.objectSubId, stmt->comment); /* * If get_object_address() opened the relation for us, we close it to keep * the reference count correct - but we retain any locks acquired by * get_object_address() until commit time, to guard against concurrent * activity. */ if (relation != NULL) relation_close(relation, NoLock); }
/* * Change tablespace owner */ void AlterTableSpaceOwner(const char *name, Oid newOwnerId) { Relation rel; Oid tablespaceoid; cqContext cqc; cqContext *pcqCtx; Form_pg_tablespace spcForm; HeapTuple tup; /* Search pg_tablespace */ rel = heap_open(TableSpaceRelationId, RowExclusiveLock); pcqCtx = caql_addrel(cqclr(&cqc), rel); tup = caql_getfirst( pcqCtx, cql("SELECT * FROM pg_tablespace " " WHERE spcname = :1 " " FOR UPDATE ", CStringGetDatum(name))); if (!HeapTupleIsValid(tup)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("tablespace \"%s\" does not exist", name))); tablespaceoid = HeapTupleGetOid(tup); spcForm = (Form_pg_tablespace) GETSTRUCT(tup); /* * If the new owner is the same as the existing owner, consider the * command to have succeeded. This is for dump restoration purposes. */ if (spcForm->spcowner != newOwnerId) { Datum repl_val[Natts_pg_tablespace]; bool repl_null[Natts_pg_tablespace]; bool repl_repl[Natts_pg_tablespace]; Acl *newAcl; Datum aclDatum; bool isNull; HeapTuple newtuple; /* Otherwise, must be owner of the existing object */ if (!pg_tablespace_ownercheck(tablespaceoid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE, name); /* Must be able to become new owner */ check_is_member_of_role(GetUserId(), newOwnerId); /* * Normally we would also check for create permissions here, but there * are none for tablespaces so we follow what rename tablespace does * and omit the create permissions check. * * NOTE: Only superusers may create tablespaces to begin with and so * initially only a superuser would be able to change its ownership * anyway. */ memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); repl_repl[Anum_pg_tablespace_spcowner - 1] = true; repl_val[Anum_pg_tablespace_spcowner - 1] = ObjectIdGetDatum(newOwnerId); /* * Determine the modified ACL for the new owner. This is only * necessary when the ACL is non-null. */ aclDatum = heap_getattr(tup, Anum_pg_tablespace_spcacl, RelationGetDescr(rel), &isNull); if (!isNull) { newAcl = aclnewowner(DatumGetAclP(aclDatum), spcForm->spcowner, newOwnerId); repl_repl[Anum_pg_tablespace_spcacl - 1] = true; repl_val[Anum_pg_tablespace_spcacl - 1] = PointerGetDatum(newAcl); } newtuple = caql_modify_current(pcqCtx, repl_val, repl_null, repl_repl); caql_update_current(pcqCtx, newtuple); /* and Update indexes (implicit) */ /* MPP-6929: metadata tracking */ if (Gp_role == GP_ROLE_DISPATCH) MetaTrackUpdObject(TableSpaceRelationId, tablespaceoid, GetUserId(), "ALTER", "OWNER" ); heap_freetuple(newtuple); /* Update owner dependency reference */ changeDependencyOnOwner(TableSpaceRelationId, HeapTupleGetOid(tup), newOwnerId); } heap_close(rel, NoLock); }
/* * Rename a tablespace */ void RenameTableSpace(const char *oldname, const char *newname) { Relation rel; Oid tablespaceoid; cqContext cqc; cqContext cqc2; cqContext *pcqCtx; HeapTuple newtuple; Form_pg_tablespace newform; /* Search pg_tablespace */ rel = heap_open(TableSpaceRelationId, RowExclusiveLock); pcqCtx = caql_addrel(cqclr(&cqc), rel); newtuple = caql_getfirst( pcqCtx, cql("SELECT * FROM pg_tablespace " " WHERE spcname = :1 " " FOR UPDATE ", CStringGetDatum(oldname))); if (!HeapTupleIsValid(newtuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("tablespace \"%s\" does not exist", oldname))); newform = (Form_pg_tablespace) GETSTRUCT(newtuple); /* Must be owner */ tablespaceoid = HeapTupleGetOid(newtuple); if (!pg_tablespace_ownercheck(tablespaceoid, GetUserId())) aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_TABLESPACE, oldname); /* Validate new name */ if (!allowSystemTableModsDDL && IsReservedName(newname)) { ereport(ERROR, (errcode(ERRCODE_RESERVED_NAME), errmsg("unacceptable tablespace name \"%s\"", newname), errdetail("The prefix \"%s\" is reserved for system tablespaces.", GetReservedPrefix(newname)))); } /* Make sure the new name doesn't exist */ if (caql_getcount( caql_addrel(cqclr(&cqc2), rel), /* rely on rowexclusive */ cql("SELECT COUNT(*) FROM pg_tablespace " " WHERE spcname = :1 ", CStringGetDatum(newname)))) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("tablespace \"%s\" already exists", newname))); /* OK, update the entry */ namestrcpy(&(newform->spcname), newname); caql_update_current(pcqCtx, newtuple); /* and Update indexes (implicit) */ /* MPP-6929: metadata tracking */ if (Gp_role == GP_ROLE_DISPATCH) MetaTrackUpdObject(TableSpaceRelationId, tablespaceoid, GetUserId(), "ALTER", "RENAME" ); heap_close(rel, NoLock); }
/* * Drop a table space * * Be careful to check that the tablespace is empty. */ void RemoveTableSpace(List *names, DropBehavior behavior, bool missing_ok) { char *tablespacename; Relation rel; HeapTuple tuple; cqContext cqc; cqContext *pcqCtx; Oid tablespaceoid; int32 count; RelFileNode relfilenode; DbDirNode dbDirNode; PersistentFileSysState persistentState; ItemPointerData persistentTid; int64 persistentSerialNum; /* * General DROP (object) syntax allows fully qualified names, but * tablespaces are global objects that do not live in schemas, so * it is a syntax error if a fully qualified name was given. */ if (list_length(names) != 1) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("tablespace name may not be qualified"))); tablespacename = strVal(linitial(names)); /* Disallow CASCADE */ if (behavior == DROP_CASCADE) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("syntax at or near \"cascade\""))); /* * Find the target tuple */ rel = heap_open(TableSpaceRelationId, RowExclusiveLock); pcqCtx = caql_addrel(cqclr(&cqc), rel); tuple = caql_getfirst( pcqCtx, cql("SELECT * FROM pg_tablespace " " WHERE spcname = :1 " " FOR UPDATE ", CStringGetDatum(tablespacename))); if (!HeapTupleIsValid(tuple)) { /* No such tablespace, no need to hold the lock */ heap_close(rel, RowExclusiveLock); if (!missing_ok) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("tablespace \"%s\" does not exist", tablespacename))); } else { ereport(NOTICE, (errmsg("tablespace \"%s\" does not exist, skipping", tablespacename))); } return; } tablespaceoid = HeapTupleGetOid(tuple); /* Must be tablespace owner */ if (!pg_tablespace_ownercheck(tablespaceoid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE, tablespacename); /* Disallow drop of the standard tablespaces, even by superuser */ if (tablespaceoid == GLOBALTABLESPACE_OID || tablespaceoid == DEFAULTTABLESPACE_OID) aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_TABLESPACE, tablespacename); /* * Check for any databases or relations defined in this tablespace, this * is logically the same as checkSharedDependencies, however we don't * actually track these in pg_shdepend, instead we lookup this information * in the gp_persistent_database/relation_node tables. */ /* ... */ /* * Remove the pg_tablespace tuple (this will roll back if we fail below) */ caql_delete_current(pcqCtx); /* * Remove any comments on this tablespace. */ DeleteSharedComments(tablespaceoid, TableSpaceRelationId); /* * Remove dependency on owner. * * If shared dependencies are added between filespace <=> tablespace * they will be deleted as well. */ deleteSharedDependencyRecordsFor(TableSpaceRelationId, tablespaceoid); /* MPP-6929: metadata tracking */ if (Gp_role == GP_ROLE_DISPATCH) MetaTrackDropObject(TableSpaceRelationId, tablespaceoid); /* * Acquire TablespaceCreateLock to ensure that no * MirroredFileSysObj_JustInTimeDbDirCreate is running concurrently. */ LWLockAcquire(TablespaceCreateLock, LW_EXCLUSIVE); /* * Check for any relations still defined in the tablespace. */ PersistentRelation_CheckTablespace(tablespaceoid, &count, &relfilenode); if (count > 0) { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("tablespace \"%s\" is not empty", tablespacename))); } /* * Schedule the removal the physical infrastructure. * * Note: This only schedules the delete, the delete won't actually occur * until after the transaction has comitted. This should however do * everything it can to assure that the delete will occur sucessfully, * e.g. check permissions etc. */ /* * Schedule all persistent database directory removals for transaction commit. */ PersistentDatabase_DirIterateInit(); while (PersistentDatabase_DirIterateNext( &dbDirNode, &persistentState, &persistentTid, &persistentSerialNum)) { if (dbDirNode.tablespace != tablespaceoid) continue; /* * Database directory objects can linger in 'Drop Pending' state, etc, * when the mirror is down and needs drop work. So only pay attention * to 'Created' objects. */ if (persistentState != PersistentFileSysState_Created) continue; MirroredFileSysObj_ScheduleDropDbDir( &dbDirNode, &persistentTid, persistentSerialNum); } /* * Now schedule the tablespace directory removal. */ MirroredFileSysObj_ScheduleDropTablespaceDir(tablespaceoid); /* * Note: because we checked that the tablespace was empty, there should be * no need to worry about flushing shared buffers or free space map * entries for relations in the tablespace. * * CHECK THIS, also check if the lock makes any sense in this context. */ /* * Force synchronous commit, to minimize the window between removing the * files on-disk and marking the transaction committed. It's not great * that there is any window at all, but definitely we don't want to make * it larger than necessary. */ ForceSyncCommit(); /* * Allow MirroredFileSysObj_JustInTimeDbDirCreate again. */ LWLockRelease(TablespaceCreateLock); /* We keep the lock on the row in pg_tablespace until commit */ heap_close(rel, NoLock); /* Note: no need for dispatch, that is handled in utility.c */ return; }
/* * Check ownership of an object previously identified by get_object_address. */ void check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, List *objname, List *objargs, Relation relation) { switch (objtype) { case OBJECT_INDEX: case OBJECT_SEQUENCE: case OBJECT_TABLE: case OBJECT_VIEW: case OBJECT_FOREIGN_TABLE: case OBJECT_COLUMN: case OBJECT_RULE: case OBJECT_TRIGGER: case OBJECT_CONSTRAINT: if (!pg_class_ownercheck(RelationGetRelid(relation), roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(relation)); break; case OBJECT_DATABASE: if (!pg_database_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, NameListToString(objname)); break; case OBJECT_TYPE: case OBJECT_DOMAIN: case OBJECT_ATTRIBUTE: if (!pg_type_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE, format_type_be(address.objectId)); break; case OBJECT_AGGREGATE: case OBJECT_FUNCTION: if (!pg_proc_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(objname)); break; case OBJECT_OPERATOR: if (!pg_oper_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPER, NameListToString(objname)); break; case OBJECT_SCHEMA: if (!pg_namespace_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE, NameListToString(objname)); break; case OBJECT_COLLATION: if (!pg_collation_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_COLLATION, NameListToString(objname)); break; case OBJECT_CONVERSION: if (!pg_conversion_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CONVERSION, NameListToString(objname)); break; case OBJECT_EXTENSION: if (!pg_extension_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTENSION, NameListToString(objname)); break; case OBJECT_FDW: if (!pg_foreign_data_wrapper_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FDW, NameListToString(objname)); break; case OBJECT_FOREIGN_SERVER: if (!pg_foreign_server_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER, NameListToString(objname)); break; case OBJECT_LANGUAGE: if (!pg_language_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_LANGUAGE, NameListToString(objname)); break; case OBJECT_OPCLASS: if (!pg_opclass_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS, NameListToString(objname)); break; case OBJECT_OPFAMILY: if (!pg_opfamily_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPFAMILY, NameListToString(objname)); break; case OBJECT_LARGEOBJECT: if (!lo_compat_privileges && !pg_largeobject_ownercheck(address.objectId, roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be owner of large object %u", address.objectId))); break; case OBJECT_CAST: { /* We can only check permissions on the source/target types */ TypeName *sourcetype = (TypeName *) linitial(objname); TypeName *targettype = (TypeName *) linitial(objargs); Oid sourcetypeid = typenameTypeId(NULL, sourcetype); Oid targettypeid = typenameTypeId(NULL, targettype); if (!pg_type_ownercheck(sourcetypeid, roleid) && !pg_type_ownercheck(targettypeid, roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be owner of type %s or type %s", format_type_be(sourcetypeid), format_type_be(targettypeid)))); } break; case OBJECT_TABLESPACE: if (!pg_tablespace_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE, NameListToString(objname)); break; case OBJECT_TSDICTIONARY: if (!pg_ts_dict_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY, NameListToString(objname)); break; case OBJECT_TSCONFIGURATION: if (!pg_ts_config_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION, NameListToString(objname)); break; case OBJECT_ROLE: /* * We treat roles as being "owned" by those with CREATEROLE priv, * except that superusers are only owned by superusers. */ if (superuser_arg(address.objectId)) { if (!superuser_arg(roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser"))); } else { if (!has_createrole_privilege(roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must have CREATEROLE privilege"))); } break; case OBJECT_TSPARSER: case OBJECT_TSTEMPLATE: /* We treat these object types as being owned by superusers */ if (!superuser_arg(roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser"))); break; default: elog(ERROR, "unrecognized object type: %d", (int) objtype); } }
/* * Drop a table space * * Be careful to check that the tablespace is empty. */ void DropTableSpace(DropTableSpaceStmt *stmt) { #ifdef HAVE_SYMLINK char *tablespacename = stmt->tablespacename; HeapScanDesc scandesc; Relation rel; HeapTuple tuple; ScanKeyData entry[1]; Oid tablespaceoid; /* don't call this in a transaction block */ PreventTransactionChain((void *) stmt, "DROP TABLESPACE"); /* * Acquire ExclusiveLock on pg_tablespace to ensure that no one else is * trying to do DROP TABLESPACE or TablespaceCreateDbspace concurrently. */ rel = heap_open(TableSpaceRelationId, ExclusiveLock); /* * Find the target tuple */ ScanKeyInit(&entry[0], Anum_pg_tablespace_spcname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(tablespacename)); scandesc = heap_beginscan(rel, SnapshotNow, 1, entry); tuple = heap_getnext(scandesc, ForwardScanDirection); if (!HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("tablespace \"%s\" does not exist", tablespacename))); tablespaceoid = HeapTupleGetOid(tuple); /* Must be tablespace owner */ if (!pg_tablespace_ownercheck(tablespaceoid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE, tablespacename); /* Disallow drop of the standard tablespaces, even by superuser */ if (tablespaceoid == GLOBALTABLESPACE_OID || tablespaceoid == DEFAULTTABLESPACE_OID) aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_TABLESPACE, tablespacename); /* * Remove the pg_tablespace tuple (this will roll back if we fail below) */ simple_heap_delete(rel, &tuple->t_self); heap_endscan(scandesc); /* * Remove dependency on owner. */ deleteSharedDependencyRecordsFor(TableSpaceRelationId, tablespaceoid); /* * Try to remove the physical infrastructure */ if (!remove_tablespace_directories(tablespaceoid, false)) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("tablespace \"%s\" is not empty", tablespacename))); /* Record the filesystem change in XLOG */ { xl_tblspc_drop_rec xlrec; XLogRecData rdata[1]; xlrec.ts_id = tablespaceoid; rdata[0].data = (char *) &xlrec; rdata[0].len = sizeof(xl_tblspc_drop_rec); rdata[0].buffer = InvalidBuffer; rdata[0].next = NULL; (void) XLogInsert(RM_TBLSPC_ID, XLOG_TBLSPC_DROP, rdata); } /* We keep the lock on pg_tablespace until commit */ heap_close(rel, NoLock); #else /* !HAVE_SYMLINK */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("tablespaces are not supported on this platform"))); #endif /* HAVE_SYMLINK */ }