aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands/extension.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2011-02-11 21:25:20 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2011-02-11 21:25:57 -0500
commit1214749901fc3c66732cfd9f276b989635c01360 (patch)
tree8c2fa16c2ac4a79cb0408b982b85de7d7c960bbf /src/backend/commands/extension.c
parent60141eefaf28e4ff5e406952740423dd6fd16266 (diff)
downloadpostgresql-1214749901fc3c66732cfd9f276b989635c01360.tar.gz
postgresql-1214749901fc3c66732cfd9f276b989635c01360.zip
Add support for multiple versions of an extension and ALTER EXTENSION UPDATE.
This follows recent discussions, so it's quite a bit different from Dimitri's original. There will probably be more changes once we get a bit of experience with it, but let's get it in and start playing with it. This is still just core code. I'll start converting contrib modules shortly. Dimitri Fontaine and Tom Lane
Diffstat (limited to 'src/backend/commands/extension.c')
-rw-r--r--src/backend/commands/extension.c753
1 files changed, 694 insertions, 59 deletions
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index bc121808bec..5d8b36b0966 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -53,17 +53,21 @@
#include "utils/tqual.h"
+/* Globally visible state variables */
bool creating_extension = false;
Oid CurrentExtensionObject = InvalidOid;
+/* Character that separates extension & version names in a script filename */
+#define EXT_VERSION_SEP '-'
+
/*
* Internal data structure to hold the results of parsing a control file
*/
typedef struct ExtensionControlFile
{
char *name; /* name of the extension */
- char *script; /* filename of the installation script */
- char *version; /* version ID, if any */
+ char *directory; /* directory for script files */
+ char *default_version; /* default install target version, if any */
char *comment; /* comment, if any */
char *schema; /* target schema (allowed if !relocatable) */
bool relocatable; /* is ALTER EXTENSION SET SCHEMA supported? */
@@ -71,6 +75,19 @@ typedef struct ExtensionControlFile
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 */
+ /* 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;
+
/*
* get_extension_oid - given an extension name, look up the OID
@@ -197,6 +214,44 @@ get_extension_schema(Oid ext_oid)
}
/*
+ * Utility functions to check validity of extension and version names
+ */
+static void
+check_valid_extension_name(const char *extensionname)
+{
+ /*
+ * No directory separators (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)
+{
+ /* No separators --- would risk confusion of install vs update scripts */
+ if (strchr(versionname, EXT_VERSION_SEP))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid extension version name: \"%s\"", versionname),
+ errdetail("Version names must not contain the character \"%c\".",
+ EXT_VERSION_SEP)));
+ /*
+ * No directory separators (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
@@ -207,6 +262,14 @@ is_extension_control_filename(const char *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)
{
@@ -228,83 +291,150 @@ get_extension_control_filename(const char *extname)
get_share_path(my_exec_path, sharepath);
result = (char *) palloc(MAXPGPATH);
- snprintf(result, MAXPGPATH, "%s/contrib/%s.control", sharepath, extname);
+ snprintf(result, MAXPGPATH, "%s/contrib/%s.control",
+ sharepath, extname);
return result;
}
-/*
- * Given a relative pathname such as "name.sql", return the full path to
- * the script file. If given an absolute name, just return it.
- */
static char *
-get_extension_absolute_path(const char *filename)
+get_extension_script_directory(ExtensionControlFile *control)
{
char sharepath[MAXPGPATH];
char *result;
- if (is_absolute_path(filename))
- return pstrdup(filename);
+ /*
+ * The directory parameter can be omitted, absolute, or relative to the
+ * control-file 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/contrib/%s", sharepath, filename);
+ snprintf(result, MAXPGPATH, "%s/contrib/%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%c%s.control",
+ scriptdir, control->name, EXT_VERSION_SEP, 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%c%s%c%s.sql",
+ scriptdir, control->name, EXT_VERSION_SEP, from_version,
+ EXT_VERSION_SEP, version);
+ else
+ snprintf(result, MAXPGPATH, "%s/%s%c%s.sql",
+ scriptdir, control->name, EXT_VERSION_SEP, version);
+
+ pfree(scriptdir);
return result;
}
/*
- * Read the control file for the specified extension.
+ * 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.
*
- * The control file is supposed to be very short, half a dozen lines, and
- * reading it is only allowed to superuser, so we don't worry about
- * memory allocation risks here. Also note that we don't worry about
- * what encoding it's in; all values are expected to be ASCII.
+ * 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 ExtensionControlFile *
-read_extension_control_file(const char *extname)
+static void
+parse_extension_control_file(ExtensionControlFile *control,
+ const char *version)
{
- char *filename = get_extension_control_filename(extname);
+ char *filename;
FILE *file;
- ExtensionControlFile *control;
ConfigVariable *item,
*head = NULL,
*tail = NULL;
/*
- * Parse the file content, using GUC's file parsing code
+ * 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
+ */
ParseConfigFp(file, filename, 0, ERROR, &head, &tail);
FreeFile(file);
/*
- * Set up default values. Pointer fields are initially null.
- */
- control = (ExtensionControlFile *) palloc0(sizeof(ExtensionControlFile));
- control->name = pstrdup(extname);
- control->relocatable = false;
- control->encoding = -1;
-
- /*
* Convert the ConfigVariable list into ExtensionControlFile entries.
*/
for (item = head; item != NULL; item = item->next)
{
- if (strcmp(item->name, "script") == 0)
+ if (strcmp(item->name, "directory") == 0)
{
- control->script = pstrdup(item->value);
+ if (version)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"%s\" cannot be set in a per-version extension control file",
+ item->name)));
+
+ control->directory = pstrdup(item->value);
}
- else if (strcmp(item->name, "version") == 0)
+ else if (strcmp(item->name, "default_version") == 0)
{
- control->version = pstrdup(item->value);
+ if (version)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"%s\" cannot be set in a per-version extension control file",
+ item->name)));
+
+ control->default_version = pstrdup(item->value);
}
else if (strcmp(item->name, "comment") == 0)
{
@@ -324,6 +454,12 @@ read_extension_control_file(const char *extname)
}
else if (strcmp(item->name, "encoding") == 0)
{
+ if (version)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"%s\" cannot be set in a per-version extension control file",
+ item->name)));
+
control->encoding = pg_valid_server_encoding(item->value);
if (control->encoding < 0)
ereport(ERROR,
@@ -360,22 +496,35 @@ read_extension_control_file(const char *extname)
(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;
+
/*
- * script defaults to ${extension-name}.sql
+ * Set up default values. Pointer fields are initially null.
*/
- if (control->script == NULL)
- {
- char script[MAXPGPATH];
+ control = (ExtensionControlFile *) palloc0(sizeof(ExtensionControlFile));
+ control->name = pstrdup(extname);
+ control->relocatable = false;
+ control->encoding = -1;
- snprintf(script, MAXPGPATH, "%s.sql", control->name);
- control->script = pstrdup(script);
- }
+ /*
+ * Parse the primary control file.
+ */
+ parse_extension_control_file(control, NULL);
return control;
}
/*
- * Read the SQL script into a string, and convert to database encoding
+ * Read a SQL script file into a string, and convert to database encoding
*/
static char *
read_extension_script_file(const ExtensionControlFile *control,
@@ -513,20 +662,26 @@ execute_sql_string(const char *sql, const char *filename)
}
/*
- * Execute the extension's script file
+ * 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)
{
- char *filename = get_extension_absolute_path(control->script);
+ char *filename;
char *save_client_min_messages,
*save_log_min_messages,
*save_search_path;
StringInfoData pathbuf;
ListCell *lc;
+ filename = get_extension_script_filename(control, from_version, version);
+
/*
* 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
@@ -636,14 +791,198 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
}
/*
+ * 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;
+ /* 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] != EXT_VERSION_SEP)
+ continue;
+
+ /* extract version names from 'extname-something.sql' filename */
+ vername = pstrdup(de->d_name + extnamelen + 1);
+ *strrchr(vername, '.') = '\0';
+ vername2 = strchr(vername, EXT_VERSION_SEP);
+ if (!vername2)
+ continue; /* it's not an update script */
+ *vername2++ = '\0';
+
+ /* 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.
+ */
+static List *
+identify_update_path(ExtensionControlFile *control,
+ const char *oldVersion, const char *newVersion)
+{
+ List *result;
+ List *evi_list;
+ ExtensionVersionInfo *evi_start;
+ ExtensionVersionInfo *evi_target;
+ ExtensionVersionInfo *evi;
+
+ if (strcmp(oldVersion, newVersion) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("version to install or update to must be different from old version")));
+
+ /* 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);
+
+ evi_start->distance = 0;
+
+ while ((evi = get_nearest_unprocessed_vertex(evi_list)) != NULL)
+ {
+ ListCell *lc;
+
+ 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;
+
+ newdist = evi->distance + 1;
+ if (newdist < evi2->distance)
+ {
+ evi2->distance = newdist;
+ evi2->previous = evi;
+ }
+ }
+ }
+
+ if (!evi_target->distance_known)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("extension \"%s\" has no update path from version \"%s\" to version \"%s\"",
+ control->name, oldVersion, newVersion)));
+
+ /* 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;
+}
+
+/*
* CREATE EXTENSION
*/
void
CreateExtension(CreateExtensionStmt *stmt)
{
DefElem *d_schema = NULL;
+ DefElem *d_new_version = NULL;
+ DefElem *d_old_version = NULL;
char *schemaName;
Oid schemaOid;
+ char *versionName;
+ char *oldVersionName;
Oid extowner = GetUserId();
ExtensionControlFile *control;
List *requiredExtensions;
@@ -668,6 +1007,9 @@ CreateExtension(CreateExtensionStmt *stmt)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("nested CREATE EXTENSION is not supported")));
+ /* 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
@@ -679,9 +1021,9 @@ CreateExtension(CreateExtensionStmt *stmt)
errmsg("extension \"%s\" already exists", stmt->extname)));
/*
- * Read the control file. Note we assume that it does not contain
- * any non-ASCII data, so there is no need to worry about encoding
- * at this point.
+ * 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);
@@ -700,11 +1042,59 @@ CreateExtension(CreateExtensionStmt *stmt)
errmsg("conflicting or redundant options")));
d_schema = defel;
}
+ else if (strcmp(defel->defname, "new_version") == 0)
+ {
+ if (d_new_version)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ d_new_version = defel;
+ }
+ else if (strcmp(defel->defname, "old_version") == 0)
+ {
+ if (d_old_version)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ d_old_version = defel;
+ }
else
elog(ERROR, "unrecognized option: %s", defel->defname);
}
/*
+ * Determine the version to install
+ */
+ 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);
+
+ /*
+ * Modify control parameters for specific new version
+ */
+ parse_extension_control_file(control, versionName);
+
+ /*
+ * Determine the (unpackaged) version to update from, if any
+ */
+ if (d_old_version && d_old_version->arg)
+ {
+ oldVersionName = strVal(d_old_version->arg);
+ check_valid_version_name(oldVersionName);
+ }
+ else
+ oldVersionName = NULL;
+
+ /*
* Determine the target schema to install the extension into
*/
if (d_schema && d_schema->arg)
@@ -799,7 +1189,7 @@ CreateExtension(CreateExtensionStmt *stmt)
*/
extensionOid = InsertExtensionTuple(control->name, extowner,
schemaOid, control->relocatable,
- control->version,
+ versionName,
PointerGetDatum(NULL),
PointerGetDatum(NULL),
requiredExtensions);
@@ -811,10 +1201,36 @@ CreateExtension(CreateExtensionStmt *stmt)
CreateComments(extensionOid, ExtensionRelationId, 0, control->comment);
/*
- * Finally, execute the extension script to create the member objects
+ * Finally, execute the extension's script file(s)
*/
- execute_extension_script(extensionOid, control, requiredSchemas,
- schemaName, schemaOid);
+ if (oldVersionName == NULL)
+ {
+ /* Simple install */
+ execute_extension_script(extensionOid, control,
+ oldVersionName, versionName,
+ requiredSchemas,
+ schemaName, schemaOid);
+ }
+ else
+ {
+ /* Update from unpackaged objects --- find update-file path */
+ List *updateVersions;
+
+ updateVersions = identify_update_path(control,
+ oldVersionName,
+ versionName);
+
+ foreach(lc, updateVersions)
+ {
+ char *vname = (char *) lfirst(lc);
+
+ execute_extension_script(extensionOid, control,
+ oldVersionName, vname,
+ requiredSchemas,
+ schemaName, schemaOid);
+ oldVersionName = vname;
+ }
+ }
}
/*
@@ -858,12 +1274,7 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
values[Anum_pg_extension_extowner - 1] = ObjectIdGetDatum(extOwner);
values[Anum_pg_extension_extnamespace - 1] = ObjectIdGetDatum(schemaOid);
values[Anum_pg_extension_extrelocatable - 1] = BoolGetDatum(relocatable);
-
- if (extVersion == NULL)
- nulls[Anum_pg_extension_extversion - 1] = true;
- else
- values[Anum_pg_extension_extversion - 1] =
- CStringGetTextDatum(extVersion);
+ values[Anum_pg_extension_extversion - 1] = CStringGetTextDatum(extVersion);
if (extConfig == PointerGetDatum(NULL))
nulls[Anum_pg_extension_extconfig - 1] = true;
@@ -1102,11 +1513,11 @@ pg_available_extensions(PG_FUNCTION_ARGS)
/* name */
values[0] = DirectFunctionCall1(namein,
CStringGetDatum(control->name));
- /* version */
- if (control->version == NULL)
+ /* default_version */
+ if (control->default_version == NULL)
nulls[1] = true;
else
- values[1] = CStringGetTextDatum(control->version);
+ values[1] = CStringGetTextDatum(control->default_version);
/* relocatable */
values[2] = BoolGetDatum(control->relocatable);
/* comment */
@@ -1436,6 +1847,230 @@ AlterExtensionNamespace(List *names, const char *newschema)
}
/*
+ * Execute ALTER EXTENSION UPDATE
+ */
+void
+ExecAlterExtensionStmt(AlterExtensionStmt *stmt)
+{
+ DefElem *d_new_version = NULL;
+ char *schemaName;
+ Oid schemaOid;
+ char *versionName;
+ char *oldVersionName;
+ ExtensionControlFile *control;
+ List *requiredExtensions;
+ List *requiredSchemas;
+ Oid extensionOid;
+ 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];
+ List *updateVersions;
+ ObjectAddress myself;
+ Datum datum;
+ bool isnull;
+ ListCell *lc;
+
+ /*
+ * For now, insist on superuser privilege. Later we might want to
+ * relax this to ownership of the extension.
+ */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use ALTER EXTENSION"))));
+
+ /*
+ * We use global variables to track the extension being created, so we
+ * can create/alter 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 = heap_open(ExtensionRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_extension_extname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->extname));
+
+ extScan = systable_beginscan(extRel, ExtensionNameIndexId, true,
+ SnapshotNow, 1, key);
+
+ extTup = systable_getnext(extScan);
+
+ if (!HeapTupleIsValid(extTup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("extension \"%s\" does not exist",
+ stmt->extname)));
+
+ /* Copy tuple so we can modify it below */
+ extTup = heap_copytuple(extTup);
+ extForm = (Form_pg_extension) GETSTRUCT(extTup);
+ extensionOid = HeapTupleGetOid(extTup);
+
+ systable_endscan(extScan);
+
+ /*
+ * 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")));
+ d_new_version = defel;
+ }
+ else
+ elog(ERROR, "unrecognized option: %s", defel->defname);
+ }
+
+ /*
+ * Determine the version to install
+ */
+ 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);
+
+ /*
+ * Modify control parameters for specific new version
+ */
+ parse_extension_control_file(control, versionName);
+
+ /*
+ * Determine the existing version we are upgrading 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));
+
+ /*
+ * Determine the target schema (already set by original install)
+ */
+ schemaOid = extForm->extnamespace;
+ schemaName = get_namespace_name(schemaOid);
+
+ /*
+ * Look up the prerequisite extensions, and build lists of their OIDs
+ * and the OIDs of their target schemas. We assume that the requires
+ * list is version-specific, so the dependencies can change across
+ * versions. But note that only the final version's requires list
+ * is being consulted here!
+ */
+ requiredExtensions = NIL;
+ requiredSchemas = NIL;
+ foreach(lc, control->requires)
+ {
+ char *curreq = (char *) lfirst(lc);
+ Oid reqext;
+ Oid reqschema;
+
+ /*
+ * We intentionally don't use get_extension_oid's default error
+ * message here, because it would be confusing in this context.
+ */
+ reqext = get_extension_oid(curreq, true);
+ if (!OidIsValid(reqext))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("required extension \"%s\" is not installed",
+ curreq)));
+ reqschema = get_extension_schema(reqext);
+ requiredExtensions = lappend_oid(requiredExtensions, reqext);
+ requiredSchemas = lappend_oid(requiredSchemas, reqschema);
+ }
+
+ /*
+ * Modify 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_extversion - 1] = CStringGetTextDatum(versionName);
+ repl[Anum_pg_extension_extversion - 1] = true;
+
+ extTup = heap_modify_tuple(extTup, RelationGetDescr(extRel),
+ values, nulls, repl);
+
+ simple_heap_update(extRel, &extTup->t_self, extTup);
+ CatalogUpdateIndexes(extRel, extTup);
+
+ heap_close(extRel, RowExclusiveLock);
+
+ /*
+ * 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);
+ }
+
+ /*
+ * Finally, execute the extension's script file(s)
+ */
+ updateVersions = identify_update_path(control,
+ oldVersionName,
+ versionName);
+
+ foreach(lc, updateVersions)
+ {
+ char *vname = (char *) lfirst(lc);
+
+ execute_extension_script(extensionOid, control,
+ oldVersionName, vname,
+ requiredSchemas,
+ schemaName, schemaOid);
+ oldVersionName = vname;
+ }
+}
+
+/*
* Execute ALTER EXTENSION ADD/DROP
*/
void