From 40b449ae84dcf71177d7749a7b0c582b64dc15f0 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 11 Sep 2016 14:15:07 -0400 Subject: [PATCH] Allow CREATE EXTENSION to follow extension update paths. Previously, to update an extension you had to produce both a version-update script and a new base installation script. It's become more and more obvious that that's tedious, duplicative, and error-prone. This patch attempts to improve matters by allowing the new base installation script to be omitted. CREATE EXTENSION will install a requested version if it can find a base script and a chain of update scripts that will get there. As in the existing update logic, shorter chains are preferred if there's more than one possibility, with an arbitrary tie-break rule for chains of equal length. Also adjust the pg_available_extension_versions view to show such versions as installable. While at it, refactor the code so that CASCADE processing works for extensions requested during ApplyExtensionUpdates(). Without this, addition of a new requirement in an updated extension would require creating a new base script, even if there was no other reason to do that. (It would be easy at this point to add a CASCADE option to ALTER EXTENSION UPDATE, to allow the same thing to happen during a manually-commanded version update, but I have not done that here.) Tom Lane, reviewed by Andres Freund Discussion: <20160905005919.jz2m2yh3und2dsuy@alap3.anarazel.de> --- doc/src/sgml/extend.sgml | 41 +++ src/backend/commands/extension.c | 610 ++++++++++++++++++++----------- 2 files changed, 440 insertions(+), 211 deletions(-) diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index df88380a23..e19c657d8f 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -885,6 +885,47 @@ SELECT * FROM pg_extension_update_paths('extension_name'); + + Installing Extensions using Update Scripts + + + An extension that has been around for awhile will probably exist in + several versions, for which the author will need to write update scripts. + For example, if you have released a foo extension in + versions 1.0, 1.1, and 1.2, there + should be update scripts foo--1.0--1.1.sql + and foo--1.1--1.2.sql. + Before PostgreSQL 10, it was necessary to also create + new script files foo--1.1.sql and foo--1.2.sql + that directly build the newer extension versions, or else the newer + versions could not be installed directly, only by + installing 1.0 and then updating. That was tedious and + duplicative, but now it's unnecessary, because CREATE + EXTENSION can follow update chains automatically. + For example, if only the script + files foo--1.0.sql, foo--1.0--1.1.sql, + and foo--1.1--1.2.sql are available then a request to + install version 1.2 is honored by running those three + scripts in sequence. The processing is the same as if you'd first + installed 1.0 and then updated to 1.2. + (As with ALTER EXTENSION UPDATE, if multiple pathways are + available then the shortest is preferred.) Arranging an extension's + script files in this style can reduce the amount of maintenance effort + needed to produce small updates. + + + + If you use secondary (version-specific) control files with an extension + maintained in this style, keep in mind that each version needs a control + file even if it has no stand-alone installation script, as that control + file will determine how the implicit update to that version is performed. + For example, if foo--1.0.control specifies requires + = 'bar' but foo's other control files do not, the + extension's dependency on bar will be dropped when updating + from 1.0 to another version. + + + Extension Example diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index df49a78e2f..f6c2c8af91 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -100,14 +100,25 @@ typedef struct ExtensionVersionInfo 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); @@ -1071,7 +1082,7 @@ identify_update_path(ExtensionControlFile *control, 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, @@ -1086,9 +1097,13 @@ identify_update_path(ExtensionControlFile *control, * 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. @@ -1097,6 +1112,7 @@ static List * find_update_path(List *evi_list, ExtensionVersionInfo *evi_start, ExtensionVersionInfo *evi_target, + bool reject_indirect, bool reinitialize) { List *result; @@ -1105,6 +1121,8 @@ find_update_path(List *evi_list, /* 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) { @@ -1131,6 +1149,9 @@ find_update_path(List *evi_list, 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) { @@ -1166,6 +1187,67 @@ find_update_path(List *evi_list, 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 * @@ -1175,17 +1257,16 @@ find_update_path(List *evi_list, * 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; @@ -1193,87 +1274,43 @@ CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *par 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) @@ -1308,8 +1345,48 @@ CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *par } 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; + } } /* @@ -1320,13 +1397,8 @@ CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *par /* * 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); } @@ -1374,7 +1446,7 @@ CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *par 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. */ @@ -1415,66 +1487,12 @@ CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *par 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); @@ -1510,17 +1528,100 @@ CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *par * 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); @@ -1556,8 +1657,63 @@ CreateExtension(ParseState *pstate, CreateExtensionStmt *stmt) (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); } /* @@ -1914,43 +2070,28 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol, 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)); @@ -1959,7 +2100,7 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol, values[0] = DirectFunctionCall1(namein, CStringGetDatum(control->name)); /* version */ - values[1] = CStringGetTextDatum(vername); + values[1] = CStringGetTextDatum(evi->name); /* superuser */ values[2] = BoolGetDatum(control->superuser); /* relocatable */ @@ -1974,27 +2115,7 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol, 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; @@ -2002,9 +2123,75 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol, values[6] = CStringGetTextDatum(control->comment); tuplestore_putvalues(tupstore, tupdesc, values, nulls); - } - FreeDir(dir); + /* + * 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); + } + } + } +} + +/* + * 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); } /* @@ -2076,7 +2263,7 @@ pg_extension_update_paths(PG_FUNCTION_ARGS) 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)); @@ -2808,7 +2995,8 @@ ExecAlterExtensionStmt(ParseState *pstate, AlterExtensionStmt *stmt) * time */ ApplyExtensionUpdates(extensionOid, control, - oldVersionName, updateVersions); + oldVersionName, updateVersions, + NULL, false, false); ObjectAddressSet(address, ExtensionRelationId, extensionOid); @@ -2827,7 +3015,10 @@ static void 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; @@ -2906,8 +3097,9 @@ ApplyExtensionUpdates(Oid extensionOid, 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; @@ -2917,16 +3109,12 @@ ApplyExtensionUpdates(Oid extensionOid, 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);