本文分析一下CREATE EXTENSION的源码实现。顺带学习一下PG中的插件相关的原理。
语法解析层面
gram.y中的语法定义如下:
/************************************************************************** QUERY:* CREATE EXTENSION extension* [ WITH ] [ SCHEMA schema ] [ VERSION version ]****************************************************************************/
CreateExtensionStmt: CREATE EXTENSION name opt_with create_extension_opt_list{CreateExtensionStmt *n = makeNode(CreateExtensionStmt);n->extname = $3;n->if_not_exists = false;n->options = $5;$$ = (Node *) n;}| CREATE EXTENSION IF_P NOT EXISTS name opt_with create_extension_opt_list{CreateExtensionStmt *n = makeNode(CreateExtensionStmt);n->extname = $6;n->if_not_exists = true;n->options = $8;$$ = (Node *) n;};create_extension_opt_list:create_extension_opt_list create_extension_opt_item{ $$ = lappend($1, $2); }| /* EMPTY */{ $$ = NIL; };create_extension_opt_item:SCHEMA name{$$ = makeDefElem("schema", (Node *)makeString($2), @1);}| VERSION_P NonReservedWord_or_Sconst{$$ = makeDefElem("new_version", (Node *)makeString($2), @1);}| FROM NonReservedWord_or_Sconst{ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),errmsg("CREATE EXTENSION ... FROM is no longer supported"),parser_errposition(@1));}| CASCADE{$$ = makeDefElem("cascade", (Node *)makeInteger(true), @1);};
其核心就是要记录插件名字,有哪些扩展选项:
typedef struct CreateExtensionStmt
{NodeTag type;char *extname;bool if_not_exists; /* just do nothing if it already exists? */List *options; /* List of DefElem nodes */
} CreateExtensionStmt;
很明显这是一个UTILITY命令,不需要经过查询优化器进行优化,直接简单封装为查询树继而封装为执行计划树后进入执行器执行。
执行层面
生成执行计划树后,进入到执行器中进行处理,Utility语句会调用standard_ProcessUtility
进行处理,对应的创建插件由CreateExtension
函数实现。
/* CREATE EXTENSION*/
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) // 从pg_extension系统表中查看是否有冲突{if (stmt->if_not_exists){ereport(NOTICE,(errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("extension \"%s\" already exists, skipping",stmt->extname)));return InvalidObjectAddress;}elseereport(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 */ // 解析optionsforeach(lc, stmt->options){DefElem *defel = (DefElem *) lfirst(lc);if (strcmp(defel->defname, "schema") == 0){if (d_schema)ereport(ERROR,(errcode(ERRCODE_SYNTAX_ERROR),errmsg("conflicting or redundant options"), parser_errposition(pstate, defel->location)));d_schema = defel;schemaName = defGetString(d_schema);}else if (strcmp(defel->defname, "new_version") == 0){if (d_new_version)ereport(ERROR,(errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"), parser_errposition(pstate, defel->location)));d_new_version = defel;versionName = defGetString(d_new_version);}else if (strcmp(defel->defname, "cascade") == 0){if (d_cascade)ereport(ERROR,(errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"), parser_errposition(pstate, defel->location)));d_cascade = defel;cascade = defGetBoolean(d_cascade);}elseelog(ERROR, "unrecognized option: %s", defel->defname);}/* Call CreateExtensionInternal to do the real work. */ // 实际创建是在这里实现的return CreateExtensionInternal(stmt->extname, schemaName, versionName, cascade, NIL, true);
}
我们接下来分析一下关键函数CreateExtensionInternal
,源码如下:
/* CREATE EXTENSION worker** When CASCADE is specified, CreateExtensionInternal() recurses if required* extensions need to be installed. To sanely handle cyclic dependencies,* the "parents" list contains a list of names of extensions already being* installed, allowing us to error out if we recurse to one of those. */
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. */pcontrol = read_extension_control_file(extensionName); // 读*.control文件。在插件的目录里。/* Determine the version to install */if (versionName == NULL){if (pcontrol->default_version)versionName = pcontrol->default_version;elseereport(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 = get_extension_script_filename(pcontrol, NULL, versionName);if (stat(filename, &fst) == 0)updateVersions = NIL; /* Easy, no extra scripts */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)));versionName = evi_start->name; /* Otherwise, install best starting point and then upgrade */}/* 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)schemaOid = get_namespace_oid(schemaName, false); /* If the user is giving us the schema name, it must exist already. */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)));schemaName = control->schema; /* Always use the schema from control file for current extension. */schemaOid = get_namespace_oid(schemaName, true); /* Find or create the schema in case it does not exist. */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, 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);}// 新插件信息插入到系统表pg_extension中。/* 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 */ // 执行插件脚本中的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
函数时,不同的插件有所差别,但最后都会调用到_PG_init
。
后续
插件创建完成后,可查系统表pg_extension查看:
postgres@postgres=# select * from pg_extension;oid | extname | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition
-------+---------+----------+--------------+----------------+------------+-----------+--------------16384 | plpgsql | 10 | 11 | f | 1.0 | |
(1 row)
或者通\x
进行查看:
postgres@postgres=# \dxList of installed extensionsName | Version | Schema | Description
---------+---------+------------+------------------------------plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language
(1 row)l