/* * 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; /* validate */ /* 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, "CreateTablespaceCommand"); /* MPP-6929: metadata tracking */ MetaTrackAddObject(TableSpaceRelationId, tablespaceoid, GetUserId(), "CREATE", "TABLESPACE" ); } }
/* * 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, "CreateSchemaCommand"); } /* 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 PROCEDURAL LANGUAGE * --------------------------------------------------------------------- */ void CreateProceduralLanguage(CreatePLangStmt *stmt) { char *languageName; PLTemplate *pltemplate; Oid handlerOid, inlineOid, valOid; Oid funcrettype; Oid funcargtypes[1]; /* * Translate the language name and check that this language doesn't * already exist */ languageName = case_translate_language_name(stmt->plname); if (SearchSysCacheExists(LANGNAME, PointerGetDatum(languageName), 0, 0, 0)) { /* * MPP-7563: special case plpgsql to omit a notice if it already exists * rather than an error. This allows us to install plpgsql by default * while allowing it to be dropped and not create issues for * dump/restore. This should be phased out in a later releases if/when * plpgsql becomes a true internal language that can not be dropped. * * Note: hardcoding this on the name is semi-safe since we would ignore * any handler functions anyways since plpgsql exists in pg_pltemplate. * Alternatively this logic could be extended to apply to all languages * in pg_pltemplate. */ if (strcmp(languageName, "plpgsql") == 0) { ereport(NOTICE, (errmsg("language \"plpgsql\" already exists, skipping"))); return; } else { ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("language \"%s\" already exists", languageName))); } } /* * If we have template information for the language, ignore the supplied * parameters (if any) and use the template information. */ if ((pltemplate = find_language_template(languageName)) != NULL) { List *funcname; /* * Give a notice if we are ignoring supplied parameters. */ if (stmt->plhandler) if (Gp_role != GP_ROLE_EXECUTE) ereport(NOTICE, (errmsg("using pg_pltemplate information instead of " "CREATE LANGUAGE parameters"))); /* * Check permission */ if (!superuser()) { if (!pltemplate->tmpldbacreate) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to create procedural language \"%s\"", languageName))); if (!pg_database_ownercheck(MyDatabaseId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, get_database_name(MyDatabaseId)); } /* * Find or create the handler function, which we force to be in the * pg_catalog schema. If already present, it must have the correct * return type. */ funcname = SystemFuncName(pltemplate->tmplhandler); handlerOid = LookupFuncName(funcname, 0, funcargtypes, true); if (OidIsValid(handlerOid)) { funcrettype = get_func_rettype(handlerOid); if (funcrettype != LANGUAGE_HANDLEROID) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("function %s must return type \"language_handler\"", NameListToString(funcname)))); } else { handlerOid = ProcedureCreate(pltemplate->tmplhandler, PG_CATALOG_NAMESPACE, false, /* replace */ false, /* returnsSet */ LANGUAGE_HANDLEROID, ClanguageId, F_FMGR_C_VALIDATOR, InvalidOid, /* describeFuncOid */ pltemplate->tmplhandler, pltemplate->tmpllibrary, false, /* isAgg */ false, /* isWindowFunc */ false, /* security_definer */ false, /* isStrict */ PROVOLATILE_VOLATILE, buildoidvector(funcargtypes, 0), PointerGetDatum(NULL), PointerGetDatum(NULL), PointerGetDatum(NULL), NIL, PointerGetDatum(NULL), 1, 0, PRODATAACCESS_NONE, PROEXECLOCATION_ANY); } /* * Likewise for the anonymous block handler, if required; but we don't * care about its return type. */ if (pltemplate->tmplinline) { funcname = SystemFuncName(pltemplate->tmplinline); funcargtypes[0] = INTERNALOID; inlineOid = LookupFuncName(funcname, 1, funcargtypes, true); if (!OidIsValid(inlineOid)) { inlineOid = ProcedureCreate(pltemplate->tmplinline, PG_CATALOG_NAMESPACE, false, /* replace */ false, /* returnsSet */ VOIDOID, ClanguageId, F_FMGR_C_VALIDATOR, InvalidOid, /* describeFuncOid */ pltemplate->tmplinline, pltemplate->tmpllibrary, false, /* isAgg */ false, /* isWin */ false, /* security_definer */ true, /* isStrict */ PROVOLATILE_IMMUTABLE, buildoidvector(funcargtypes, 1), PointerGetDatum(NULL), PointerGetDatum(NULL), PointerGetDatum(NULL), NIL, PointerGetDatum(NULL), 1, 0, PRODATAACCESS_NONE, PROEXECLOCATION_ANY); } } else inlineOid = InvalidOid; /* * Likewise for the validator, if required; but we don't care about * its return type. */ if (pltemplate->tmplvalidator) { funcname = SystemFuncName(pltemplate->tmplvalidator); funcargtypes[0] = OIDOID; valOid = LookupFuncName(funcname, 1, funcargtypes, true); if (!OidIsValid(valOid)) { valOid = ProcedureCreate(pltemplate->tmplvalidator, PG_CATALOG_NAMESPACE, false, /* replace */ false, /* returnsSet */ VOIDOID, ClanguageId, F_FMGR_C_VALIDATOR, InvalidOid, /* describeFuncOid */ pltemplate->tmplvalidator, pltemplate->tmpllibrary, false, /* isAgg */ false, /* isWindowFunc */ false, /* security_definer */ true, /* isStrict */ PROVOLATILE_IMMUTABLE, buildoidvector(funcargtypes, 1), PointerGetDatum(NULL), PointerGetDatum(NULL), PointerGetDatum(NULL), NIL, PointerGetDatum(NULL), 1, 0, PRODATAACCESS_NONE, PROEXECLOCATION_ANY); } } else valOid = InvalidOid; /* ok, create it */ create_proc_lang(languageName, GetUserId(), handlerOid, inlineOid, valOid, pltemplate->tmpltrusted); } else { /* * No template, so use the provided information. If there's no * handler clause, the user is trying to rely on a template that we * don't have, so complain accordingly. */ if (!stmt->plhandler) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("unsupported language \"%s\"", languageName), errhint("The supported languages are listed in the pg_pltemplate system catalog."))); /* * Check permission */ if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to create custom procedural language"))); /* * Lookup the PL handler function and check that it is of the expected * return type */ handlerOid = LookupFuncName(stmt->plhandler, 0, funcargtypes, false); funcrettype = get_func_rettype(handlerOid); if (funcrettype != LANGUAGE_HANDLEROID) { /* * We allow OPAQUE just so we can load old dump files. When we * see a handler function declared OPAQUE, change it to * LANGUAGE_HANDLER. (This is probably obsolete and removable?) */ if (funcrettype == OPAQUEOID) { if (Gp_role != GP_ROLE_EXECUTE) ereport(WARNING, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("changing return type of function %s from \"opaque\" to \"language_handler\"", NameListToString(stmt->plhandler)))); SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID); } else ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("function %s must return type \"language_handler\"", NameListToString(stmt->plhandler)))); } /* validate the inline function */ if (stmt->plinline) { funcargtypes[0] = INTERNALOID; inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false); /* return value is ignored, so we don't check the type */ } else inlineOid = InvalidOid; /* validate the validator function */ if (stmt->plvalidator) { funcargtypes[0] = OIDOID; valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false); /* return value is ignored, so we don't check the type */ } else valOid = InvalidOid; /* ok, create it */ create_proc_lang(languageName, GetUserId(), handlerOid, inlineOid, valOid, stmt->pltrusted); } if (Gp_role == GP_ROLE_DISPATCH) { CdbDispatchUtilityStatement((Node *) stmt, DF_CANCEL_ON_ERROR| DF_WITH_SNAPSHOT| DF_NEED_TWO_PHASE, GetAssignedOidsForDispatch(), NULL); } }