/*------------------------------------------------------------------------- * * 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 #include #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); }