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);
+ List *updateVersions,
+ char *origSchemaName,
+ bool cascade,
+ bool is_create);
static char *read_whole_file(const char *filename, int *length);
evi_target = get_ext_ver_info(newVersion, &evi_list);
/* Find shortest path */
- result = find_update_path(evi_list, evi_start, evi_target, false);
+ result = find_update_path(evi_list, evi_start, evi_target, false, false);
if (result == NIL)
ereport(ERROR,
* 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.
+ * 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.
find_update_path(List *evi_list,
ExtensionVersionInfo *evi_start,
ExtensionVersionInfo *evi_target,
+ bool reject_indirect,
bool reinitialize)
{
List *result;
/* 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)
{
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)
{
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
*
* installed, allowing us to error out if we recurse to one of those.
*/
static ObjectAddress
-CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *parents)
+CreateExtensionInternal(char *extensionName,
+ char *schemaName,
+ char *versionName,
+ char *oldVersionName,
+ bool cascade,
+ List *parents,
+ bool is_create)
{
- DefElem *d_schema = NULL;
- DefElem *d_new_version = NULL;
- DefElem *d_old_version = NULL;
- DefElem *d_cascade = NULL;
- char *schemaName = NULL;
+ char *origSchemaName = schemaName;
Oid schemaOid = InvalidOid;
- char *versionName;
- char *oldVersionName;
- bool cascade = false;
Oid extowner = GetUserId();
ExtensionControlFile *pcontrol;
ExtensionControlFile *control;
List *requiredExtensions;
List *requiredSchemas;
Oid extensionOid;
- ListCell *lc;
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(stmt->extname);
-
- /*
- * Read the statement option list
- */
- foreach(lc, stmt->options)
- {
- DefElem *defel = (DefElem *) lfirst(lc);
-
- if (strcmp(defel->defname, "schema") == 0)
- {
- if (d_schema)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("conflicting or redundant options"),
- parser_errposition(pstate, defel->location)));
- 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"),
- parser_errposition(pstate, defel->location)));
- 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"),
- parser_errposition(pstate, defel->location)));
- d_old_version = defel;
- }
- 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);
- }
+ pcontrol = read_extension_control_file(extensionName);
/*
* Determine the version to install
*/
- if (d_new_version && d_new_version->arg)
- versionName = strVal(d_new_version->arg);
- else if (pcontrol->default_version)
- versionName = pcontrol->default_version;
- else
+ if (versionName == NULL)
{
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("version to install must be specified")));
- versionName = NULL; /* keep compiler quiet */
+ 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);
/*
- * Determine the (unpackaged) version to update from, if any, and then
- * figure out what sequence of update scripts we need to apply.
+ * 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.
*/
- if (d_old_version && d_old_version->arg)
+ if (oldVersionName)
{
- oldVersionName = strVal(d_old_version->arg);
+ /*
+ * "FROM old_version" was specified, indicating that we're trying to
+ * update from some unpackaged version of the extension. Locate a
+ * series of update scripts that will do it.
+ */
check_valid_version_name(oldVersionName);
if (strcmp(oldVersionName, versionName) == 0)
}
else
{
+ /*
+ * No FROM, so we're installing from scratch. If there is an install
+ * script for the desired version, we only need to run that one.
+ */
+ char *filename;
+ struct stat fst;
+
oldVersionName = NULL;
- updateVersions = NIL;
+
+ 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;
+ }
}
/*
/*
* Determine the target schema to install the extension into
*/
- if (d_schema && d_schema->arg)
+ if (schemaName)
{
- /*
- * User given schema, CREATE EXTENSION ... WITH SCHEMA ...
- */
- schemaName = strVal(d_schema->arg);
-
/* If the user is giving us the schema name, it must exist already. */
schemaOid = get_namespace_oid(schemaName, false);
}
else if (!OidIsValid(schemaOid))
{
/*
- * Neither user nor author of the extension specified schema, use the
+ * 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.
*/
Oid reqext;
Oid reqschema;
- reqext = get_extension_oid(curreq, true);
- if (!OidIsValid(reqext))
- {
- if (cascade)
- {
- /* Must install it. */
- CreateExtensionStmt *ces;
- ListCell *lc2;
- ObjectAddress addr;
- List *cascade_parents;
-
- /* Check extension name validity before trying to cascade. */
- check_valid_extension_name(curreq);
-
- /* Check for cyclic dependency between extensions. */
- foreach(lc2, parents)
- {
- char *pname = (char *) lfirst(lc2);
-
- if (strcmp(pname, curreq) == 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_RECURSION),
- errmsg("cyclic dependency detected between extensions \"%s\" and \"%s\"",
- curreq, stmt->extname)));
- }
-
- ereport(NOTICE,
- (errmsg("installing required extension \"%s\"",
- curreq)));
-
- /* Build a CREATE EXTENSION statement to pass down. */
- ces = makeNode(CreateExtensionStmt);
- ces->extname = curreq;
- ces->if_not_exists = false;
-
- /* Propagate the CASCADE option. */
- ces->options = list_make1(d_cascade);
-
- /* Propagate the SCHEMA option if given. */
- if (d_schema && d_schema->arg)
- ces->options = lappend(ces->options, d_schema);
-
- /* Add current extension to list of parents to pass down. */
- cascade_parents =
- lappend(list_copy(parents), stmt->extname);
-
- /* Create the required extension. */
- addr = CreateExtensionInternal(pstate, ces, cascade_parents);
-
- /* Get its newly-assigned OID. */
- reqext = addr.objectId;
- }
- else
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("required extension \"%s\" is not installed",
- curreq),
- errhint("Use CREATE EXTENSION ... CASCADE to install required extensions too.")));
- }
-
+ 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);
* though a series of ALTER EXTENSION UPDATE commands were given
*/
ApplyExtensionUpdates(extensionOid, pcontrol,
- versionName, updateVersions);
+ 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,
+ 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_old_version = NULL;
+ DefElem *d_cascade = NULL;
+ char *schemaName = NULL;
+ char *versionName = NULL;
+ char *oldVersionName = NULL;
+ bool cascade = false;
+ ListCell *lc;
+
/* Check extension name validity before any filesystem access */
check_valid_extension_name(stmt->extname);
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("nested CREATE EXTENSION is not supported")));
- /* Finally create the extension. */
- return CreateExtensionInternal(pstate, stmt, NIL);
+ /* 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, "old_version") == 0)
+ {
+ if (d_old_version)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options"),
+ parser_errposition(pstate, defel->location)));
+ d_old_version = defel;
+ oldVersionName = defGetString(d_old_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,
+ oldVersionName,
+ cascade,
+ NIL,
+ true);
}
/*
Tuplestorestate *tupstore,
TupleDesc tupdesc)
{
- int extnamelen = strlen(pcontrol->name);
- char *location;
- DIR *dir;
- struct dirent *de;
+ List *evi_list;
+ ListCell *lc;
- location = get_extension_script_directory(pcontrol);
- dir = AllocateDir(location);
- /* Note this will fail if script directory doesn't exist */
- while ((de = ReadDir(dir, location)) != NULL)
+ /* 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;
- char *vername;
Datum values[7];
bool nulls[7];
+ ListCell *lc2;
- /* 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, pcontrol->name, extnamelen) != 0 ||
- de->d_name[extnamelen] != '-' ||
- de->d_name[extnamelen + 1] != '-')
- continue;
-
- /* extract version name from 'extname--something.sql' filename */
- vername = pstrdup(de->d_name + extnamelen + 2);
- *strrchr(vername, '.') = '\0';
-
- /* ignore it if it's an update script */
- if (strstr(vername, "--"))
+ if (!evi->installable)
continue;
/*
* Fetch parameters for specific version (pcontrol is not changed)
*/
- control = read_extension_aux_control_file(pcontrol, vername);
+ control = read_extension_aux_control_file(pcontrol, evi->name);
memset(values, 0, sizeof(values));
memset(nulls, 0, sizeof(nulls));
values[0] = DirectFunctionCall1(namein,
CStringGetDatum(control->name));
/* version */
- values[1] = CStringGetTextDatum(vername);
+ values[1] = CStringGetTextDatum(evi->name);
/* superuser */
values[2] = BoolGetDatum(control->superuser);
/* relocatable */
if (control->requires == NIL)
nulls[5] = true;
else
- {
- Datum *datums;
- int ndatums;
- ArrayType *a;
- ListCell *lc;
-
- ndatums = list_length(control->requires);
- datums = (Datum *) palloc(ndatums * sizeof(Datum));
- ndatums = 0;
- foreach(lc, control->requires)
- {
- char *curreq = (char *) lfirst(lc);
-
- datums[ndatums++] =
- DirectFunctionCall1(namein, CStringGetDatum(curreq));
- }
- a = construct_array(datums, ndatums,
- NAMEOID,
- NAMEDATALEN, false, 'c');
- values[5] = PointerGetDatum(a);
- }
+ values[5] = convert_requires_to_datum(control->requires);
/* comment */
if (control->comment == NULL)
nulls[6] = true;
values[6] = 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);
+ /* relocatable */
+ values[3] = BoolGetDatum(control->relocatable);
+ /* schema stays the same */
+ /* requires */
+ if (control->requires == NIL)
+ nulls[5] = true;
+ else
+ {
+ values[5] = convert_requires_to_datum(control->requires);
+ nulls[5] = false;
+ }
+ /* comment stays the same */
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+ }
}
+}
- FreeDir(dir);
+/*
+ * 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, 'c');
+ return PointerGetDatum(a);
}
/*
continue;
/* Find shortest path from evi1 to evi2 */
- path = find_update_path(evi_list, evi1, evi2, true);
+ path = find_update_path(evi_list, evi1, evi2, false, true);
/* Emit result row */
memset(values, 0, sizeof(values));
* time
*/
ApplyExtensionUpdates(extensionOid, control,
- oldVersionName, updateVersions);
+ oldVersionName, updateVersions,
+ NULL, false, false);
ObjectAddressSet(address, ExtensionRelationId, extensionOid);
ApplyExtensionUpdates(Oid extensionOid,
ExtensionControlFile *pcontrol,
const char *initialVersion,
- List *updateVersions)
+ List *updateVersions,
+ char *origSchemaName,
+ bool cascade,
+ bool is_create)
{
const char *oldVersionName = initialVersion;
ListCell *lcv;
heap_close(extRel, RowExclusiveLock);
/*
- * Look up the prerequisite extensions for this version, and build
- * lists of their OIDs and the OIDs of their target schemas.
+ * 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;
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)));
+ 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);