/* * CREATE SCHEMA */ void CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString) { const char *schemaName = stmt->schemaname; const char *authId = stmt->authid; const bool istemp = stmt->istemp; Oid namespaceId = 0; List *parsetree_list; ListCell *parsetree_item; Oid owner_uid; Oid saved_uid; AclResult aclresult; bool saved_secdefcxt; /* bool shouldDispatch = (Gp_role == GP_ROLE_DISPATCH && !IsBootstrapProcessingMode());*/ GetUserIdAndContext(&saved_uid, &saved_secdefcxt); /* * Who is supposed to own the new schema? */ if (authId) owner_uid = get_roleid_checked(authId); else owner_uid = saved_uid; /* * If we are creating a temporary schema then we can skip a * bunch of checks that we would otherwise make. */ if (istemp) { /* * CDB: Delete old temp schema. * * Remove any vestigages of old temporary schema, if any. This can * happen when an old session crashes and doesn't run normal session * shutdown. * * In postgres they try to reuse existing schemas in this case, * however that does not work well for us since the schemas may exist * on a segment by segment basis and we want to keep them syncronized * on oid. The best way of dealing with this is to just delete the * old schemas. */ RemoveSchema_internal(schemaName, DROP_CASCADE, true, true); } else { /* * To create a schema, must have schema-create privilege on the current * database and must be able to become the target role (this does not * imply that the target role itself must have create-schema privilege). * The latter provision guards against "giveaway" attacks. Note that a * superuser will always have both of these privileges a fortiori. */ aclresult = pg_database_aclcheck(MyDatabaseId, saved_uid, ACL_CREATE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_DATABASE, get_database_name(MyDatabaseId)); check_is_member_of_role(saved_uid, owner_uid); /* Additional check to protect reserved schema names */ if (!allowSystemTableModsDDL && (IsReservedName(schemaName) || strcmp(schemaName, "madlib") == 0)) { ereport(ERROR, (errcode(ERRCODE_RESERVED_NAME), errmsg("unacceptable schema name \"%s\"", schemaName), errdetail("The prefix \"%s\" is reserved for system schemas.", GetReservedPrefix(schemaName)))); } } /* * If the requested authorization is different from the current user, * temporarily set the current user so that the object(s) will be created * with the correct ownership. * * (The setting will be restored at the end of this routine, or in case * of error, transaction abort will clean things up.) */ if (saved_uid != owner_uid) SetUserIdAndContext(owner_uid, true); /* * in hawq, should be only called on master except * UPGRADE model. */ Assert(gp_upgrade_mode || (Gp_role != GP_ROLE_EXECUTE)); /* Create the schema's namespace */ namespaceId = NamespaceCreate(schemaName, owner_uid, 0); /* MPP-6929: metadata tracking */ if (Gp_role == GP_ROLE_DISPATCH && !istemp) MetaTrackAddObject(NamespaceRelationId, namespaceId, saved_uid, "CREATE", "SCHEMA" ); /* Advance cmd counter to make the namespace visible */ CommandCounterIncrement(); /* If this is the temporary namespace we must mark it specially */ if (istemp) SetTempNamespace(namespaceId); /* * Temporarily make the new namespace be the front of the search path, as * well as the default creation target namespace. This will be undone at * the end of this routine, or upon error. */ PushSpecialNamespace(namespaceId); /* * Examine the list of commands embedded in the CREATE SCHEMA command, and * reorganize them into a sequentially executable order with no forward * references. Note that the result is still a list of raw parsetrees in * need of parse analysis --- we cannot, in general, run analyze.c on one * statement until we have actually executed the prior ones. */ parsetree_list = analyzeCreateSchemaStmt(stmt); /* * Analyze and execute each command contained in the CREATE SCHEMA */ foreach(parsetree_item, parsetree_list) { Node *parsetree = (Node *) lfirst(parsetree_item); List *querytree_list; ListCell *querytree_item; querytree_list = parse_analyze(parsetree, NULL, NULL, 0); foreach(querytree_item, querytree_list) { Query *querytree = (Query *) lfirst(querytree_item); /* schemas should contain only utility stmts */ Assert(querytree->commandType == CMD_UTILITY); /* do this step */ ProcessUtility(querytree->utilityStmt, queryString, NULL, false, /* not top level */ None_Receiver, NULL); /* make sure later steps can see the object created here */ CommandCounterIncrement(); }
/* * 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); }
/* * CREATE SCHEMA */ void CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString) { const char *schemaName = stmt->schemaname; const char *authId = stmt->authid; Oid namespaceId; OverrideSearchPath *overridePath; List *parsetree_list; ListCell *parsetree_item; Oid owner_uid; Oid saved_uid; int save_sec_context; AclResult aclresult; bool shouldDispatch = (Gp_role == GP_ROLE_DISPATCH && !IsBootstrapProcessingMode()); /* * GPDB: Creation of temporary namespaces is a special case. This statement * is dispatched by the dispatcher node the first time a temporary table is * created. It bypasses all the normal checks and logic of schema creation, * and is routed to the internal routine for creating temporary namespaces, * instead. */ if (stmt->istemp) { Assert(Gp_role == GP_ROLE_EXECUTE); Assert(stmt->schemaname == InvalidOid); Assert(stmt->authid == NULL); Assert(stmt->schemaElts == NIL); Assert(stmt->schemaOid != InvalidOid); Assert(stmt->toastSchemaOid != InvalidOid); InitTempTableNamespaceWithOids(stmt->schemaOid, stmt->toastSchemaOid); return; } GetUserIdAndSecContext(&saved_uid, &save_sec_context); /* * Who is supposed to own the new schema? */ if (authId) owner_uid = get_roleid_checked(authId); else owner_uid = saved_uid; /* * To create a schema, must have schema-create privilege on the current * database and must be able to become the target role (this does not * imply that the target role itself must have create-schema privilege). * The latter provision guards against "giveaway" attacks. Note that a * superuser will always have both of these privileges a fortiori. */ aclresult = pg_database_aclcheck(MyDatabaseId, saved_uid, ACL_CREATE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_DATABASE, get_database_name(MyDatabaseId)); check_is_member_of_role(saved_uid, owner_uid); /* Additional check to protect reserved schema names */ if (!allowSystemTableModsDDL && IsReservedName(schemaName)) { ereport(ERROR, (errcode(ERRCODE_RESERVED_NAME), errmsg("unacceptable schema name \"%s\"", schemaName), errdetail("The prefix \"%s\" is reserved for system schemas.", GetReservedPrefix(schemaName)))); } /* * If the requested authorization is different from the current user, * temporarily set the current user so that the object(s) will be created * with the correct ownership. * * (The setting will be restored at the end of this routine, or in case * of error, transaction abort will clean things up.) */ if (saved_uid != owner_uid) SetUserIdAndSecContext(owner_uid, save_sec_context | SECURITY_LOCAL_USERID_CHANGE); /* Create the schema's namespace */ if (shouldDispatch || Gp_role != GP_ROLE_EXECUTE) { namespaceId = NamespaceCreate(schemaName, owner_uid, 0); if (shouldDispatch) { elog(DEBUG5, "shouldDispatch = true, namespaceOid = %d", namespaceId); Assert(stmt->schemaOid == 0); stmt->schemaOid = namespaceId; /* * Dispatch the command to all primary and mirror segment dbs. * Starts a global transaction and reconfigures cluster if needed. * Waits for QEs to finish. Exits via ereport(ERROR,...) if error. */ CdbDispatchUtilityStatement((Node *) stmt, DF_CANCEL_ON_ERROR | DF_WITH_SNAPSHOT | DF_NEED_TWO_PHASE, NULL); } /* MPP-6929: metadata tracking */ if (Gp_role == GP_ROLE_DISPATCH) MetaTrackAddObject(NamespaceRelationId, namespaceId, saved_uid, "CREATE", "SCHEMA" ); } else { namespaceId = NamespaceCreate(schemaName, owner_uid, stmt->schemaOid); } /* Advance cmd counter to make the namespace visible */ CommandCounterIncrement(); /* * Temporarily make the new namespace be the front of the search path, as * well as the default creation target namespace. This will be undone at * the end of this routine, or upon error. */ overridePath = GetOverrideSearchPath(CurrentMemoryContext); overridePath->schemas = lcons_oid(namespaceId, overridePath->schemas); /* XXX should we clear overridePath->useTemp? */ PushOverrideSearchPath(overridePath); /* * Examine the list of commands embedded in the CREATE SCHEMA command, and * reorganize them into a sequentially executable order with no forward * references. Note that the result is still a list of raw parsetrees --- * we cannot, in general, run parse analysis on one statement until we * have actually executed the prior ones. */ parsetree_list = transformCreateSchemaStmt(stmt); /* * Execute each command contained in the CREATE SCHEMA. Since the grammar * allows only utility commands in CREATE SCHEMA, there is no need to pass * them through parse_analyze() or the rewriter; we can just hand them * straight to ProcessUtility. */ foreach(parsetree_item, parsetree_list) { Node *stmt = (Node *) lfirst(parsetree_item); /* do this step */ ProcessUtility(stmt, queryString, NULL, false, /* not top level */ None_Receiver, NULL); /* make sure later steps can see the object created here */ CommandCounterIncrement(); }
/* * Create a table space * * Only superusers can create a tablespace. This seems a reasonable restriction * since we're determining the system layout and, anyway, we probably have * root if we're doing this kind of activity */ void CreateTableSpace(CreateTableSpaceStmt *stmt) { Relation rel; Relation filespaceRel; Datum values[Natts_pg_tablespace]; bool nulls[Natts_pg_tablespace]; HeapTuple tuple; Oid tablespaceoid; Oid filespaceoid; Oid ownerId; TablespaceDirNode tablespaceDirNode; ItemPointerData persistentTid; int64 persistentSerialNum; cqContext cqc; cqContext *pcqCtx; /* Must be super user */ if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to create tablespace \"%s\"", stmt->tablespacename), errhint("Must be superuser to create a tablespace."))); /* However, the eventual owner of the tablespace need not be */ if (stmt->owner) ownerId = get_roleid_checked(stmt->owner); else ownerId = GetUserId(); /* * Disallow creation of tablespaces named "pg_xxx"; we reserve this * namespace for system purposes. */ if (!allowSystemTableModsDDL && IsReservedName(stmt->tablespacename)) { ereport(ERROR, (errcode(ERRCODE_RESERVED_NAME), errmsg("unacceptable tablespace name \"%s\"", stmt->tablespacename), errdetail("The prefix \"%s\" is reserved for system tablespaces.", GetReservedPrefix(stmt->tablespacename)))); } /* * Check the specified filespace */ filespaceRel = heap_open(FileSpaceRelationId, RowShareLock); filespaceoid = get_filespace_oid(filespaceRel, stmt->filespacename); heap_close(filespaceRel, NoLock); /* hold lock until commit/abort */ if (!OidIsValid(filespaceoid)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("filespace \"%s\" does not exist", stmt->filespacename))); /* * Filespace pg_system is reserved for system use: * - Used for pg_global and pg_default tablespaces only * * Directory layout is slightly different for the system filespace. * Instead of having subdirectories for individual tablespaces instead * the two system tablespaces have specific locations within it: * pg_global : $PG_SYSTEM/global/relfilenode * pg_default : $PG_SYSTEM/base/dboid/relfilenode * * In other words PG_SYSTEM points to the segments "datadir", or in * postgres vocabulary $PGDATA. * */ if (filespaceoid == SYSTEMFILESPACE_OID && !IsBootstrapProcessingMode()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to create tablespace \"%s\"", stmt->tablespacename), errhint("filespace %s is reserved for system use", stmt->filespacename))); /* * Check that there is no other tablespace by this name. (The unique * index would catch this anyway, but might as well give a friendlier * message.) */ if (OidIsValid(get_tablespace_oid(stmt->tablespacename))) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("tablespace \"%s\" already exists", stmt->tablespacename))); /* * Insert tuple into pg_tablespace. The purpose of doing this first is to * lock the proposed tablename against other would-be creators. The * insertion will roll back if we find problems below. */ rel = heap_open(TableSpaceRelationId, RowExclusiveLock); pcqCtx = caql_beginscan( caql_addrel(cqclr(&cqc), rel), cql("INSERT INTO pg_tablespace", NULL)); MemSet(nulls, true, sizeof(nulls)); values[Anum_pg_tablespace_spcname - 1] = DirectFunctionCall1(namein, CStringGetDatum(stmt->tablespacename)); values[Anum_pg_tablespace_spcowner - 1] = ObjectIdGetDatum(ownerId); values[Anum_pg_tablespace_spcfsoid - 1] = ObjectIdGetDatum(filespaceoid); nulls[Anum_pg_tablespace_spcname - 1] = false; nulls[Anum_pg_tablespace_spcowner - 1] = false; nulls[Anum_pg_tablespace_spcfsoid - 1] = false; tuple = caql_form_tuple(pcqCtx, values, nulls); /* Keep oids synchonized between master and segments */ if (OidIsValid(stmt->tsoid)) HeapTupleSetOid(tuple, stmt->tsoid); tablespaceoid = caql_insert(pcqCtx, tuple); /* and Update indexes (implicit) */ heap_freetuple(tuple); /* We keep the lock on pg_tablespace until commit */ caql_endscan(pcqCtx); heap_close(rel, NoLock); /* Create the persistent directory for the tablespace */ tablespaceDirNode.tablespace = tablespaceoid; tablespaceDirNode.filespace = filespaceoid; MirroredFileSysObj_TransactionCreateTablespaceDir( &tablespaceDirNode, &persistentTid, &persistentSerialNum); /* * Record dependency on owner * * We do not record the dependency on pg_filespace because we do not track * dependencies between shared objects. Additionally the pg_tablespace * table itself contains the foreign key back to pg_filespace and can be * used to fulfill the same purpose that an entry in pg_shdepend would. */ recordDependencyOnOwner(TableSpaceRelationId, tablespaceoid, ownerId); /* * Create the PG_VERSION file in the target directory. This has several * purposes: to make sure we can write in the directory, to prevent * someone from creating another tablespace pointing at the same directory * (the emptiness check above will fail), and to label tablespace * directories by PG version. */ // set_short_version(sublocation); if (Gp_role == GP_ROLE_DISPATCH) { stmt->tsoid = tablespaceoid; CdbDispatchUtilityStatement((Node *) stmt, DF_CANCEL_ON_ERROR| DF_WITH_SNAPSHOT| DF_NEED_TWO_PHASE, NULL); /* MPP-6929: metadata tracking */ MetaTrackAddObject(TableSpaceRelationId, tablespaceoid, GetUserId(), "CREATE", "TABLESPACE" ); } /* * Force synchronous commit, to minimize the window between creating the * symlink 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(); }