summaryrefslogtreecommitdiffstats
path: root/src/backend/commands/extension.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/commands/extension.c')
-rw-r--r--src/backend/commands/extension.c3480
1 files changed, 3480 insertions, 0 deletions
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
new file mode 100644
index 0000000..b37009b
--- /dev/null
+++ b/src/backend/commands/extension.c
@@ -0,0 +1,3480 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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. An extension also has an installation script file,
+ * containing SQL commands to create the extension's objects.
+ *
+ * Portions Copyright (c) 1996-2021, 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 <limits.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/relation.h"
+#include "access/sysattr.h"
+#include "access/table.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_collation.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/defrem.h"
+#include "commands/extension.h"
+#include "commands/schemacmds.h"
+#include "funcapi.h"
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/fd.h"
+#include "tcop/utility.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/snapmgr.h"
+#include "utils/varlena.h"
+
+
+/* Globally visible state variables */
+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 *directory; /* directory for script files */
+ char *default_version; /* default install target version, if any */
+ char *module_pathname; /* string to substitute for
+ * MODULE_PATHNAME */
+ char *comment; /* comment, if any */
+ char *schema; /* target schema (allowed if !relocatable) */
+ bool relocatable; /* is ALTER EXTENSION SET SCHEMA supported? */
+ bool superuser; /* must be superuser to install? */
+ bool trusted; /* allow becoming superuser on the fly? */
+ int encoding; /* encoding of the script file, or -1 */
+ List *requires; /* names of prerequisite extensions */
+} ExtensionControlFile;
+
+/*
+ * Internal data structure for update path information
+ */
+typedef struct ExtensionVersionInfo
+{
+ char *name; /* name of the starting version */
+ List *reachable; /* List of ExtensionVersionInfo's */
+ bool installable; /* does this version have an install script? */
+ /* working state for Dijkstra's algorithm: */
+ bool distance_known; /* is distance from start known yet? */
+ int distance; /* current worst-case distance estimate */
+ struct ExtensionVersionInfo *previous; /* current best predecessor */
+} ExtensionVersionInfo;
+
+/* Local functions */
+static List *find_update_path(List *evi_list,
+ ExtensionVersionInfo *evi_start,
+ ExtensionVersionInfo *evi_target,
+ bool reject_indirect,
+ bool reinitialize);
+static Oid get_required_extension(char *reqExtensionName,
+ char *extensionName,
+ char *origSchemaName,
+ bool cascade,
+ List *parents,
+ bool is_create);
+static void get_available_versions_for_extension(ExtensionControlFile *pcontrol,
+ Tuplestorestate *tupstore,
+ TupleDesc tupdesc);
+static Datum convert_requires_to_datum(List *requires);
+static void ApplyExtensionUpdates(Oid extensionOid,
+ ExtensionControlFile *pcontrol,
+ const char *initialVersion,
+ List *updateVersions,
+ char *origSchemaName,
+ bool cascade,
+ bool is_create);
+static char *read_whole_file(const char *filename, int *length);
+
+
+/*
+ * 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 = table_open(ExtensionRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_extname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ scandesc = systable_beginscan(rel, ExtensionNameIndexId, true,
+ NULL, 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))->oid;
+ else
+ result = InvalidOid;
+
+ systable_endscan(scandesc);
+
+ table_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 = table_open(ExtensionRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_oid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(ext_oid));
+
+ scandesc = systable_beginscan(rel, ExtensionOidIndexId, true,
+ NULL, 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);
+
+ table_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 = table_open(ExtensionRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_oid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(ext_oid));
+
+ scandesc = systable_beginscan(rel, ExtensionOidIndexId, true,
+ NULL, 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);
+
+ table_close(rel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Utility functions to check validity of extension and version names
+ */
+static void
+check_valid_extension_name(const char *extensionname)
+{
+ int namelen = strlen(extensionname);
+
+ /*
+ * Disallow empty names (the parser rejects empty identifiers anyway, but
+ * let's check).
+ */
+ if (namelen == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid extension name: \"%s\"", extensionname),
+ errdetail("Extension names must not be empty.")));
+
+ /*
+ * No double dashes, since that would make script filenames ambiguous.
+ */
+ if (strstr(extensionname, "--"))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid extension name: \"%s\"", extensionname),
+ errdetail("Extension names must not contain \"--\".")));
+
+ /*
+ * No leading or trailing dash either. (We could probably allow this, but
+ * it would require much care in filename parsing and would make filenames
+ * visually if not formally ambiguous. Since there's no real-world use
+ * case, let's just forbid it.)
+ */
+ if (extensionname[0] == '-' || extensionname[namelen - 1] == '-')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid extension name: \"%s\"", extensionname),
+ errdetail("Extension names must not begin or end with \"-\".")));
+
+ /*
+ * No directory separators either (this is sufficient to prevent ".."
+ * style attacks).
+ */
+ if (first_dir_separator(extensionname) != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid extension name: \"%s\"", extensionname),
+ errdetail("Extension names must not contain directory separator characters.")));
+}
+
+static void
+check_valid_version_name(const char *versionname)
+{
+ int namelen = strlen(versionname);
+
+ /*
+ * Disallow empty names (we could possibly allow this, but there seems
+ * little point).
+ */
+ if (namelen == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid extension version name: \"%s\"", versionname),
+ errdetail("Version names must not be empty.")));
+
+ /*
+ * No double dashes, since that would make script filenames ambiguous.
+ */
+ if (strstr(versionname, "--"))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid extension version name: \"%s\"", versionname),
+ errdetail("Version names must not contain \"--\".")));
+
+ /*
+ * No leading or trailing dash either.
+ */
+ if (versionname[0] == '-' || versionname[namelen - 1] == '-')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid extension version name: \"%s\"", versionname),
+ errdetail("Version names must not begin or end with \"-\".")));
+
+ /*
+ * No directory separators either (this is sufficient to prevent ".."
+ * style attacks).
+ */
+ if (first_dir_separator(versionname) != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid extension version name: \"%s\"", versionname),
+ errdetail("Version names must not contain directory separator characters.")));
+}
+
+/*
+ * 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 bool
+is_extension_script_filename(const char *filename)
+{
+ const char *extension = strrchr(filename, '.');
+
+ return (extension != NULL) && (strcmp(extension, ".sql") == 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/extension", 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/extension/%s.control",
+ sharepath, extname);
+
+ return result;
+}
+
+static char *
+get_extension_script_directory(ExtensionControlFile *control)
+{
+ char sharepath[MAXPGPATH];
+ char *result;
+
+ /*
+ * The directory parameter can be omitted, absolute, or relative to the
+ * installation's share directory.
+ */
+ if (!control->directory)
+ return get_extension_control_directory();
+
+ if (is_absolute_path(control->directory))
+ return pstrdup(control->directory);
+
+ get_share_path(my_exec_path, sharepath);
+ result = (char *) palloc(MAXPGPATH);
+ snprintf(result, MAXPGPATH, "%s/%s", sharepath, control->directory);
+
+ return result;
+}
+
+static char *
+get_extension_aux_control_filename(ExtensionControlFile *control,
+ const char *version)
+{
+ char *result;
+ char *scriptdir;
+
+ scriptdir = get_extension_script_directory(control);
+
+ result = (char *) palloc(MAXPGPATH);
+ snprintf(result, MAXPGPATH, "%s/%s--%s.control",
+ scriptdir, control->name, version);
+
+ pfree(scriptdir);
+
+ return result;
+}
+
+static char *
+get_extension_script_filename(ExtensionControlFile *control,
+ const char *from_version, const char *version)
+{
+ char *result;
+ char *scriptdir;
+
+ scriptdir = get_extension_script_directory(control);
+
+ result = (char *) palloc(MAXPGPATH);
+ if (from_version)
+ snprintf(result, MAXPGPATH, "%s/%s--%s--%s.sql",
+ scriptdir, control->name, from_version, version);
+ else
+ snprintf(result, MAXPGPATH, "%s/%s--%s.sql",
+ scriptdir, control->name, version);
+
+ pfree(scriptdir);
+
+ return result;
+}
+
+
+/*
+ * Parse contents of primary or auxiliary control file, and fill in
+ * fields of *control. We parse primary file if version == NULL,
+ * else the optional auxiliary file for that version.
+ *
+ * Control files are supposed to be very short, half a dozen lines,
+ * so we don't worry about memory allocation risks here. Also we don't
+ * worry about what encoding it's in; all values are expected to be ASCII.
+ */
+static void
+parse_extension_control_file(ExtensionControlFile *control,
+ const char *version)
+{
+ char *filename;
+ FILE *file;
+ ConfigVariable *item,
+ *head = NULL,
+ *tail = NULL;
+
+ /*
+ * Locate the file to read. Auxiliary files are optional.
+ */
+ if (version)
+ filename = get_extension_aux_control_filename(control, version);
+ else
+ filename = get_extension_control_filename(control->name);
+
+ if ((file = AllocateFile(filename, "r")) == NULL)
+ {
+ if (version && errno == ENOENT)
+ {
+ /* no auxiliary file for this version */
+ pfree(filename);
+ return;
+ }
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open extension control file \"%s\": %m",
+ filename)));
+ }
+
+ /*
+ * Parse the file content, using GUC's file parsing code. We need not
+ * check the return value since any errors will be thrown at ERROR level.
+ */
+ (void) ParseConfigFp(file, filename, 0, ERROR, &head, &tail);
+
+ FreeFile(file);
+
+ /*
+ * Convert the ConfigVariable list into ExtensionControlFile entries.
+ */
+ for (item = head; item != NULL; item = item->next)
+ {
+ if (strcmp(item->name, "directory") == 0)
+ {
+ if (version)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"%s\" cannot be set in a secondary extension control file",
+ item->name)));
+
+ control->directory = pstrdup(item->value);
+ }
+ else if (strcmp(item->name, "default_version") == 0)
+ {
+ if (version)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"%s\" cannot be set in a secondary extension control file",
+ item->name)));
+
+ control->default_version = pstrdup(item->value);
+ }
+ else if (strcmp(item->name, "module_pathname") == 0)
+ {
+ control->module_pathname = 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, "superuser") == 0)
+ {
+ if (!parse_bool(item->value, &control->superuser))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"%s\" requires a Boolean value",
+ item->name)));
+ }
+ else if (strcmp(item->name, "trusted") == 0)
+ {
+ if (!parse_bool(item->value, &control->trusted))
+ 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")));
+
+ pfree(filename);
+}
+
+/*
+ * Read the primary control file for the specified extension.
+ */
+static ExtensionControlFile *
+read_extension_control_file(const char *extname)
+{
+ ExtensionControlFile *control;
+
+ /*
+ * Set up default values. Pointer fields are initially null.
+ */
+ control = (ExtensionControlFile *) palloc0(sizeof(ExtensionControlFile));
+ control->name = pstrdup(extname);
+ control->relocatable = false;
+ control->superuser = true;
+ control->trusted = false;
+ control->encoding = -1;
+
+ /*
+ * Parse the primary control file.
+ */
+ parse_extension_control_file(control, NULL);
+
+ return control;
+}
+
+/*
+ * Read the auxiliary control file for the specified extension and version.
+ *
+ * Returns a new modified ExtensionControlFile struct; the original struct
+ * (reflecting just the primary control file) is not modified.
+ */
+static ExtensionControlFile *
+read_extension_aux_control_file(const ExtensionControlFile *pcontrol,
+ const char *version)
+{
+ ExtensionControlFile *acontrol;
+
+ /*
+ * Flat-copy the struct. Pointer fields share values with original.
+ */
+ acontrol = (ExtensionControlFile *) palloc(sizeof(ExtensionControlFile));
+ memcpy(acontrol, pcontrol, sizeof(ExtensionControlFile));
+
+ /*
+ * Parse the auxiliary control file, overwriting struct fields
+ */
+ parse_extension_control_file(acontrol, version);
+
+ return acontrol;
+}
+
+/*
+ * Read an SQL script file into a string, and convert to database encoding
+ */
+static char *
+read_extension_script_file(const ExtensionControlFile *control,
+ const char *filename)
+{
+ int src_encoding;
+ char *src_str;
+ char *dest_str;
+ int len;
+
+ src_str = read_whole_file(filename, &len);
+
+ /* use database encoding if not given */
+ if (control->encoding < 0)
+ src_encoding = GetDatabaseEncoding();
+ else
+ src_encoding = control->encoding;
+
+ /* make sure that source string is valid in the expected encoding */
+ (void) pg_verify_mbstr(src_encoding, src_str, len, false);
+
+ /*
+ * Convert the encoding to the database encoding. read_whole_file
+ * null-terminated the string, so if no conversion happens the string is
+ * valid as is.
+ */
+ dest_str = pg_any_to_server(src_str, len, src_encoding);
+
+ return dest_str;
+}
+
+/*
+ * Execute given SQL string.
+ *
+ * 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)
+{
+ 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)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc1);
+ MemoryContext per_parsetree_context,
+ oldcontext;
+ List *stmt_list;
+ ListCell *lc2;
+
+ /*
+ * We do the work for each parsetree in a short-lived context, to
+ * limit the memory used when there are many commands in the string.
+ */
+ per_parsetree_context =
+ AllocSetContextCreate(CurrentMemoryContext,
+ "execute_sql_string per-statement context",
+ ALLOCSET_DEFAULT_SIZES);
+ oldcontext = MemoryContextSwitchTo(per_parsetree_context);
+
+ /* Be sure parser can see any DDL done so far */
+ CommandCounterIncrement();
+
+ stmt_list = pg_analyze_and_rewrite(parsetree,
+ sql,
+ NULL,
+ 0,
+ NULL);
+ stmt_list = pg_plan_queries(stmt_list, sql, CURSOR_OPT_PARALLEL_OK, NULL);
+
+ foreach(lc2, stmt_list)
+ {
+ PlannedStmt *stmt = lfirst_node(PlannedStmt, lc2);
+
+ CommandCounterIncrement();
+
+ PushActiveSnapshot(GetTransactionSnapshot());
+
+ if (stmt->utilityStmt == NULL)
+ {
+ QueryDesc *qdesc;
+
+ qdesc = CreateQueryDesc(stmt,
+ sql,
+ GetActiveSnapshot(), NULL,
+ dest, NULL, NULL, 0);
+
+ ExecutorStart(qdesc, 0);
+ ExecutorRun(qdesc, ForwardScanDirection, 0, true);
+ ExecutorFinish(qdesc);
+ ExecutorEnd(qdesc);
+
+ FreeQueryDesc(qdesc);
+ }
+ else
+ {
+ if (IsA(stmt->utilityStmt, TransactionStmt))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("transaction control statements are not allowed within an extension script")));
+
+ ProcessUtility(stmt,
+ sql,
+ false,
+ PROCESS_UTILITY_QUERY,
+ NULL,
+ NULL,
+ dest,
+ NULL);
+ }
+
+ PopActiveSnapshot();
+ }
+
+ /* Clean up per-parsetree context. */
+ MemoryContextSwitchTo(oldcontext);
+ MemoryContextDelete(per_parsetree_context);
+ }
+
+ /* Be sure to advance the command counter after the last script command */
+ CommandCounterIncrement();
+}
+
+/*
+ * Policy function: is the given extension trusted for installation by a
+ * non-superuser?
+ *
+ * (Update the errhint logic below if you change this.)
+ */
+static bool
+extension_is_trusted(ExtensionControlFile *control)
+{
+ AclResult aclresult;
+
+ /* Never trust unless extension's control file says it's okay */
+ if (!control->trusted)
+ return false;
+ /* Allow if user has CREATE privilege on current database */
+ aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
+ if (aclresult == ACLCHECK_OK)
+ return true;
+ return false;
+}
+
+/*
+ * Execute the appropriate script file for installing or updating the extension
+ *
+ * If from_version isn't NULL, it's an update
+ */
+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;
+
+ /*
+ * 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.
+ */
+ if (control->superuser && !superuser())
+ {
+ 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 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)
+ {
+ 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.
+ */
+ 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("log_min_messages", "warning",
+ PGC_SUSET, PGC_S_SESSION,
+ 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.
+ *
+ * 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. In any case, the
+ * GUC_ACTION_SAVE method is just as convenient.
+ */
+ 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 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 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)
+ {
+ const char *qSchemaName = quote_identifier(schemaName);
+
+ t_sql = DirectFunctionCall3Coll(replace_text,
+ C_COLLATION_OID,
+ t_sql,
+ CStringGetTextDatum("@extschema@"),
+ CStringGetTextDatum(qSchemaName));
+ }
+
+ /*
+ * 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));
+
+ 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);
+}
+
+/*
+ * Find or create an ExtensionVersionInfo for the specified version name
+ *
+ * Currently, we just use a List of the ExtensionVersionInfo's. Searching
+ * for them therefore uses about O(N^2) time when there are N versions of
+ * the extension. We could change the data structure to a hash table if
+ * this ever becomes a bottleneck.
+ */
+static ExtensionVersionInfo *
+get_ext_ver_info(const char *versionname, List **evi_list)
+{
+ ExtensionVersionInfo *evi;
+ ListCell *lc;
+
+ foreach(lc, *evi_list)
+ {
+ evi = (ExtensionVersionInfo *) lfirst(lc);
+ if (strcmp(evi->name, versionname) == 0)
+ return evi;
+ }
+
+ evi = (ExtensionVersionInfo *) palloc(sizeof(ExtensionVersionInfo));
+ evi->name = pstrdup(versionname);
+ evi->reachable = NIL;
+ evi->installable = false;
+ /* initialize for later application of Dijkstra's algorithm */
+ evi->distance_known = false;
+ evi->distance = INT_MAX;
+ evi->previous = NULL;
+
+ *evi_list = lappend(*evi_list, evi);
+
+ return evi;
+}
+
+/*
+ * Locate the nearest unprocessed ExtensionVersionInfo
+ *
+ * This part of the algorithm is also about O(N^2). A priority queue would
+ * make it much faster, but for now there's no need.
+ */
+static ExtensionVersionInfo *
+get_nearest_unprocessed_vertex(List *evi_list)
+{
+ ExtensionVersionInfo *evi = NULL;
+ ListCell *lc;
+
+ foreach(lc, evi_list)
+ {
+ ExtensionVersionInfo *evi2 = (ExtensionVersionInfo *) lfirst(lc);
+
+ /* only vertices whose distance is still uncertain are candidates */
+ if (evi2->distance_known)
+ continue;
+ /* remember the closest such vertex */
+ if (evi == NULL ||
+ evi->distance > evi2->distance)
+ evi = evi2;
+ }
+
+ return evi;
+}
+
+/*
+ * Obtain information about the set of update scripts available for the
+ * specified extension. The result is a List of ExtensionVersionInfo
+ * structs, each with a subsidiary list of the ExtensionVersionInfos for
+ * the versions that can be reached in one step from that version.
+ */
+static List *
+get_ext_ver_list(ExtensionControlFile *control)
+{
+ List *evi_list = NIL;
+ int extnamelen = strlen(control->name);
+ char *location;
+ DIR *dir;
+ struct dirent *de;
+
+ location = get_extension_script_directory(control);
+ dir = AllocateDir(location);
+ while ((de = ReadDir(dir, location)) != NULL)
+ {
+ char *vername;
+ char *vername2;
+ ExtensionVersionInfo *evi;
+ ExtensionVersionInfo *evi2;
+
+ /* must be a .sql file ... */
+ if (!is_extension_script_filename(de->d_name))
+ continue;
+
+ /* ... matching extension name followed by separator */
+ if (strncmp(de->d_name, control->name, extnamelen) != 0 ||
+ de->d_name[extnamelen] != '-' ||
+ de->d_name[extnamelen + 1] != '-')
+ continue;
+
+ /* extract version name(s) from 'extname--something.sql' filename */
+ vername = pstrdup(de->d_name + extnamelen + 2);
+ *strrchr(vername, '.') = '\0';
+ vername2 = strstr(vername, "--");
+ if (!vername2)
+ {
+ /* It's an install, not update, script; record its version name */
+ evi = get_ext_ver_info(vername, &evi_list);
+ evi->installable = true;
+ continue;
+ }
+ *vername2 = '\0'; /* terminate first version */
+ vername2 += 2; /* and point to second */
+
+ /* if there's a third --, it's bogus, ignore it */
+ if (strstr(vername2, "--"))
+ continue;
+
+ /* Create ExtensionVersionInfos and link them together */
+ evi = get_ext_ver_info(vername, &evi_list);
+ evi2 = get_ext_ver_info(vername2, &evi_list);
+ evi->reachable = lappend(evi->reachable, evi2);
+ }
+ FreeDir(dir);
+
+ return evi_list;
+}
+
+/*
+ * Given an initial and final version name, identify the sequence of update
+ * scripts that have to be applied to perform that update.
+ *
+ * Result is a List of names of versions to transition through (the initial
+ * version is *not* included).
+ */
+static List *
+identify_update_path(ExtensionControlFile *control,
+ const char *oldVersion, const char *newVersion)
+{
+ List *result;
+ List *evi_list;
+ ExtensionVersionInfo *evi_start;
+ ExtensionVersionInfo *evi_target;
+
+ /* Extract the version update graph from the script directory */
+ evi_list = get_ext_ver_list(control);
+
+ /* Initialize start and end vertices */
+ evi_start = get_ext_ver_info(oldVersion, &evi_list);
+ evi_target = get_ext_ver_info(newVersion, &evi_list);
+
+ /* Find shortest path */
+ result = find_update_path(evi_list, evi_start, evi_target, false, false);
+
+ if (result == NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("extension \"%s\" has no update path from version \"%s\" to version \"%s\"",
+ control->name, oldVersion, newVersion)));
+
+ return result;
+}
+
+/*
+ * Apply Dijkstra's algorithm to find the shortest path from evi_start to
+ * evi_target.
+ *
+ * If reject_indirect is true, ignore paths that go through installable
+ * versions. This saves work when the caller will consider starting from
+ * all installable versions anyway.
+ *
+ * If reinitialize is false, assume the ExtensionVersionInfo list has not
+ * been used for this before, and the initialization done by get_ext_ver_info
+ * is still good. Otherwise, reinitialize all transient fields used here.
+ *
+ * Result is a List of names of versions to transition through (the initial
+ * version is *not* included). Returns NIL if no such path.
+ */
+static List *
+find_update_path(List *evi_list,
+ ExtensionVersionInfo *evi_start,
+ ExtensionVersionInfo *evi_target,
+ bool reject_indirect,
+ bool reinitialize)
+{
+ List *result;
+ ExtensionVersionInfo *evi;
+ ListCell *lc;
+
+ /* Caller error if start == target */
+ Assert(evi_start != evi_target);
+ /* Caller error if reject_indirect and target is installable */
+ Assert(!(reject_indirect && evi_target->installable));
+
+ if (reinitialize)
+ {
+ foreach(lc, evi_list)
+ {
+ evi = (ExtensionVersionInfo *) lfirst(lc);
+ evi->distance_known = false;
+ evi->distance = INT_MAX;
+ evi->previous = NULL;
+ }
+ }
+
+ evi_start->distance = 0;
+
+ while ((evi = get_nearest_unprocessed_vertex(evi_list)) != NULL)
+ {
+ if (evi->distance == INT_MAX)
+ break; /* all remaining vertices are unreachable */
+ evi->distance_known = true;
+ if (evi == evi_target)
+ break; /* found shortest path to target */
+ foreach(lc, evi->reachable)
+ {
+ ExtensionVersionInfo *evi2 = (ExtensionVersionInfo *) lfirst(lc);
+ int newdist;
+
+ /* if reject_indirect, treat installable versions as unreachable */
+ if (reject_indirect && evi2->installable)
+ continue;
+ newdist = evi->distance + 1;
+ if (newdist < evi2->distance)
+ {
+ evi2->distance = newdist;
+ evi2->previous = evi;
+ }
+ else if (newdist == evi2->distance &&
+ evi2->previous != NULL &&
+ strcmp(evi->name, evi2->previous->name) < 0)
+ {
+ /*
+ * Break ties in favor of the version name that comes first
+ * according to strcmp(). This behavior is undocumented and
+ * users shouldn't rely on it. We do it just to ensure that
+ * if there is a tie, the update path that is chosen does not
+ * depend on random factors like the order in which directory
+ * entries get visited.
+ */
+ evi2->previous = evi;
+ }
+ }
+ }
+
+ /* Return NIL if target is not reachable from start */
+ if (!evi_target->distance_known)
+ return NIL;
+
+ /* Build and return list of version names representing the update path */
+ result = NIL;
+ for (evi = evi_target; evi != evi_start; evi = evi->previous)
+ result = lcons(evi->name, result);
+
+ return result;
+}
+
+/*
+ * Given a target version that is not directly installable, find the
+ * best installation sequence starting from a directly-installable version.
+ *
+ * evi_list: previously-collected version update graph
+ * evi_target: member of that list that we want to reach
+ *
+ * Returns the best starting-point version, or NULL if there is none.
+ * On success, *best_path is set to the path from the start point.
+ *
+ * If there's more than one possible start point, prefer shorter update paths,
+ * and break any ties arbitrarily on the basis of strcmp'ing the starting
+ * versions' names.
+ */
+static ExtensionVersionInfo *
+find_install_path(List *evi_list, ExtensionVersionInfo *evi_target,
+ List **best_path)
+{
+ ExtensionVersionInfo *evi_start = NULL;
+ ListCell *lc;
+
+ *best_path = NIL;
+
+ /*
+ * We don't expect to be called for an installable target, but if we are,
+ * the answer is easy: just start from there, with an empty update path.
+ */
+ if (evi_target->installable)
+ return evi_target;
+
+ /* Consider all installable versions as start points */
+ foreach(lc, evi_list)
+ {
+ ExtensionVersionInfo *evi1 = (ExtensionVersionInfo *) lfirst(lc);
+ List *path;
+
+ if (!evi1->installable)
+ continue;
+
+ /*
+ * Find shortest path from evi1 to evi_target; but no need to consider
+ * paths going through other installable versions.
+ */
+ path = find_update_path(evi_list, evi1, evi_target, true, true);
+ if (path == NIL)
+ continue;
+
+ /* Remember best path */
+ if (evi_start == NULL ||
+ list_length(path) < list_length(*best_path) ||
+ (list_length(path) == list_length(*best_path) &&
+ strcmp(evi_start->name, evi1->name) < 0))
+ {
+ evi_start = evi1;
+ *best_path = path;
+ }
+ }
+
+ return evi_start;
+}
+
+/*
+ * 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. Note we assume that it does not contain
+ * any non-ASCII data, so there is no need to worry about encoding at this
+ * point.
+ */
+ 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 = 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
+ */
+ 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;
+}
+
+/*
+ * Get the OID of an extension listed in "requires", possibly creating it.
+ */
+static Oid
+get_required_extension(char *reqExtensionName,
+ char *extensionName,
+ char *origSchemaName,
+ bool cascade,
+ List *parents,
+ bool is_create)
+{
+ Oid reqExtensionOid;
+
+ reqExtensionOid = get_extension_oid(reqExtensionName, true);
+ if (!OidIsValid(reqExtensionOid))
+ {
+ if (cascade)
+ {
+ /* Must install it. */
+ ObjectAddress addr;
+ List *cascade_parents;
+ ListCell *lc;
+
+ /* Check extension name validity before trying to cascade. */
+ check_valid_extension_name(reqExtensionName);
+
+ /* Check for cyclic dependency between extensions. */
+ foreach(lc, parents)
+ {
+ char *pname = (char *) lfirst(lc);
+
+ if (strcmp(pname, reqExtensionName) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_RECURSION),
+ errmsg("cyclic dependency detected between extensions \"%s\" and \"%s\"",
+ reqExtensionName, extensionName)));
+ }
+
+ ereport(NOTICE,
+ (errmsg("installing required extension \"%s\"",
+ reqExtensionName)));
+
+ /* Add current extension to list of parents to pass down. */
+ cascade_parents = lappend(list_copy(parents), extensionName);
+
+ /*
+ * Create the required extension. We propagate the SCHEMA option
+ * if any, and CASCADE, but no other options.
+ */
+ addr = CreateExtensionInternal(reqExtensionName,
+ origSchemaName,
+ NULL,
+ cascade,
+ cascade_parents,
+ is_create);
+
+ /* Get its newly-assigned OID. */
+ reqExtensionOid = addr.objectId;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("required extension \"%s\" is not installed",
+ reqExtensionName),
+ is_create ?
+ errhint("Use CREATE EXTENSION ... CASCADE to install required extensions too.") : 0));
+ }
+
+ return reqExtensionOid;
+}
+
+/*
+ * 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)
+ {
+ 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 */
+ 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"),
+ 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);
+ }
+ else
+ elog(ERROR, "unrecognized option: %s", defel->defname);
+ }
+
+ /* Call CreateExtensionInternal to do the real work. */
+ return CreateExtensionInternal(stmt->extname,
+ schemaName,
+ versionName,
+ cascade,
+ NIL,
+ true);
+}
+
+/*
+ * InsertExtensionTuple
+ *
+ * Insert the new pg_extension row, and create extension's dependency entries.
+ * Return the OID assigned to the new row.
+ *
+ * This is exported for the benefit of pg_upgrade, which has to create a
+ * pg_extension entry (and the extension-level dependencies) without
+ * actually running the extension's script.
+ *
+ * extConfig and extCondition should be arrays or PointerGetDatum(NULL).
+ * We declare them as plain Datum to avoid needing array.h in extension.h.
+ */
+ObjectAddress
+InsertExtensionTuple(const char *extName, Oid extOwner,
+ Oid schemaOid, bool relocatable, const char *extVersion,
+ Datum extConfig, Datum extCondition,
+ List *requiredExtensions)
+{
+ Oid extensionOid;
+ Relation rel;
+ Datum values[Natts_pg_extension];
+ bool nulls[Natts_pg_extension];
+ HeapTuple tuple;
+ ObjectAddress myself;
+ ObjectAddress nsp;
+ ObjectAddresses *refobjs;
+ ListCell *lc;
+
+ /*
+ * Build and insert the pg_extension tuple
+ */
+ rel = table_open(ExtensionRelationId, RowExclusiveLock);
+
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+
+ extensionOid = GetNewOidWithIndex(rel, ExtensionOidIndexId,
+ Anum_pg_extension_oid);
+ values[Anum_pg_extension_oid - 1] = ObjectIdGetDatum(extensionOid);
+ values[Anum_pg_extension_extname - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(extName));
+ values[Anum_pg_extension_extowner - 1] = ObjectIdGetDatum(extOwner);
+ values[Anum_pg_extension_extnamespace - 1] = ObjectIdGetDatum(schemaOid);
+ values[Anum_pg_extension_extrelocatable - 1] = BoolGetDatum(relocatable);
+ values[Anum_pg_extension_extversion - 1] = CStringGetTextDatum(extVersion);
+
+ if (extConfig == PointerGetDatum(NULL))
+ nulls[Anum_pg_extension_extconfig - 1] = true;
+ else
+ values[Anum_pg_extension_extconfig - 1] = extConfig;
+
+ if (extCondition == PointerGetDatum(NULL))
+ nulls[Anum_pg_extension_extcondition - 1] = true;
+ else
+ values[Anum_pg_extension_extcondition - 1] = extCondition;
+
+ tuple = heap_form_tuple(rel->rd_att, values, nulls);
+
+ CatalogTupleInsert(rel, tuple);
+
+ heap_freetuple(tuple);
+ table_close(rel, RowExclusiveLock);
+
+ /*
+ * Record dependencies on owner, schema, and prerequisite extensions
+ */
+ recordDependencyOnOwner(ExtensionRelationId, extensionOid, extOwner);
+
+ refobjs = new_object_addresses();
+
+ ObjectAddressSet(myself, ExtensionRelationId, extensionOid);
+
+ ObjectAddressSet(nsp, NamespaceRelationId, schemaOid);
+ add_exact_object_address(&nsp, refobjs);
+
+ foreach(lc, requiredExtensions)
+ {
+ Oid reqext = lfirst_oid(lc);
+ ObjectAddress otherext;
+
+ ObjectAddressSet(otherext, ExtensionRelationId, reqext);
+ add_exact_object_address(&otherext, refobjs);
+ }
+
+ /* Record all of them (this includes duplicate elimination) */
+ record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+ free_object_addresses(refobjs);
+
+ /* Post creation hook for new extension */
+ InvokeObjectPostCreateHook(ExtensionRelationId, extensionOid, 0);
+
+ return myself;
+}
+
+/*
+ * 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];
+
+ /*
+ * Disallow deletion of any extension that's currently open for insertion;
+ * else subsequent executions of recordDependencyOnCurrentExtension()
+ * could create dangling pg_depend records that refer to a no-longer-valid
+ * pg_extension OID. This is needed not so much because we think people
+ * might write "DROP EXTENSION foo" in foo's own script files, as because
+ * errors in dependency management in extension script files could give
+ * rise to cases where an extension is dropped as a result of recursing
+ * from some contained object. Because of that, we must test for the case
+ * here, not at some higher level of the DROP EXTENSION command.
+ */
+ if (extId == CurrentExtensionObject)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot drop extension \"%s\" because it is being modified",
+ get_extension_name(extId))));
+
+ rel = table_open(ExtensionRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_oid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(extId));
+ scandesc = systable_beginscan(rel, ExtensionOidIndexId, true,
+ NULL, 1, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ CatalogTupleDelete(rel, &tuple->t_self);
+
+ systable_endscan(scandesc);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * This function lists the available extensions (one row per primary control
+ * file in the control directory). We parse each 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;
+
+ /* 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");
+
+ /* Build tuplestore to hold the result rows */
+ 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[3];
+ bool nulls[3];
+
+ if (!is_extension_control_filename(de->d_name))
+ continue;
+
+ /* extract extension name from 'name.control' filename */
+ extname = pstrdup(de->d_name);
+ *strrchr(extname, '.') = '\0';
+
+ /* ignore it if it's an auxiliary control file */
+ if (strstr(extname, "--"))
+ continue;
+
+ control = read_extension_control_file(extname);
+
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+
+ /* name */
+ values[0] = DirectFunctionCall1(namein,
+ CStringGetDatum(control->name));
+ /* default_version */
+ if (control->default_version == NULL)
+ nulls[1] = true;
+ else
+ values[1] = CStringGetTextDatum(control->default_version);
+ /* comment */
+ if (control->comment == NULL)
+ nulls[2] = true;
+ else
+ values[2] = CStringGetTextDatum(control->comment);
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ FreeDir(dir);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+/*
+ * This function lists the available extension versions (one row per
+ * extension installation script). For each version, we parse the related
+ * control file(s) and report the interesting fields.
+ *
+ * The system view pg_available_extension_versions provides a user interface
+ * to this SRF, adding information about which versions are installed in the
+ * current DB.
+ */
+Datum
+pg_available_extension_versions(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;
+
+ /* 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");
+
+ /* Build tuplestore to hold the result rows */
+ 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;
+
+ if (!is_extension_control_filename(de->d_name))
+ continue;
+
+ /* extract extension name from 'name.control' filename */
+ extname = pstrdup(de->d_name);
+ *strrchr(extname, '.') = '\0';
+
+ /* ignore it if it's an auxiliary control file */
+ if (strstr(extname, "--"))
+ continue;
+
+ /* read the control file */
+ control = read_extension_control_file(extname);
+
+ /* scan extension's script directory for install scripts */
+ get_available_versions_for_extension(control, tupstore, tupdesc);
+ }
+
+ FreeDir(dir);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+/*
+ * Inner loop for pg_available_extension_versions:
+ * read versions of one extension, add rows to tupstore
+ */
+static void
+get_available_versions_for_extension(ExtensionControlFile *pcontrol,
+ Tuplestorestate *tupstore,
+ TupleDesc tupdesc)
+{
+ List *evi_list;
+ ListCell *lc;
+
+ /* Extract the version update graph from the script directory */
+ evi_list = get_ext_ver_list(pcontrol);
+
+ /* For each installable version ... */
+ foreach(lc, evi_list)
+ {
+ ExtensionVersionInfo *evi = (ExtensionVersionInfo *) lfirst(lc);
+ ExtensionControlFile *control;
+ Datum values[8];
+ bool nulls[8];
+ ListCell *lc2;
+
+ if (!evi->installable)
+ continue;
+
+ /*
+ * Fetch parameters for specific version (pcontrol is not changed)
+ */
+ control = read_extension_aux_control_file(pcontrol, evi->name);
+
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+
+ /* name */
+ values[0] = DirectFunctionCall1(namein,
+ CStringGetDatum(control->name));
+ /* version */
+ values[1] = CStringGetTextDatum(evi->name);
+ /* superuser */
+ values[2] = BoolGetDatum(control->superuser);
+ /* trusted */
+ values[3] = BoolGetDatum(control->trusted);
+ /* relocatable */
+ values[4] = BoolGetDatum(control->relocatable);
+ /* schema */
+ if (control->schema == NULL)
+ nulls[5] = true;
+ else
+ values[5] = DirectFunctionCall1(namein,
+ CStringGetDatum(control->schema));
+ /* requires */
+ if (control->requires == NIL)
+ nulls[6] = true;
+ else
+ values[6] = convert_requires_to_datum(control->requires);
+ /* comment */
+ if (control->comment == NULL)
+ nulls[7] = true;
+ else
+ values[7] = CStringGetTextDatum(control->comment);
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+
+ /*
+ * Find all non-directly-installable versions that would be installed
+ * starting from this version, and report them, inheriting the
+ * parameters that aren't changed in updates from this version.
+ */
+ foreach(lc2, evi_list)
+ {
+ ExtensionVersionInfo *evi2 = (ExtensionVersionInfo *) lfirst(lc2);
+ List *best_path;
+
+ if (evi2->installable)
+ continue;
+ if (find_install_path(evi_list, evi2, &best_path) == evi)
+ {
+ /*
+ * Fetch parameters for this version (pcontrol is not changed)
+ */
+ control = read_extension_aux_control_file(pcontrol, evi2->name);
+
+ /* name stays the same */
+ /* version */
+ values[1] = CStringGetTextDatum(evi2->name);
+ /* superuser */
+ values[2] = BoolGetDatum(control->superuser);
+ /* trusted */
+ values[3] = BoolGetDatum(control->trusted);
+ /* relocatable */
+ values[4] = BoolGetDatum(control->relocatable);
+ /* schema stays the same */
+ /* requires */
+ if (control->requires == NIL)
+ nulls[6] = true;
+ else
+ {
+ values[6] = convert_requires_to_datum(control->requires);
+ nulls[6] = false;
+ }
+ /* comment stays the same */
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+ }
+ }
+}
+
+/*
+ * Test whether the given extension exists (not whether it's installed)
+ *
+ * This checks for the existence of a matching control file in the extension
+ * directory. That's not a bulletproof check, since the file might be
+ * invalid, but this is only used for hints so it doesn't have to be 100%
+ * right.
+ */
+bool
+extension_file_exists(const char *extensionName)
+{
+ bool result = false;
+ char *location;
+ DIR *dir;
+ struct dirent *de;
+
+ location = get_extension_control_directory();
+ dir = AllocateDir(location);
+
+ /*
+ * If the control directory doesn't exist, we want to silently return
+ * false. Any other error will be reported by ReadDir.
+ */
+ if (dir == NULL && errno == ENOENT)
+ {
+ /* do nothing */
+ }
+ else
+ {
+ while ((de = ReadDir(dir, location)) != NULL)
+ {
+ char *extname;
+
+ if (!is_extension_control_filename(de->d_name))
+ continue;
+
+ /* extract extension name from 'name.control' filename */
+ extname = pstrdup(de->d_name);
+ *strrchr(extname, '.') = '\0';
+
+ /* ignore it if it's an auxiliary control file */
+ if (strstr(extname, "--"))
+ continue;
+
+ /* done if it matches request */
+ if (strcmp(extname, extensionName) == 0)
+ {
+ result = true;
+ break;
+ }
+ }
+
+ FreeDir(dir);
+ }
+
+ return result;
+}
+
+/*
+ * Convert a list of extension names to a name[] Datum
+ */
+static Datum
+convert_requires_to_datum(List *requires)
+{
+ Datum *datums;
+ int ndatums;
+ ArrayType *a;
+ ListCell *lc;
+
+ ndatums = list_length(requires);
+ datums = (Datum *) palloc(ndatums * sizeof(Datum));
+ ndatums = 0;
+ foreach(lc, requires)
+ {
+ char *curreq = (char *) lfirst(lc);
+
+ datums[ndatums++] =
+ DirectFunctionCall1(namein, CStringGetDatum(curreq));
+ }
+ a = construct_array(datums, ndatums,
+ NAMEOID,
+ NAMEDATALEN, false, TYPALIGN_CHAR);
+ return PointerGetDatum(a);
+}
+
+/*
+ * This function reports the version update paths that exist for the
+ * specified extension.
+ */
+Datum
+pg_extension_update_paths(PG_FUNCTION_ARGS)
+{
+ Name extname = PG_GETARG_NAME(0);
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ List *evi_list;
+ ExtensionControlFile *control;
+ ListCell *lc1;
+
+ /* Check extension name validity before any filesystem access */
+ check_valid_extension_name(NameStr(*extname));
+
+ /* 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");
+
+ /* Build tuplestore to hold the result rows */
+ 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);
+
+ /* Read the extension's control file */
+ control = read_extension_control_file(NameStr(*extname));
+
+ /* Extract the version update graph from the script directory */
+ evi_list = get_ext_ver_list(control);
+
+ /* Iterate over all pairs of versions */
+ foreach(lc1, evi_list)
+ {
+ ExtensionVersionInfo *evi1 = (ExtensionVersionInfo *) lfirst(lc1);
+ ListCell *lc2;
+
+ foreach(lc2, evi_list)
+ {
+ ExtensionVersionInfo *evi2 = (ExtensionVersionInfo *) lfirst(lc2);
+ List *path;
+ Datum values[3];
+ bool nulls[3];
+
+ if (evi1 == evi2)
+ continue;
+
+ /* Find shortest path from evi1 to evi2 */
+ path = find_update_path(evi_list, evi1, evi2, false, true);
+
+ /* Emit result row */
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+
+ /* source */
+ values[0] = CStringGetTextDatum(evi1->name);
+ /* target */
+ values[1] = CStringGetTextDatum(evi2->name);
+ /* path */
+ if (path == NIL)
+ nulls[2] = true;
+ else
+ {
+ StringInfoData pathbuf;
+ ListCell *lcv;
+
+ initStringInfo(&pathbuf);
+ /* The path doesn't include start vertex, but show it */
+ appendStringInfoString(&pathbuf, evi1->name);
+ foreach(lcv, path)
+ {
+ char *versionName = (char *) lfirst(lcv);
+
+ appendStringInfoString(&pathbuf, "--");
+ appendStringInfoString(&pathbuf, versionName);
+ }
+ values[2] = CStringGetTextDatum(pathbuf.data);
+ pfree(pathbuf.data);
+ }
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+ }
+
+ /* 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_PP(1);
+ char *tablename;
+ Relation extRel;
+ ScanKeyData key[1];
+ SysScanDesc extScan;
+ HeapTuple extTup;
+ Datum arrayDatum;
+ Datum elementDatum;
+ int arrayLength;
+ 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("%s can only be called from an SQL script executed by CREATE EXTENSION",
+ "pg_extension_config_dump()")));
+
+ /*
+ * Check that the table exists and is a member of the extension being
+ * created. This ensures that we don't need to register an additional
+ * 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_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ 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.
+ *
+ * If the table is already in extconfig, treat this as an update of the
+ * WHERE condition.
+ */
+
+ /* Find the pg_extension tuple */
+ extRel = table_open(ExtensionRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_extension_oid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(CurrentExtensionObject));
+
+ extScan = systable_beginscan(extRel, ExtensionOidIndexId, true,
+ NULL, 1, key);
+
+ extTup = systable_getnext(extScan);
+
+ if (!HeapTupleIsValid(extTup)) /* should not happen */
+ elog(ERROR, "could not find tuple for extension %u",
+ 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)
+ {
+ /* Previously empty extconfig, so build 1-element array */
+ arrayLength = 0;
+ arrayIndex = 1;
+
+ a = construct_array(&elementDatum, 1,
+ OIDOID,
+ sizeof(Oid), true, TYPALIGN_INT);
+ }
+ else
+ {
+ /* Modify or extend existing extconfig array */
+ Oid *arrayData;
+ int i;
+
+ a = DatumGetArrayTypeP(arrayDatum);
+
+ arrayLength = ARR_DIMS(a)[0];
+ if (ARR_NDIM(a) != 1 ||
+ ARR_LBOUND(a)[0] != 1 ||
+ arrayLength < 0 ||
+ ARR_HASNULL(a) ||
+ ARR_ELEMTYPE(a) != OIDOID)
+ elog(ERROR, "extconfig is not a 1-D Oid array");
+ arrayData = (Oid *) ARR_DATA_PTR(a);
+
+ arrayIndex = arrayLength + 1; /* set up to add after end */
+
+ for (i = 0; i < arrayLength; i++)
+ {
+ if (arrayData[i] == tableoid)
+ {
+ arrayIndex = i + 1; /* replace this element instead */
+ break;
+ }
+ }
+
+ a = array_set(a, 1, &arrayIndex,
+ elementDatum,
+ false,
+ -1 /* varlena array */ ,
+ sizeof(Oid) /* OID's typlen */ ,
+ true /* OID's typbyval */ ,
+ TYPALIGN_INT /* 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)
+ {
+ if (arrayLength != 0)
+ elog(ERROR, "extconfig and extcondition arrays do not match");
+
+ a = construct_array(&elementDatum, 1,
+ TEXTOID,
+ -1, false, TYPALIGN_INT);
+ }
+ else
+ {
+ a = DatumGetArrayTypeP(arrayDatum);
+
+ if (ARR_NDIM(a) != 1 ||
+ ARR_LBOUND(a)[0] != 1 ||
+ ARR_HASNULL(a) ||
+ ARR_ELEMTYPE(a) != TEXTOID)
+ elog(ERROR, "extcondition is not a 1-D text array");
+ if (ARR_DIMS(a)[0] != arrayLength)
+ elog(ERROR, "extconfig and extcondition arrays do not match");
+
+ /* Add or replace at same index as in extconfig */
+ a = array_set(a, 1, &arrayIndex,
+ elementDatum,
+ false,
+ -1 /* varlena array */ ,
+ -1 /* TEXT's typlen */ ,
+ false /* TEXT's typbyval */ ,
+ TYPALIGN_INT /* 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);
+
+ CatalogTupleUpdate(extRel, &extTup->t_self, extTup);
+
+ systable_endscan(extScan);
+
+ table_close(extRel, RowExclusiveLock);
+
+ PG_RETURN_VOID();
+}
+
+/*
+ * extension_config_remove
+ *
+ * Remove the specified table OID from extension's extconfig, if present.
+ * This is not currently exposed as a function, but it could be;
+ * for now, we just invoke it from ALTER EXTENSION DROP.
+ */
+static void
+extension_config_remove(Oid extensionoid, Oid tableoid)
+{
+ Relation extRel;
+ ScanKeyData key[1];
+ SysScanDesc extScan;
+ HeapTuple extTup;
+ Datum arrayDatum;
+ int arrayLength;
+ int arrayIndex;
+ bool isnull;
+ Datum repl_val[Natts_pg_extension];
+ bool repl_null[Natts_pg_extension];
+ bool repl_repl[Natts_pg_extension];
+ ArrayType *a;
+
+ /* Find the pg_extension tuple */
+ extRel = table_open(ExtensionRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_extension_oid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(extensionoid));
+
+ extScan = systable_beginscan(extRel, ExtensionOidIndexId, true,
+ NULL, 1, key);
+
+ extTup = systable_getnext(extScan);
+
+ if (!HeapTupleIsValid(extTup)) /* should not happen */
+ elog(ERROR, "could not find tuple for extension %u",
+ extensionoid);
+
+ /* Search extconfig for the tableoid */
+ arrayDatum = heap_getattr(extTup, Anum_pg_extension_extconfig,
+ RelationGetDescr(extRel), &isnull);
+ if (isnull)
+ {
+ /* nothing to do */
+ a = NULL;
+ arrayLength = 0;
+ arrayIndex = -1;
+ }
+ else
+ {
+ Oid *arrayData;
+ int i;
+
+ a = DatumGetArrayTypeP(arrayDatum);
+
+ arrayLength = ARR_DIMS(a)[0];
+ if (ARR_NDIM(a) != 1 ||
+ ARR_LBOUND(a)[0] != 1 ||
+ arrayLength < 0 ||
+ ARR_HASNULL(a) ||
+ ARR_ELEMTYPE(a) != OIDOID)
+ elog(ERROR, "extconfig is not a 1-D Oid array");
+ arrayData = (Oid *) ARR_DATA_PTR(a);
+
+ arrayIndex = -1; /* flag for no deletion needed */
+
+ for (i = 0; i < arrayLength; i++)
+ {
+ if (arrayData[i] == tableoid)
+ {
+ arrayIndex = i; /* index to remove */
+ break;
+ }
+ }
+ }
+
+ /* If tableoid is not in extconfig, nothing to do */
+ if (arrayIndex < 0)
+ {
+ systable_endscan(extScan);
+ table_close(extRel, RowExclusiveLock);
+ return;
+ }
+
+ /* Modify or delete the extconfig value */
+ memset(repl_val, 0, sizeof(repl_val));
+ memset(repl_null, false, sizeof(repl_null));
+ memset(repl_repl, false, sizeof(repl_repl));
+
+ if (arrayLength <= 1)
+ {
+ /* removing only element, just set array to null */
+ repl_null[Anum_pg_extension_extconfig - 1] = true;
+ }
+ else
+ {
+ /* squeeze out the target element */
+ Datum *dvalues;
+ int nelems;
+ int i;
+
+ /* We already checked there are no nulls */
+ deconstruct_array(a, OIDOID, sizeof(Oid), true, TYPALIGN_INT,
+ &dvalues, NULL, &nelems);
+
+ for (i = arrayIndex; i < arrayLength - 1; i++)
+ dvalues[i] = dvalues[i + 1];
+
+ a = construct_array(dvalues, arrayLength - 1,
+ OIDOID, sizeof(Oid), true, TYPALIGN_INT);
+
+ repl_val[Anum_pg_extension_extconfig - 1] = PointerGetDatum(a);
+ }
+ repl_repl[Anum_pg_extension_extconfig - 1] = true;
+
+ /* Modify or delete the extcondition value */
+ arrayDatum = heap_getattr(extTup, Anum_pg_extension_extcondition,
+ RelationGetDescr(extRel), &isnull);
+ if (isnull)
+ {
+ elog(ERROR, "extconfig and extcondition arrays do not match");
+ }
+ else
+ {
+ a = DatumGetArrayTypeP(arrayDatum);
+
+ if (ARR_NDIM(a) != 1 ||
+ ARR_LBOUND(a)[0] != 1 ||
+ ARR_HASNULL(a) ||
+ ARR_ELEMTYPE(a) != TEXTOID)
+ elog(ERROR, "extcondition is not a 1-D text array");
+ if (ARR_DIMS(a)[0] != arrayLength)
+ elog(ERROR, "extconfig and extcondition arrays do not match");
+ }
+
+ if (arrayLength <= 1)
+ {
+ /* removing only element, just set array to null */
+ repl_null[Anum_pg_extension_extcondition - 1] = true;
+ }
+ else
+ {
+ /* squeeze out the target element */
+ Datum *dvalues;
+ int nelems;
+ int i;
+
+ /* We already checked there are no nulls */
+ deconstruct_array(a, TEXTOID, -1, false, TYPALIGN_INT,
+ &dvalues, NULL, &nelems);
+
+ for (i = arrayIndex; i < arrayLength - 1; i++)
+ dvalues[i] = dvalues[i + 1];
+
+ a = construct_array(dvalues, arrayLength - 1,
+ TEXTOID, -1, false, TYPALIGN_INT);
+
+ 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);
+
+ CatalogTupleUpdate(extRel, &extTup->t_self, extTup);
+
+ systable_endscan(extScan);
+
+ table_close(extRel, RowExclusiveLock);
+}
+
+/*
+ * Execute ALTER EXTENSION SET SCHEMA
+ */
+ObjectAddress
+AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *oldschema)
+{
+ Oid extensionOid;
+ Oid nspOid;
+ Oid oldNspOid = InvalidOid;
+ AclResult aclresult;
+ Relation extRel;
+ ScanKeyData key[2];
+ SysScanDesc extScan;
+ HeapTuple extTup;
+ Form_pg_extension extForm;
+ Relation depRel;
+ SysScanDesc depScan;
+ HeapTuple depTup;
+ ObjectAddresses *objsMoved;
+ ObjectAddress extAddr;
+
+ extensionOid = get_extension_oid(extensionName, false);
+
+ nspOid = LookupCreationNamespace(newschema);
+
+ /*
+ * Permission check: must own extension. Note that we don't bother to
+ * check ownership of the individual member objects ...
+ */
+ if (!pg_extension_ownercheck(extensionOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_EXTENSION,
+ extensionName);
+
+ /* Permission check: must have creation rights in target namespace */
+ aclresult = pg_namespace_aclcheck(nspOid, GetUserId(), ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_SCHEMA, newschema);
+
+ /*
+ * If the schema is currently a member of the extension, disallow moving
+ * the extension into the schema. That would create a dependency loop.
+ */
+ if (getExtensionOfObject(NamespaceRelationId, nspOid) == extensionOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot move extension \"%s\" into schema \"%s\" "
+ "because the extension contains the schema",
+ extensionName, newschema)));
+
+ /* Locate the pg_extension tuple */
+ extRel = table_open(ExtensionRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_extension_oid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(extensionOid));
+
+ extScan = systable_beginscan(extRel, ExtensionOidIndexId, true,
+ NULL, 1, key);
+
+ extTup = systable_getnext(extScan);
+
+ if (!HeapTupleIsValid(extTup)) /* should not happen */
+ elog(ERROR, "could not find tuple for extension %u",
+ 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)
+ {
+ table_close(extRel, RowExclusiveLock);
+ return InvalidObjectAddress;
+ }
+
+ /* 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))));
+
+ objsMoved = new_object_addresses();
+
+ /*
+ * Scan pg_depend to find objects that depend directly on the extension,
+ * and alter each one's schema.
+ */
+ depRel = table_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,
+ NULL, 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");
+
+ /* Relocate the object */
+ dep_oldNspOid = AlterObjectNamespace_oid(dep.classId,
+ dep.objectId,
+ nspOid,
+ objsMoved);
+
+ /*
+ * 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, false),
+ get_namespace_name(oldNspOid))));
+ }
+
+ /* report old schema, if caller wants it */
+ if (oldschema)
+ *oldschema = oldNspOid;
+
+ systable_endscan(depScan);
+
+ relation_close(depRel, AccessShareLock);
+
+ /* Now adjust pg_extension.extnamespace */
+ extForm->extnamespace = nspOid;
+
+ CatalogTupleUpdate(extRel, &extTup->t_self, extTup);
+
+ table_close(extRel, RowExclusiveLock);
+
+ /* update dependencies to point to the new schema */
+ changeDependencyFor(ExtensionRelationId, extensionOid,
+ NamespaceRelationId, oldNspOid, nspOid);
+
+ InvokeObjectPostAlterHook(ExtensionRelationId, extensionOid, 0);
+
+ ObjectAddressSet(extAddr, ExtensionRelationId, extensionOid);
+
+ return extAddr;
+}
+
+/*
+ * Execute ALTER EXTENSION UPDATE
+ */
+ObjectAddress
+ExecAlterExtensionStmt(ParseState *pstate, AlterExtensionStmt *stmt)
+{
+ DefElem *d_new_version = NULL;
+ char *versionName;
+ char *oldVersionName;
+ ExtensionControlFile *control;
+ Oid extensionOid;
+ Relation extRel;
+ ScanKeyData key[1];
+ SysScanDesc extScan;
+ HeapTuple extTup;
+ List *updateVersions;
+ Datum datum;
+ bool isnull;
+ ListCell *lc;
+ ObjectAddress address;
+
+ /*
+ * We use global variables to track the extension being created, so we can
+ * create/update only one extension at the same time.
+ */
+ if (creating_extension)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("nested ALTER EXTENSION is not supported")));
+
+ /*
+ * Look up the extension --- it must already exist in pg_extension
+ */
+ extRel = table_open(ExtensionRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_extension_extname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->extname));
+
+ extScan = systable_beginscan(extRel, ExtensionNameIndexId, true,
+ NULL, 1, key);
+
+ extTup = systable_getnext(extScan);
+
+ if (!HeapTupleIsValid(extTup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("extension \"%s\" does not exist",
+ stmt->extname)));
+
+ extensionOid = ((Form_pg_extension) GETSTRUCT(extTup))->oid;
+
+ /*
+ * Determine the existing version we are updating from
+ */
+ datum = heap_getattr(extTup, Anum_pg_extension_extversion,
+ RelationGetDescr(extRel), &isnull);
+ if (isnull)
+ elog(ERROR, "extversion is null");
+ oldVersionName = text_to_cstring(DatumGetTextPP(datum));
+
+ systable_endscan(extScan);
+
+ table_close(extRel, AccessShareLock);
+
+ /* Permission check: must own extension */
+ if (!pg_extension_ownercheck(extensionOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_EXTENSION,
+ stmt->extname);
+
+ /*
+ * 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.
+ */
+ 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, "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;
+ }
+ else
+ elog(ERROR, "unrecognized option: %s", defel->defname);
+ }
+
+ /*
+ * Determine the version to update to
+ */
+ if (d_new_version && d_new_version->arg)
+ versionName = strVal(d_new_version->arg);
+ else if (control->default_version)
+ versionName = control->default_version;
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("version to install must be specified")));
+ versionName = NULL; /* keep compiler quiet */
+ }
+ check_valid_version_name(versionName);
+
+ /*
+ * If we're already at that version, just say so
+ */
+ if (strcmp(oldVersionName, versionName) == 0)
+ {
+ ereport(NOTICE,
+ (errmsg("version \"%s\" of extension \"%s\" is already installed",
+ versionName, stmt->extname)));
+ return InvalidObjectAddress;
+ }
+
+ /*
+ * Identify the series of update script files we need to execute
+ */
+ updateVersions = identify_update_path(control,
+ oldVersionName,
+ versionName);
+
+ /*
+ * Update the pg_extension row and execute the update scripts, one at a
+ * time
+ */
+ ApplyExtensionUpdates(extensionOid, control,
+ oldVersionName, updateVersions,
+ NULL, false, false);
+
+ ObjectAddressSet(address, ExtensionRelationId, extensionOid);
+
+ return address;
+}
+
+/*
+ * Apply a series of update scripts as though individual ALTER EXTENSION
+ * UPDATE commands had been given, including altering the pg_extension row
+ * and dependencies each time.
+ *
+ * This might be more work than necessary, but it ensures that old update
+ * scripts don't break if newer versions have different control parameters.
+ */
+static void
+ApplyExtensionUpdates(Oid extensionOid,
+ ExtensionControlFile *pcontrol,
+ const char *initialVersion,
+ List *updateVersions,
+ char *origSchemaName,
+ bool cascade,
+ bool is_create)
+{
+ const char *oldVersionName = initialVersion;
+ ListCell *lcv;
+
+ foreach(lcv, updateVersions)
+ {
+ char *versionName = (char *) lfirst(lcv);
+ ExtensionControlFile *control;
+ char *schemaName;
+ Oid schemaOid;
+ List *requiredExtensions;
+ List *requiredSchemas;
+ Relation extRel;
+ ScanKeyData key[1];
+ SysScanDesc extScan;
+ HeapTuple extTup;
+ Form_pg_extension extForm;
+ Datum values[Natts_pg_extension];
+ bool nulls[Natts_pg_extension];
+ bool repl[Natts_pg_extension];
+ ObjectAddress myself;
+ ListCell *lc;
+
+ /*
+ * Fetch parameters for specific version (pcontrol is not changed)
+ */
+ control = read_extension_aux_control_file(pcontrol, versionName);
+
+ /* Find the pg_extension tuple */
+ extRel = table_open(ExtensionRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_extension_oid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(extensionOid));
+
+ extScan = systable_beginscan(extRel, ExtensionOidIndexId, true,
+ NULL, 1, key);
+
+ extTup = systable_getnext(extScan);
+
+ if (!HeapTupleIsValid(extTup)) /* should not happen */
+ elog(ERROR, "could not find tuple for extension %u",
+ extensionOid);
+
+ extForm = (Form_pg_extension) GETSTRUCT(extTup);
+
+ /*
+ * Determine the target schema (set by original install)
+ */
+ schemaOid = extForm->extnamespace;
+ schemaName = get_namespace_name(schemaOid);
+
+ /*
+ * Modify extrelocatable and extversion in the pg_extension tuple
+ */
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+ memset(repl, 0, sizeof(repl));
+
+ values[Anum_pg_extension_extrelocatable - 1] =
+ BoolGetDatum(control->relocatable);
+ repl[Anum_pg_extension_extrelocatable - 1] = true;
+ values[Anum_pg_extension_extversion - 1] =
+ CStringGetTextDatum(versionName);
+ repl[Anum_pg_extension_extversion - 1] = true;
+
+ extTup = heap_modify_tuple(extTup, RelationGetDescr(extRel),
+ values, nulls, repl);
+
+ CatalogTupleUpdate(extRel, &extTup->t_self, extTup);
+
+ systable_endscan(extScan);
+
+ table_close(extRel, RowExclusiveLock);
+
+ /*
+ * Look up the prerequisite extensions for this version, 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,
+ control->name,
+ origSchemaName,
+ cascade,
+ NIL,
+ is_create);
+ reqschema = get_extension_schema(reqext);
+ requiredExtensions = lappend_oid(requiredExtensions, reqext);
+ requiredSchemas = lappend_oid(requiredSchemas, reqschema);
+ }
+
+ /*
+ * Remove and recreate dependencies on prerequisite extensions
+ */
+ deleteDependencyRecordsForClass(ExtensionRelationId, extensionOid,
+ ExtensionRelationId,
+ DEPENDENCY_NORMAL);
+
+ myself.classId = ExtensionRelationId;
+ myself.objectId = extensionOid;
+ myself.objectSubId = 0;
+
+ foreach(lc, requiredExtensions)
+ {
+ Oid reqext = lfirst_oid(lc);
+ ObjectAddress otherext;
+
+ otherext.classId = ExtensionRelationId;
+ otherext.objectId = reqext;
+ otherext.objectSubId = 0;
+
+ recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
+ }
+
+ InvokeObjectPostAlterHook(ExtensionRelationId, extensionOid, 0);
+
+ /*
+ * Finally, execute the update script file
+ */
+ execute_extension_script(extensionOid, control,
+ oldVersionName, versionName,
+ requiredSchemas,
+ schemaName, schemaOid);
+
+ /*
+ * Update prior-version name and loop around. Since
+ * execute_sql_string did a final CommandCounterIncrement, we can
+ * update the pg_extension row again.
+ */
+ oldVersionName = versionName;
+ }
+}
+
+/*
+ * Execute ALTER EXTENSION ADD/DROP
+ *
+ * Return value is the address of the altered extension.
+ *
+ * objAddr is an output argument which, if not NULL, is set to the address of
+ * the added/dropped object.
+ */
+ObjectAddress
+ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
+ ObjectAddress *objAddr)
+{
+ ObjectAddress extension;
+ ObjectAddress object;
+ Relation relation;
+ Oid oldExtension;
+
+ switch (stmt->objtype)
+ {
+ case OBJECT_DATABASE:
+ case OBJECT_EXTENSION:
+ case OBJECT_INDEX:
+ case OBJECT_PUBLICATION:
+ case OBJECT_ROLE:
+ case OBJECT_STATISTIC_EXT:
+ case OBJECT_SUBSCRIPTION:
+ case OBJECT_TABLESPACE:
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot add an object of this type to an extension")));
+ break;
+ default:
+ /* OK */
+ break;
+ }
+
+ /*
+ * Find the extension and acquire a lock on it, to ensure it doesn't get
+ * dropped concurrently. A sharable lock seems sufficient: there's no
+ * reason not to allow other sorts of manipulations, such as add/drop of
+ * other objects, to occur concurrently. Concurrently adding/dropping the
+ * *same* object would be bad, but we prevent that by using a non-sharable
+ * lock on the individual object, below.
+ */
+ extension = get_object_address(OBJECT_EXTENSION,
+ (Node *) makeString(stmt->extname),
+ &relation, AccessShareLock, false);
+
+ /* Permission check: must own extension */
+ if (!pg_extension_ownercheck(extension.objectId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_EXTENSION,
+ stmt->extname);
+
+ /*
+ * Translate the parser representation that identifies the object into an
+ * ObjectAddress. get_object_address() will throw an error if the object
+ * does not exist, and will also acquire a lock on the object to guard
+ * against concurrent DROP and ALTER EXTENSION ADD/DROP operations.
+ */
+ object = get_object_address(stmt->objtype, stmt->object,
+ &relation, ShareUpdateExclusiveLock, false);
+
+ Assert(object.objectSubId == 0);
+ if (objAddr)
+ *objAddr = object;
+
+ /* Permission check: must own target object, too */
+ check_object_ownership(GetUserId(), stmt->objtype, object,
+ stmt->object, relation);
+
+ /*
+ * Check existing extension membership.
+ */
+ oldExtension = getExtensionOfObject(object.classId, object.objectId);
+
+ if (stmt->action > 0)
+ {
+ /*
+ * ADD, so complain if object is already attached to some extension.
+ */
+ if (OidIsValid(oldExtension))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("%s is already a member of extension \"%s\"",
+ getObjectDescription(&object, false),
+ get_extension_name(oldExtension))));
+
+ /*
+ * Prevent a schema from being added to an extension if the schema
+ * contains the extension. That would create a dependency loop.
+ */
+ if (object.classId == NamespaceRelationId &&
+ object.objectId == get_extension_schema(extension.objectId))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot add schema \"%s\" to extension \"%s\" "
+ "because the schema contains the extension",
+ get_namespace_name(object.objectId),
+ stmt->extname)));
+
+ /*
+ * OK, add the dependency.
+ */
+ recordDependencyOn(&object, &extension, DEPENDENCY_EXTENSION);
+
+ /*
+ * Also record the initial ACL on the object, if any.
+ *
+ * Note that this will handle the object's ACLs, as well as any ACLs
+ * on object subIds. (In other words, when the object is a table,
+ * this will record the table's ACL and the ACLs for the columns on
+ * the table, if any).
+ */
+ recordExtObjInitPriv(object.objectId, object.classId);
+ }
+ else
+ {
+ /*
+ * DROP, so complain if it's not a member.
+ */
+ if (oldExtension != extension.objectId)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("%s is not a member of extension \"%s\"",
+ getObjectDescription(&object, false),
+ stmt->extname)));
+
+ /*
+ * OK, drop the dependency.
+ */
+ if (deleteDependencyRecordsForClass(object.classId, object.objectId,
+ ExtensionRelationId,
+ DEPENDENCY_EXTENSION) != 1)
+ elog(ERROR, "unexpected number of extension dependency records");
+
+ /*
+ * If it's a relation, it might have an entry in the extension's
+ * extconfig array, which we must remove.
+ */
+ if (object.classId == RelationRelationId)
+ extension_config_remove(extension.objectId, object.objectId);
+
+ /*
+ * Remove all the initial ACLs, if any.
+ *
+ * Note that this will remove the object's ACLs, as well as any ACLs
+ * on object subIds. (In other words, when the object is a table,
+ * this will remove the table's ACL and the ACLs for the columns on
+ * the table, if any).
+ */
+ removeExtObjInitPriv(object.objectId, object.classId);
+ }
+
+ InvokeObjectPostAlterHook(ExtensionRelationId, extension.objectId, 0);
+
+ /*
+ * If get_object_address() opened the relation for us, we close it to keep
+ * the reference count correct - but we retain any locks acquired by
+ * get_object_address() until commit time, to guard against concurrent
+ * activity.
+ */
+ if (relation != NULL)
+ relation_close(relation, NoLock);
+
+ return extension;
+}
+
+/*
+ * Read the whole of file into memory.
+ *
+ * The file contents are returned as a single palloc'd chunk. For convenience
+ * of the callers, an extra \0 byte is added to the end.
+ */
+static char *
+read_whole_file(const char *filename, int *length)
+{
+ char *buf;
+ FILE *file;
+ size_t bytes_to_read;
+ struct stat fst;
+
+ if (stat(filename, &fst) < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m", filename)));
+
+ if (fst.st_size > (MaxAllocSize - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("file \"%s\" is too large", filename)));
+ bytes_to_read = (size_t) fst.st_size;
+
+ if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\" for reading: %m",
+ filename)));
+
+ buf = (char *) palloc(bytes_to_read + 1);
+
+ *length = fread(buf, 1, bytes_to_read, file);
+
+ if (ferror(file))
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read file \"%s\": %m", filename)));
+
+ FreeFile(file);
+
+ buf[*length] = '\0';
+ return buf;
+}