本文以创建postgis扩展来介绍PG后台所执行的流程
执行create extension postgis函数后,后台会进入到exec_simple_query->ProcessUtility->CreateExtension函数中
cpp
ObjectAddress
CreateExtension(ParseState *pstate, CreateExtensionStmt *stmt)
{
DefElem *d_schema = NULL;
DefElem *d_new_version = NULL;
DefElem *d_cascade = NULL;
char *schemaName = NULL;
char *versionName = NULL;
bool cascade = false;
ListCell *lc;
/* Check extension name validity before any filesystem access */
check_valid_extension_name(stmt->extname);
/*
* Check for duplicate extension name. The unique index on
* pg_extension.extname would catch this anyway, and serves as a backstop
* in case of race conditions; but this is a friendlier error message, and
* besides we need a check to support IF NOT EXISTS.
*/
if (get_extension_oid(stmt->extname, true) != InvalidOid)
{
if (stmt->if_not_exists)
{
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("extension \"%s\" already exists, skipping",
stmt->extname)));
return InvalidObjectAddress;
}
else
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("extension \"%s\" already exists",
stmt->extname)));
}
/*
* We use global variables to track the extension being created, so we can
* create only one extension at the same time.
*/
if (creating_extension)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("nested CREATE EXTENSION is not supported")));
/* Deconstruct the statement option list */
//获取创建扩展的参数,比如schema信息等
foreach(lc, stmt->options)
{
DefElem *defel = (DefElem *) lfirst(lc);
if (strcmp(defel->defname, "schema") == 0)
{
if (d_schema)
errorConflictingDefElem(defel, pstate);
d_schema = defel;
schemaName = defGetString(d_schema);
}
else if (strcmp(defel->defname, "new_version") == 0)
{
if (d_new_version)
errorConflictingDefElem(defel, pstate);
d_new_version = defel;
versionName = defGetString(d_new_version);
}
else if (strcmp(defel->defname, "cascade") == 0)
{
if (d_cascade)
errorConflictingDefElem(defel, pstate);
d_cascade = defel;
cascade = defGetBoolean(d_cascade);
}
else
elog(ERROR, "unrecognized option: %s", defel->defname);
}
/* Call CreateExtensionInternal to do the real work. */
return CreateExtensionInternal(stmt->extname,
schemaName,
versionName,
cascade,
NIL,
true);
}
具体创建过程在函数CreateExtensionInternal中完成
cpp
static ObjectAddress
CreateExtensionInternal(char *extensionName,
char *schemaName,
const char *versionName,
bool cascade,
List *parents,
bool is_create)
{
char *origSchemaName = schemaName;
Oid schemaOid = InvalidOid;
Oid extowner = GetUserId();
ExtensionControlFile *pcontrol;
ExtensionControlFile *control;
char *filename;
struct stat fst;
List *updateVersions;
List *requiredExtensions;
List *requiredSchemas;
Oid extensionOid;
ObjectAddress address;
ListCell *lc;
/*
* Read the primary control file. Note we assume that it does not contain
* any non-ASCII data, so there is no need to worry about encoding at this
* point.
*/
//根据扩展名字获取到扩展控制文件内的信息
//控制文件路径为
pg_config --sharedir
/home/postgres174/postgresoft_debug/share + "extesion" + postgis.control
{name = 0x26c5ff8 "postgis", directory = 0x0, default_version = 0x275c7f0 "3.4.4", module_pathname = 0x275c800 "$libdir/postgis-3",
comment = 0x275c7a8 "PostGIS geometry and geography spatial types and functions", schema = 0x0, relocatable = false, superuser = true, trusted = false,
encoding = -1, requires = 0x0, no_relocate = 0x0}
pcontrol = read_extension_control_file(extensionName);
/*
* Determine the version to install
*/
if (versionName == NULL)
{
if (pcontrol->default_version)
versionName = pcontrol->default_version;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("version to install must be specified")));
}
check_valid_version_name(versionName);
/*
* Figure out which script(s) we need to run to install the desired
* version of the extension. If we do not have a script that directly
* does what is needed, we try to find a sequence of update scripts that
* will get us there.
*/
///filename= home/postgres174/postgresoft_debug/share/extension/postgis--3.4.4.sql
filename = get_extension_script_filename(pcontrol, NULL, versionName);
if (stat(filename, &fst) == 0)
{
/* Easy, no extra scripts */
updateVersions = NIL;
}
else
{
/* Look for best way to install this version */
List *evi_list;
ExtensionVersionInfo *evi_start;
ExtensionVersionInfo *evi_target;
/* Extract the version update graph from the script directory */
evi_list = get_ext_ver_list(pcontrol);
/* Identify the target version */
evi_target = get_ext_ver_info(versionName, &evi_list);
/* Identify best path to reach target */
evi_start = find_install_path(evi_list, evi_target,
&updateVersions);
/* Fail if no path ... */
if (evi_start == NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("extension \"%s\" has no installation script nor update path for version \"%s\"",
pcontrol->name, versionName)));
/* Otherwise, install best starting point and then upgrade */
versionName = evi_start->name;
}
/*
* Fetch control parameters for installation target version
*/
control = read_extension_aux_control_file(pcontrol, versionName);
/*
* Determine the target schema to install the extension into
*/
if (schemaName)
{
/* If the user is giving us the schema name, it must exist already. */
schemaOid = get_namespace_oid(schemaName, false);
}
if (control->schema != NULL)
{
/*
* The extension is not relocatable and the author gave us a schema
* for it.
*
* Unless CASCADE parameter was given, it's an error to give a schema
* different from control->schema if control->schema is specified.
*/
if (schemaName && strcmp(control->schema, schemaName) != 0 &&
!cascade)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("extension \"%s\" must be installed in schema \"%s\"",
control->name,
control->schema)));
/* Always use the schema from control file for current extension. */
schemaName = control->schema;
/* Find or create the schema in case it does not exist. */
schemaOid = get_namespace_oid(schemaName, true);
if (!OidIsValid(schemaOid))
{
CreateSchemaStmt *csstmt = makeNode(CreateSchemaStmt);
csstmt->schemaname = schemaName;
csstmt->authrole = NULL; /* will be created by current user */
csstmt->schemaElts = NIL;
csstmt->if_not_exists = false;
CreateSchemaCommand(csstmt, "(generated CREATE SCHEMA command)",
-1, -1);
/*
* CreateSchemaCommand includes CommandCounterIncrement, so new
* schema is now visible.
*/
schemaOid = get_namespace_oid(schemaName, false);
}
}
else if (!OidIsValid(schemaOid))
{
/*
* Neither user nor author of the extension specified schema; use the
* current default creation namespace, which is the first explicit
* entry in the search_path.
*/
List *search_path = fetch_search_path(false);
if (search_path == NIL) /* nothing valid in search_path? */
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_SCHEMA),
errmsg("no schema has been selected to create in")));
schemaOid = linitial_oid(search_path);
schemaName = get_namespace_name(schemaOid);
if (schemaName == NULL) /* recently-deleted namespace? */
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_SCHEMA),
errmsg("no schema has been selected to create in")));
list_free(search_path);
}
/*
* Make note if a temporary namespace has been accessed in this
* transaction.
*/
if (isTempNamespace(schemaOid))
MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
/*
* We don't check creation rights on the target namespace here. If the
* extension script actually creates any objects there, it will fail if
* the user doesn't have such permissions. But there are cases such as
* procedural languages where it's convenient to set schema = pg_catalog
* yet we don't want to restrict the command to users with ACL_CREATE for
* pg_catalog.
*/
/*
* Look up the prerequisite extensions, install them if necessary, and
* build lists of their OIDs and the OIDs of their target schemas.
*/
requiredExtensions = NIL;
requiredSchemas = NIL;
foreach(lc, control->requires)
{
char *curreq = (char *) lfirst(lc);
Oid reqext;
Oid reqschema;
reqext = get_required_extension(curreq,
extensionName,
origSchemaName,
cascade,
parents,
is_create);
reqschema = get_extension_schema(reqext);
requiredExtensions = lappend_oid(requiredExtensions, reqext);
requiredSchemas = lappend_oid(requiredSchemas, reqschema);
}
/*
* Insert new tuple into pg_extension, and create dependency entries.
*/
address = InsertExtensionTuple(control->name, extowner,
schemaOid, control->relocatable,
versionName,
PointerGetDatum(NULL),
PointerGetDatum(NULL),
requiredExtensions);
extensionOid = address.objectId;
/*
* Apply any control-file comment on extension
*/
if (control->comment != NULL)
CreateComments(extensionOid, ExtensionRelationId, 0, control->comment);
/*
* Execute the installation script file
*/
//执行script文本中的sql语句
execute_extension_script(extensionOid, control,
NULL, versionName,
requiredSchemas,
schemaName, schemaOid);
/*
* If additional update scripts have to be executed, apply the updates as
* though a series of ALTER EXTENSION UPDATE commands were given
*/
ApplyExtensionUpdates(extensionOid, pcontrol,
versionName, updateVersions,
origSchemaName, cascade, is_create);
return address;
}
execute_extension_script
cpp
static void
execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
const char *from_version,
const char *version,
List *requiredSchemas,
const char *schemaName, Oid schemaOid)
{
bool switch_to_superuser = false;
char *filename;
Oid save_userid = 0;
int save_sec_context = 0;
int save_nestlevel;
StringInfoData pathbuf;
ListCell *lc;
ListCell *lc2;
/*
* Enforce superuser-ness if appropriate. We postpone these checks until
* here so that the control flags are correctly associated with the right
* script(s) if they happen to be set in secondary control files.
*/
//要求是su用户,但是实际并不是
if (control->superuser && !superuser())
{
//数据库的create权限也可以
if (extension_is_trusted(control))
switch_to_superuser = true;
else if (from_version == NULL)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to create extension \"%s\"",
control->name),
control->trusted
? errhint("Must have CREATE privilege on current database to create this extension.")
: errhint("Must be superuser to create this extension.")));
else
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to update extension \"%s\"",
control->name),
control->trusted
? errhint("Must have CREATE privilege on current database to update this extension.")
: errhint("Must be superuser to update this extension.")));
}
filename = get_extension_script_filename(control, from_version, version);
if (from_version == NULL)
elog(DEBUG1, "executing extension script for \"%s\" version '%s'", control->name, version);
else
elog(DEBUG1, "executing extension script for \"%s\" update from version '%s' to '%s'", control->name, from_version, version);
/*
* If installing a trusted extension on behalf of a non-superuser, become
* the bootstrap superuser. (This switch will be cleaned up automatically
* if the transaction aborts, as will the GUC changes below.)
*/
if (switch_to_superuser)
{
//临时切换到su账户权限
GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID,
save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
}
/*
* Force client_min_messages and log_min_messages to be at least WARNING,
* so that we won't spam the user with useless NOTICE messages from common
* script actions like creating shell types.
*
* We use the equivalent of a function SET option to allow the setting to
* persist for exactly the duration of the script execution. guc.c also
* takes care of undoing the setting on error.
*
* log_min_messages can't be set by ordinary users, so for that one we
* pretend to be superuser.
*/
save_nestlevel = NewGUCNestLevel();
if (client_min_messages < WARNING)
(void) set_config_option("client_min_messages", "warning",
PGC_USERSET, PGC_S_SESSION,
GUC_ACTION_SAVE, true, 0, false);
if (log_min_messages < WARNING)
(void) set_config_option_ext("log_min_messages", "warning",
PGC_SUSET, PGC_S_SESSION,
BOOTSTRAP_SUPERUSERID,
GUC_ACTION_SAVE, true, 0, false);
/*
* Similarly disable check_function_bodies, to ensure that SQL functions
* won't be parsed during creation.
*/
if (check_function_bodies)
(void) set_config_option("check_function_bodies", "off",
PGC_USERSET, PGC_S_SESSION,
GUC_ACTION_SAVE, true, 0, false);
/*
* Set up the search path to have the target schema first, making it be
* the default creation target namespace. Then add the schemas of any
* prerequisite extensions, unless they are in pg_catalog which would be
* searched anyway. (Listing pg_catalog explicitly in a non-first
* position would be bad for security.) Finally add pg_temp to ensure
* that temp objects can't take precedence over others.
*/
initStringInfo(&pathbuf);
appendStringInfoString(&pathbuf, quote_identifier(schemaName));
foreach(lc, requiredSchemas)
{
Oid reqschema = lfirst_oid(lc);
char *reqname = get_namespace_name(reqschema);
if (reqname && strcmp(reqname, "pg_catalog") != 0)
appendStringInfo(&pathbuf, ", %s", quote_identifier(reqname));
}
appendStringInfoString(&pathbuf, ", pg_temp");
(void) set_config_option("search_path", pathbuf.data,
PGC_USERSET, PGC_S_SESSION,
GUC_ACTION_SAVE, true, 0, false);
/*
* Set creating_extension and related variables so that
* recordDependencyOnCurrentExtension and other functions do the right
* things. On failure, ensure we reset these variables.
*/
creating_extension = true;
CurrentExtensionObject = extensionOid;
PG_TRY();
{
char *c_sql = read_extension_script_file(control, filename);
Datum t_sql;
/*
* We filter each substitution through quote_identifier(). When the
* arg contains one of the following characters, no one collection of
* quoting can work inside $$dollar-quoted string literals$$,
* 'single-quoted string literals', and outside of any literal. To
* avoid a security snare for extension authors, error on substitution
* for arguments containing these.
*/
const char *quoting_relevant_chars = "\"$'\\";
/* We use various functions that want to operate on text datums */
t_sql = CStringGetTextDatum(c_sql);
/*
* Reduce any lines beginning with "\echo" to empty. This allows
* scripts to contain messages telling people not to run them via
* psql, which has been found to be necessary due to old habits.
*/
t_sql = DirectFunctionCall4Coll(textregexreplace,
C_COLLATION_OID,
t_sql,
CStringGetTextDatum("^\\\\echo.*$"),
CStringGetTextDatum(""),
CStringGetTextDatum("ng"));
/*
* If the script uses @extowner@, substitute the calling username.
*/
if (strstr(c_sql, "@extowner@"))
{
Oid uid = switch_to_superuser ? save_userid : GetUserId();
const char *userName = GetUserNameFromId(uid, false);
const char *qUserName = quote_identifier(userName);
t_sql = DirectFunctionCall3Coll(replace_text,
C_COLLATION_OID,
t_sql,
CStringGetTextDatum("@extowner@"),
CStringGetTextDatum(qUserName));
if (strpbrk(userName, quoting_relevant_chars))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid character in extension owner: must not contain any of \"%s\"",
quoting_relevant_chars)));
}
/*
* If it's not relocatable, substitute the target schema name for
* occurrences of @extschema@.
*
* For a relocatable extension, we needn't do this. There cannot be
* any need for @extschema@, else it wouldn't be relocatable.
*/
if (!control->relocatable)
{
Datum old = t_sql;
const char *qSchemaName = quote_identifier(schemaName);
t_sql = DirectFunctionCall3Coll(replace_text,
C_COLLATION_OID,
t_sql,
CStringGetTextDatum("@extschema@"),
CStringGetTextDatum(qSchemaName));
if (t_sql != old && strpbrk(schemaName, quoting_relevant_chars))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid character in extension \"%s\" schema: must not contain any of \"%s\"",
control->name, quoting_relevant_chars)));
}
/*
* Likewise, substitute required extensions' schema names for
* occurrences of @extschema:extension_name@.
*/
Assert(list_length(control->requires) == list_length(requiredSchemas));
forboth(lc, control->requires, lc2, requiredSchemas)
{
Datum old = t_sql;
char *reqextname = (char *) lfirst(lc);
Oid reqschema = lfirst_oid(lc2);
char *schemaName = get_namespace_name(reqschema);
const char *qSchemaName = quote_identifier(schemaName);
char *repltoken;
repltoken = psprintf("@extschema:%s@", reqextname);
t_sql = DirectFunctionCall3Coll(replace_text,
C_COLLATION_OID,
t_sql,
CStringGetTextDatum(repltoken),
CStringGetTextDatum(qSchemaName));
if (t_sql != old && strpbrk(schemaName, quoting_relevant_chars))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid character in extension \"%s\" schema: must not contain any of \"%s\"",
reqextname, quoting_relevant_chars)));
}
/*
* If module_pathname was set in the control file, substitute its
* value for occurrences of MODULE_PATHNAME.
*/
if (control->module_pathname)
{
t_sql = DirectFunctionCall3Coll(replace_text,
C_COLLATION_OID,
t_sql,
CStringGetTextDatum("MODULE_PATHNAME"),
CStringGetTextDatum(control->module_pathname));
}
/* And now back to C string */
c_sql = text_to_cstring(DatumGetTextPP(t_sql));
//实际执行sql语句
execute_sql_string(c_sql);
}
PG_FINALLY();
{
creating_extension = false;
CurrentExtensionObject = InvalidOid;
}
PG_END_TRY();
/*
* Restore the GUC variables we set above.
*/
AtEOXact_GUC(true, save_nestlevel);
/*
* Restore authentication state if needed.
*/
if (switch_to_superuser)
SetUserIdAndSecContext(save_userid, save_sec_context);
}