diff options
Diffstat (limited to 'src/backend/commands/extension.c')
-rw-r--r-- | src/backend/commands/extension.c | 1401 |
1 files changed, 1401 insertions, 0 deletions
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c new file mode 100644 index 00000000000..50032031976 --- /dev/null +++ b/src/backend/commands/extension.c @@ -0,0 +1,1401 @@ +/*------------------------------------------------------------------------- + * + * extension.c + * Commands to manipulate extensions + * + * Extensions in PostgreSQL allow management of collections of SQL objects. + * + * All we need internally to manage an extension is an OID so that the + * dependent objects can be associated with it. An extension is created by + * populating the pg_extension catalog from a "control" file. + * The extension control file is parsed with the same parser we use for + * postgresql.conf and recovery.conf. An extension also has an installation + * script file, containing SQL commands to create the extension's objects. + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/commands/extension.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <dirent.h> +#include <unistd.h> + +#include "access/sysattr.h" +#include "access/xact.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/pg_depend.h" +#include "catalog/pg_extension.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_type.h" +#include "commands/alter.h" +#include "commands/comment.h" +#include "commands/extension.h" +#include "commands/trigger.h" +#include "executor/executor.h" +#include "funcapi.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "tcop/tcopprot.h" +#include "tcop/utility.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/guc.h" +#include "utils/lsyscache.h" +#include "utils/snapmgr.h" +#include "utils/tqual.h" + + +bool creating_extension = false; +Oid CurrentExtensionObject = InvalidOid; + +/* + * Internal data structure to hold the results of parsing a control file + */ +typedef struct ExtensionControlFile +{ + char *name; /* name of the extension */ + char *script; /* filename of the installation script */ + char *version; /* version ID, if any */ + char *comment; /* comment, if any */ + char *schema; /* target schema (allowed if !relocatable) */ + bool relocatable; /* is ALTER EXTENSION SET SCHEMA supported? */ + int encoding; /* encoding of the script file, or -1 */ + List *requires; /* names of prerequisite extensions */ +} ExtensionControlFile; + + +/* + * get_extension_oid - given an extension name, look up the OID + * + * If missing_ok is false, throw an error if extension name not found. If + * true, just return InvalidOid. + */ +Oid +get_extension_oid(const char *extname, bool missing_ok) +{ + Oid result; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_extname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(extname)); + + scandesc = systable_beginscan(rel, ExtensionNameIndexId, true, + SnapshotNow, 1, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + result = HeapTupleGetOid(tuple); + else + result = InvalidOid; + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + if (!OidIsValid(result) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("extension \"%s\" does not exist", + extname))); + + return result; +} + +/* + * get_extension_name - given an extension OID, look up the name + * + * Returns a palloc'd string, or NULL if no such extension. + */ +char * +get_extension_name(Oid ext_oid) +{ + char *result; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ext_oid)); + + scandesc = systable_beginscan(rel, ExtensionOidIndexId, true, + SnapshotNow, 1, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + result = pstrdup(NameStr(((Form_pg_extension) GETSTRUCT(tuple))->extname)); + else + result = NULL; + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + return result; +} + +/* + * get_extension_schema - given an extension OID, fetch its extnamespace + * + * Returns InvalidOid if no such extension. + */ +static Oid +get_extension_schema(Oid ext_oid) +{ + Oid result; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ext_oid)); + + scandesc = systable_beginscan(rel, ExtensionOidIndexId, true, + SnapshotNow, 1, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + result = ((Form_pg_extension) GETSTRUCT(tuple))->extnamespace; + else + result = InvalidOid; + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + return result; +} + +/* + * Utility functions to handle extension-related path names + */ +static bool +is_extension_control_filename(const char *filename) +{ + const char *extension = strrchr(filename, '.'); + + return (extension != NULL) && (strcmp(extension, ".control") == 0); +} + +static char * +get_extension_control_directory(void) +{ + char sharepath[MAXPGPATH]; + char *result; + + get_share_path(my_exec_path, sharepath); + result = (char *) palloc(MAXPGPATH); + snprintf(result, MAXPGPATH, "%s/contrib", sharepath); + + return result; +} + +static char * +get_extension_control_filename(const char *extname) +{ + char sharepath[MAXPGPATH]; + char *result; + + get_share_path(my_exec_path, sharepath); + result = (char *) palloc(MAXPGPATH); + snprintf(result, MAXPGPATH, "%s/contrib/%s.control", sharepath, extname); + + return result; +} + +/* + * Given a relative pathname such as "name.sql", return the full path to + * the script file. If given an absolute name, just return it. + */ +static char * +get_extension_absolute_path(const char *filename) +{ + char sharepath[MAXPGPATH]; + char *result; + + if (is_absolute_path(filename)) + return pstrdup(filename); + + get_share_path(my_exec_path, sharepath); + result = (char *) palloc(MAXPGPATH); + snprintf(result, MAXPGPATH, "%s/contrib/%s", sharepath, filename); + + return result; +} + + +/* + * Read the control file for the specified extension. + * + * The control file is supposed to be very short, half a dozen lines, and + * reading it is only allowed to superuser, so we don't worry about + * memory allocation risks here. Also note that we don't worry about + * what encoding it's in; all values are expected to be ASCII. + */ +static ExtensionControlFile * +read_extension_control_file(const char *extname) +{ + char *filename = get_extension_control_filename(extname); + FILE *file; + ExtensionControlFile *control; + ConfigVariable *item, + *head = NULL, + *tail = NULL; + + /* + * Parse the file content, using GUC's file parsing code + */ + if ((file = AllocateFile(filename, "r")) == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open extension control file \"%s\": %m", + filename))); + + ParseConfigFp(file, filename, 0, ERROR, &head, &tail); + + FreeFile(file); + + /* + * Set up default values. Pointer fields are initially null. + */ + control = (ExtensionControlFile *) palloc0(sizeof(ExtensionControlFile)); + control->name = pstrdup(extname); + control->relocatable = false; + control->encoding = -1; + + /* + * Convert the ConfigVariable list into ExtensionControlFile entries. + */ + for (item = head; item != NULL; item = item->next) + { + if (strcmp(item->name, "script") == 0) + { + control->script = pstrdup(item->value); + } + else if (strcmp(item->name, "version") == 0) + { + control->version = pstrdup(item->value); + } + else if (strcmp(item->name, "comment") == 0) + { + control->comment = pstrdup(item->value); + } + else if (strcmp(item->name, "schema") == 0) + { + control->schema = pstrdup(item->value); + } + else if (strcmp(item->name, "relocatable") == 0) + { + if (!parse_bool(item->value, &control->relocatable)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"%s\" requires a Boolean value", + item->name))); + } + else if (strcmp(item->name, "encoding") == 0) + { + control->encoding = pg_valid_server_encoding(item->value); + if (control->encoding < 0) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("\"%s\" is not a valid encoding name", + item->value))); + } + else if (strcmp(item->name, "requires") == 0) + { + /* Need a modifiable copy of string */ + char *rawnames = pstrdup(item->value); + + /* Parse string into list of identifiers */ + if (!SplitIdentifierString(rawnames, ',', &control->requires)) + { + /* syntax error in name list */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"%s\" must be a list of extension names", + item->name))); + } + } + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized parameter \"%s\" in file \"%s\"", + item->name, filename))); + } + + FreeConfigVariables(head); + + if (control->relocatable && control->schema != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("parameter \"schema\" cannot be specified when \"relocatable\" is true"))); + + /* + * script defaults to ${extension-name}.sql + */ + if (control->script == NULL) + { + char script[MAXPGPATH]; + + snprintf(script, MAXPGPATH, "%s.sql", control->name); + control->script = pstrdup(script); + } + + return control; +} + +/* + * Read the SQL script into a string, and convert to database encoding + */ +static char * +read_extension_script_file(const ExtensionControlFile *control, + const char *filename) +{ + int src_encoding; + int dest_encoding = GetDatabaseEncoding(); + bytea *content; + char *src_str; + char *dest_str; + int len; + + content = read_binary_file(filename, 0, -1); + + /* use database encoding if not given */ + if (control->encoding < 0) + src_encoding = dest_encoding; + else + src_encoding = control->encoding; + + /* make sure that source string is valid in the expected encoding */ + len = VARSIZE_ANY_EXHDR(content); + src_str = VARDATA_ANY(content); + pg_verify_mbstr_len(src_encoding, src_str, len, false); + + /* convert the encoding to the database encoding */ + dest_str = (char *) pg_do_encoding_conversion((unsigned char *) src_str, + len, + src_encoding, + dest_encoding); + + /* if no conversion happened, we have to arrange for null termination */ + if (dest_str == src_str) + { + dest_str = (char *) palloc(len + 1); + memcpy(dest_str, src_str, len); + dest_str[len] = '\0'; + } + + return dest_str; +} + +/* + * Execute given SQL string. + * + * filename is used only to report errors. + * + * Note: it's tempting to just use SPI to execute the string, but that does + * not work very well. The really serious problem is that SPI will parse, + * analyze, and plan the whole string before executing any of it; of course + * this fails if there are any plannable statements referring to objects + * created earlier in the script. A lesser annoyance is that SPI insists + * on printing the whole string as errcontext in case of any error, and that + * could be very long. + */ +static void +execute_sql_string(const char *sql, const char *filename) +{ + List *raw_parsetree_list; + DestReceiver *dest; + ListCell *lc1; + + /* + * Parse the SQL string into a list of raw parse trees. + */ + raw_parsetree_list = pg_parse_query(sql); + + /* All output from SELECTs goes to the bit bucket */ + dest = CreateDestReceiver(DestNone); + + /* + * Do parse analysis, rule rewrite, planning, and execution for each raw + * parsetree. We must fully execute each query before beginning parse + * analysis on the next one, since there may be interdependencies. + */ + foreach(lc1, raw_parsetree_list) + { + Node *parsetree = (Node *) lfirst(lc1); + List *stmt_list; + ListCell *lc2; + + stmt_list = pg_analyze_and_rewrite(parsetree, + sql, + NULL, + 0); + stmt_list = pg_plan_queries(stmt_list, 0, NULL); + + foreach(lc2, stmt_list) + { + Node *stmt = (Node *) lfirst(lc2); + + if (IsA(stmt, TransactionStmt)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("transaction control statements are not allowed within an extension script"))); + + CommandCounterIncrement(); + + PushActiveSnapshot(GetTransactionSnapshot()); + + if (IsA(stmt, PlannedStmt) && + ((PlannedStmt *) stmt)->utilityStmt == NULL) + { + QueryDesc *qdesc; + + qdesc = CreateQueryDesc((PlannedStmt *) stmt, + sql, + GetActiveSnapshot(), NULL, + dest, NULL, 0); + + AfterTriggerBeginQuery(); + ExecutorStart(qdesc, 0); + ExecutorRun(qdesc, ForwardScanDirection, 0); + AfterTriggerEndQuery(qdesc->estate); + ExecutorEnd(qdesc); + + FreeQueryDesc(qdesc); + } + else + { + ProcessUtility(stmt, + sql, + NULL, + false, /* not top level */ + dest, + NULL); + } + + PopActiveSnapshot(); + } + } + + /* Be sure to advance the command counter after the last script command */ + CommandCounterIncrement(); +} + +/* + * Execute the extension's script file + */ +static void +execute_extension_script(Oid extensionOid, ExtensionControlFile *control, + List *requiredSchemas, + const char *schemaName, Oid schemaOid) +{ + char *filename = get_extension_absolute_path(control->script); + char *save_client_min_messages = NULL, + *save_log_min_messages = NULL, + *save_search_path; + StringInfoData pathbuf; + ListCell *lc; + + /* + * 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 SET LOCAL to ensure the setting is undone + * upon error. + */ + if (client_min_messages < WARNING) + { + save_client_min_messages = + pstrdup(GetConfigOption("client_min_messages", false)); + (void) set_config_option("client_min_messages", "warning", + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_LOCAL, true); + } + + if (log_min_messages < WARNING) + { + save_log_min_messages = + pstrdup(GetConfigOption("log_min_messages", false)); + (void) set_config_option("log_min_messages", "warning", + PGC_SUSET, PGC_S_SESSION, + GUC_ACTION_LOCAL, true); + } + + /* + * Set up the search path to contain the target schema, then the schemas + * of any prerequisite extensions, and nothing else. In particular this + * makes the target schema be the default creation target namespace. + * + * Note: it might look tempting to use PushOverrideSearchPath for this, + * but we cannot do that. We have to actually set the search_path GUC + * in case the extension script examines or changes it. + */ + save_search_path = pstrdup(GetConfigOption("search_path", false)); + + initStringInfo(&pathbuf); + appendStringInfoString(&pathbuf, quote_identifier(schemaName)); + foreach(lc, requiredSchemas) + { + Oid reqschema = lfirst_oid(lc); + char *reqname = get_namespace_name(reqschema); + + if (reqname) + appendStringInfo(&pathbuf, ", %s", quote_identifier(reqname)); + } + + (void) set_config_option("search_path", pathbuf.data, + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_LOCAL, true); + + /* + * 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 *sql = read_extension_script_file(control, filename); + + /* + * If it's not relocatable, substitute the target schema name for + * occcurrences of @extschema@. + * + * For a relocatable extension, we just run the script as-is. + * There cannot be any need for @extschema@, else it wouldn't + * be relocatable. + */ + if (!control->relocatable) + { + const char *qSchemaName = quote_identifier(schemaName); + + sql = text_to_cstring( + DatumGetTextPP( + DirectFunctionCall3(replace_text, + CStringGetTextDatum(sql), + CStringGetTextDatum("@extschema@"), + CStringGetTextDatum(qSchemaName)))); + + } + + execute_sql_string(sql, filename); + } + PG_CATCH(); + { + creating_extension = false; + CurrentExtensionObject = InvalidOid; + PG_RE_THROW(); + } + PG_END_TRY(); + + creating_extension = false; + CurrentExtensionObject = InvalidOid; + + /* + * Restore GUC variables for the remainder of the current transaction. + * Again use SET LOCAL, so we won't affect the session value. + */ + (void) set_config_option("search_path", save_search_path, + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_LOCAL, true); + + if (save_client_min_messages != NULL) + (void) set_config_option("client_min_messages", save_client_min_messages, + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_LOCAL, true); + if (save_log_min_messages != NULL) + (void) set_config_option("log_min_messages", save_log_min_messages, + PGC_SUSET, PGC_S_SESSION, + GUC_ACTION_LOCAL, true); +} + +/* + * CREATE EXTENSION + */ +void +CreateExtension(CreateExtensionStmt *stmt) +{ + DefElem *d_schema = NULL; + char *schemaName; + Oid schemaOid; + Oid extowner = GetUserId(); + ExtensionControlFile *control; + List *requiredExtensions; + List *requiredSchemas; + Relation rel; + Datum values[Natts_pg_extension]; + bool nulls[Natts_pg_extension]; + HeapTuple tuple; + Oid extensionOid; + ObjectAddress myself; + ObjectAddress nsp; + ListCell *lc; + + /* Must be super user */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to create extension \"%s\"", + stmt->extname), + errhint("Must be superuser to create an extension."))); + + /* + * 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"))); + + /* + * 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. + */ + if (get_extension_oid(stmt->extname, true) != InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("extension \"%s\" already exists", stmt->extname))); + + /* + * Read the 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. + */ + control = read_extension_control_file(stmt->extname); + + /* + * Read the statement option list + */ + foreach(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"))); + d_schema = defel; + } + else + elog(ERROR, "unrecognized option: %s", defel->defname); + } + + /* + * Determine the target schema to install the extension into + */ + if (d_schema && d_schema->arg) + { + /* + * User given schema, CREATE EXTENSION ... WITH SCHEMA ... + * + * It's an error to give a schema different from control->schema if + * control->schema is specified. + */ + schemaName = strVal(d_schema->arg); + + if (control->schema != NULL && + strcmp(control->schema, schemaName) != 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("extension \"%s\" must be installed in schema \"%s\"", + control->name, + control->schema))); + + /* If the user is giving us the schema name, it must exist already */ + schemaOid = get_namespace_oid(schemaName, false); + } + else if (control->schema != NULL) + { + /* + * The extension is not relocatable and the author gave us a schema + * for it. We create the schema here if it does not already exist. + */ + schemaName = control->schema; + schemaOid = get_namespace_oid(schemaName, true); + + if (schemaOid == InvalidOid) + { + schemaOid = NamespaceCreate(schemaName, extowner); + /* Advance cmd counter to make the namespace visible */ + CommandCounterIncrement(); + } + } + else + { + /* + * Else, 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) /* probably can't happen */ + elog(ERROR, "there is no default creation target"); + schemaOid = linitial_oid(search_path); + schemaName = get_namespace_name(schemaOid); + if (schemaName == NULL) /* recently-deleted namespace? */ + elog(ERROR, "there is no default creation target"); + + list_free(search_path); + } + + /* + * If we didn't already know user is superuser, we would probably want + * to do pg_namespace_aclcheck(schemaOid, extowner, ACL_CREATE) here. + */ + + /* + * Look up the prerequisite extensions, 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; + + /* + * We intentionally don't use get_extension_oid's default error + * message here, because it would be confusing in this context. + */ + reqext = get_extension_oid(curreq, true); + if (!OidIsValid(reqext)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("required extension \"%s\" is not installed", + curreq))); + reqschema = get_extension_schema(reqext); + requiredExtensions = lappend_oid(requiredExtensions, reqext); + requiredSchemas = lappend_oid(requiredSchemas, reqschema); + } + + /* + * Insert new tuple into pg_extension. + */ + rel = heap_open(ExtensionRelationId, RowExclusiveLock); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + values[Anum_pg_extension_extname - 1] = + DirectFunctionCall1(namein, CStringGetDatum(control->name)); + values[Anum_pg_extension_extowner - 1] = ObjectIdGetDatum(extowner); + values[Anum_pg_extension_extnamespace - 1] = ObjectIdGetDatum(schemaOid); + values[Anum_pg_extension_extrelocatable - 1] = BoolGetDatum(control->relocatable); + + if (control->version == NULL) + nulls[Anum_pg_extension_extversion - 1] = true; + else + values[Anum_pg_extension_extversion - 1] = + CStringGetTextDatum(control->version); + + nulls[Anum_pg_extension_extconfig - 1] = true; + nulls[Anum_pg_extension_extcondition - 1] = true; + + tuple = heap_form_tuple(rel->rd_att, values, nulls); + + extensionOid = simple_heap_insert(rel, tuple); + CatalogUpdateIndexes(rel, tuple); + + heap_freetuple(tuple); + heap_close(rel, RowExclusiveLock); + + /* + * Apply any comment on extension + */ + if (control->comment != NULL) + CreateComments(extensionOid, ExtensionRelationId, 0, control->comment); + + /* + * Record dependencies on owner, schema, and prerequisite extensions + */ + recordDependencyOnOwner(ExtensionRelationId, extensionOid, extowner); + + myself.classId = ExtensionRelationId; + myself.objectId = extensionOid; + myself.objectSubId = 0; + + nsp.classId = NamespaceRelationId; + nsp.objectId = schemaOid; + nsp.objectSubId = 0; + + recordDependencyOn(&myself, &nsp, DEPENDENCY_NORMAL); + + foreach(lc, requiredExtensions) + { + Oid reqext = lfirst_oid(lc); + ObjectAddress otherext; + + otherext.classId = ExtensionRelationId; + otherext.objectId = reqext; + otherext.objectSubId = 0; + + recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL); + } + + /* + * Finally, execute the extension script to create the member objects + */ + execute_extension_script(extensionOid, control, requiredSchemas, + schemaName, schemaOid); +} + + +/* + * RemoveExtensions + * Implements DROP EXTENSION. + */ +void +RemoveExtensions(DropStmt *drop) +{ + ObjectAddresses *objects; + ListCell *cell; + + /* + * First we identify all the extensions, then we delete them in a single + * performMultipleDeletions() call. This is to avoid unwanted DROP + * RESTRICT errors if one of the extensions depends on another. + */ + objects = new_object_addresses(); + + foreach(cell, drop->objects) + { + List *names = (List *) lfirst(cell); + char *extensionName; + Oid extensionId; + ObjectAddress object; + + if (list_length(names) != 1) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("extension name cannot be qualified"))); + extensionName = strVal(linitial(names)); + + extensionId = get_extension_oid(extensionName, drop->missing_ok); + + if (!OidIsValid(extensionId)) + { + ereport(NOTICE, + (errmsg("extension \"%s\" does not exist, skipping", + extensionName))); + continue; + } + + /* + * Permission check. For now, insist on superuser-ness; later we + * might want to relax that to being owner of the extension. + */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to drop extension \"%s\"", + extensionName), + errhint("Must be superuser to drop an extension."))); + + object.classId = ExtensionRelationId; + object.objectId = extensionId; + object.objectSubId = 0; + + add_exact_object_address(&object, objects); + } + + /* + * Do the deletions. Objects contained in the extension(s) are removed by + * means of their dependency links to the extensions. + */ + performMultipleDeletions(objects, drop->behavior); + + free_object_addresses(objects); +} + + +/* + * Guts of extension deletion. + * + * All we need do here is remove the pg_extension tuple itself. Everything + * else is taken care of by the dependency infrastructure. + */ +void +RemoveExtensionById(Oid extId) +{ + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionRelationId, RowExclusiveLock); + + ScanKeyInit(&entry[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(extId)); + scandesc = systable_beginscan(rel, ExtensionOidIndexId, true, + SnapshotNow, 1, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + simple_heap_delete(rel, &tuple->t_self); + + systable_endscan(scandesc); + + heap_close(rel, RowExclusiveLock); +} + +/* + * This function lists the extensions available in the control directory + * (each of which might or might not actually be installed). We parse each + * available control file and report the interesting fields. + * + * The system view pg_available_extensions provides a user interface to this + * SRF, adding information about whether the extensions are installed in the + * current DB. + */ +Datum +pg_available_extensions(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + char *location; + DIR *dir; + struct dirent *de; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to list available extensions")))); + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not " \ + "allowed in this context"))); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + + location = get_extension_control_directory(); + dir = AllocateDir(location); + + /* + * If the control directory doesn't exist, we want to silently return + * an empty set. Any other error will be reported by ReadDir. + */ + if (dir == NULL && errno == ENOENT) + { + /* do nothing */ + } + else + { + while ((de = ReadDir(dir, location)) != NULL) + { + ExtensionControlFile *control; + char *extname; + Datum values[4]; + bool nulls[4]; + + if (!is_extension_control_filename(de->d_name)) + continue; + + /* extract extension name from 'name.control' filename */ + extname = pstrdup(de->d_name); + *strrchr(extname, '.') = '\0'; + + control = read_extension_control_file(extname); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + /* name */ + values[0] = DirectFunctionCall1(namein, + CStringGetDatum(control->name)); + /* version */ + if (control->version == NULL) + nulls[1] = true; + else + values[1] = CStringGetTextDatum(control->version); + /* relocatable */ + values[2] = BoolGetDatum(control->relocatable); + /* comment */ + if (control->comment == NULL) + nulls[3] = true; + else + values[3] = CStringGetTextDatum(control->comment); + + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + } + + FreeDir(dir); + } + + /* clean up and return the tuplestore */ + tuplestore_donestoring(tupstore); + + return (Datum) 0; +} + +/* + * pg_extension_config_dump + * + * Record information about a configuration table that belongs to an + * extension being created, but whose contents should be dumped in whole + * or in part during pg_dump. + */ +Datum +pg_extension_config_dump(PG_FUNCTION_ARGS) +{ + Oid tableoid = PG_GETARG_OID(0); + text *wherecond = PG_GETARG_TEXT_P(1); + char *tablename; + Relation extRel; + ScanKeyData key[1]; + SysScanDesc extScan; + HeapTuple extTup; + Datum arrayDatum; + Datum elementDatum; + int arrayIndex; + bool isnull; + Datum repl_val[Natts_pg_extension]; + bool repl_null[Natts_pg_extension]; + bool repl_repl[Natts_pg_extension]; + ArrayType *a; + + /* + * We only allow this to be called from an extension's SQL script. + * We shouldn't need any permissions check beyond that. + */ + if (!creating_extension) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("pg_extension_config_dump() can only be called " + "from a SQL script executed by CREATE EXTENSION"))); + + /* + * Check that the table exists and is a member of the extension being + * created. This ensures that we don't need to register a dependency + * to protect the extconfig entry. + */ + tablename = get_rel_name(tableoid); + if (tablename == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("OID %u does not refer to a table", tableoid))); + if (getExtensionOfObject(RelationRelationId, tableoid) != + CurrentExtensionObject) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("table \"%s\" is not a member of the extension being created", + tablename))); + + /* + * Add the table OID and WHERE condition to the extension's extconfig + * and extcondition arrays. + */ + + /* Find the pg_extension tuple */ + extRel = heap_open(ExtensionRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(CurrentExtensionObject)); + + extScan = systable_beginscan(extRel, ExtensionOidIndexId, true, + SnapshotNow, 1, key); + + extTup = systable_getnext(extScan); + + if (!HeapTupleIsValid(extTup)) /* should not happen */ + elog(ERROR, "extension with oid %u does not exist", + CurrentExtensionObject); + + memset(repl_val, 0, sizeof(repl_val)); + memset(repl_null, false, sizeof(repl_null)); + memset(repl_repl, false, sizeof(repl_repl)); + + /* Build or modify the extconfig value */ + elementDatum = ObjectIdGetDatum(tableoid); + + arrayDatum = heap_getattr(extTup, Anum_pg_extension_extconfig, + RelationGetDescr(extRel), &isnull); + if (isnull) + { + a = construct_array(&elementDatum, 1, + OIDOID, + sizeof(Oid), true, 'i'); + } + else + { + a = DatumGetArrayTypeP(arrayDatum); + Assert(ARR_ELEMTYPE(a) == OIDOID); + Assert(ARR_NDIM(a) == 1); + Assert(ARR_LBOUND(a)[0] == 1); + + arrayIndex = ARR_DIMS(a)[0] + 1; /* add after end */ + + a = array_set(a, 1, &arrayIndex, + elementDatum, + false, + -1 /* varlena array */ , + sizeof(Oid) /* OID's typlen */ , + true /* OID's typbyval */ , + 'i' /* OID's typalign */ ); + } + repl_val[Anum_pg_extension_extconfig - 1] = PointerGetDatum(a); + repl_repl[Anum_pg_extension_extconfig - 1] = true; + + /* Build or modify the extcondition value */ + elementDatum = PointerGetDatum(wherecond); + + arrayDatum = heap_getattr(extTup, Anum_pg_extension_extcondition, + RelationGetDescr(extRel), &isnull); + if (isnull) + { + a = construct_array(&elementDatum, 1, + TEXTOID, + -1, false, 'i'); + } + else + { + a = DatumGetArrayTypeP(arrayDatum); + Assert(ARR_ELEMTYPE(a) == TEXTOID); + Assert(ARR_NDIM(a) == 1); + Assert(ARR_LBOUND(a)[0] == 1); + + arrayIndex = ARR_DIMS(a)[0] + 1; /* add after end */ + + a = array_set(a, 1, &arrayIndex, + elementDatum, + false, + -1 /* varlena array */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + 'i' /* TEXT's typalign */ ); + } + repl_val[Anum_pg_extension_extcondition - 1] = PointerGetDatum(a); + repl_repl[Anum_pg_extension_extcondition - 1] = true; + + extTup = heap_modify_tuple(extTup, RelationGetDescr(extRel), + repl_val, repl_null, repl_repl); + + simple_heap_update(extRel, &extTup->t_self, extTup); + CatalogUpdateIndexes(extRel, extTup); + + systable_endscan(extScan); + + heap_close(extRel, RowExclusiveLock); + + PG_RETURN_VOID(); +} + +/* + * Execute ALTER EXTENSION SET SCHEMA + */ +void +AlterExtensionNamespace(List *names, const char *newschema) +{ + char *extensionName; + Oid extensionOid; + Oid nspOid; + Oid oldNspOid = InvalidOid; + Relation extRel; + ScanKeyData key[2]; + SysScanDesc extScan; + HeapTuple extTup; + Form_pg_extension extForm; + Relation depRel; + SysScanDesc depScan; + HeapTuple depTup; + + if (list_length(names) != 1) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("extension name cannot be qualified"))); + extensionName = strVal(linitial(names)); + + extensionOid = get_extension_oid(extensionName, false); + + nspOid = LookupCreationNamespace(newschema); + + /* this might later become an ownership test */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to use ALTER EXTENSION")))); + + /* Locate the pg_extension tuple */ + extRel = heap_open(ExtensionRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(extensionOid)); + + extScan = systable_beginscan(extRel, ExtensionOidIndexId, true, + SnapshotNow, 1, key); + + extTup = systable_getnext(extScan); + + if (!HeapTupleIsValid(extTup)) /* should not happen */ + elog(ERROR, "extension with oid %u does not exist", extensionOid); + + /* Copy tuple so we can modify it below */ + extTup = heap_copytuple(extTup); + extForm = (Form_pg_extension) GETSTRUCT(extTup); + + systable_endscan(extScan); + + /* + * If the extension is already in the target schema, just silently + * do nothing. + */ + if (extForm->extnamespace == nspOid) + { + heap_close(extRel, RowExclusiveLock); + return; + } + + /* Check extension is supposed to be relocatable */ + if (!extForm->extrelocatable) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("extension \"%s\" does not support SET SCHEMA", + NameStr(extForm->extname)))); + + /* + * Scan pg_depend to find objects that depend directly on the extension, + * and alter each one's schema. + */ + depRel = heap_open(DependRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_refclassid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ExtensionRelationId)); + ScanKeyInit(&key[1], + Anum_pg_depend_refobjid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(extensionOid)); + + depScan = systable_beginscan(depRel, DependReferenceIndexId, true, + SnapshotNow, 2, key); + + while (HeapTupleIsValid(depTup = systable_getnext(depScan))) + { + Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup); + ObjectAddress dep; + Oid dep_oldNspOid; + + /* + * Ignore non-membership dependencies. (Currently, the only other + * case we could see here is a normal dependency from another + * extension.) + */ + if (pg_depend->deptype != DEPENDENCY_EXTENSION) + continue; + + dep.classId = pg_depend->classid; + dep.objectId = pg_depend->objid; + dep.objectSubId = pg_depend->objsubid; + + if (dep.objectSubId != 0) /* should not happen */ + elog(ERROR, "extension should not have a sub-object dependency"); + + dep_oldNspOid = AlterObjectNamespace_oid(dep.classId, + dep.objectId, + nspOid); + + /* + * Remember previous namespace of first object that has one + */ + if (oldNspOid == InvalidOid && dep_oldNspOid != InvalidOid) + oldNspOid = dep_oldNspOid; + + /* + * If not all the objects had the same old namespace (ignoring any + * that are not in namespaces), complain. + */ + if (dep_oldNspOid != InvalidOid && dep_oldNspOid != oldNspOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("extension \"%s\" does not support SET SCHEMA", + NameStr(extForm->extname)), + errdetail("%s is not in the extension's schema \"%s\"", + getObjectDescription(&dep), + get_namespace_name(oldNspOid)))); + } + + systable_endscan(depScan); + + relation_close(depRel, AccessShareLock); + + /* Now adjust pg_extension.extnamespace */ + extForm->extnamespace = nspOid; + + simple_heap_update(extRel, &extTup->t_self, extTup); + CatalogUpdateIndexes(extRel, extTup); + + heap_close(extRel, RowExclusiveLock); + + /* update dependencies to point to the new schema */ + changeDependencyFor(ExtensionRelationId, extensionOid, + NamespaceRelationId, oldNspOid, nspOid); +} |