]> granicus.if.org Git - postgresql/commitdiff
Allow CREATE EXTENSION to follow extension update paths.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 11 Sep 2016 18:15:07 +0000 (14:15 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 11 Sep 2016 18:15:07 +0000 (14:15 -0400)
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
src/backend/commands/extension.c

index df88380a23051152e050d749bb8130c6462221d3..e19c657d8febce16ecd9533769ff84fc7dcd1fbe 100644 (file)
@@ -885,6 +885,47 @@ SELECT * FROM pg_extension_update_paths('<replaceable>extension_name</>');
     </para>
    </sect2>
 
+   <sect2>
+    <title>Installing Extensions using Update Scripts</title>
+
+    <para>
+     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 <literal>foo</> extension in
+     versions <literal>1.0</>, <literal>1.1</>, and <literal>1.2</>, there
+     should be update scripts <filename>foo--1.0--1.1.sql</>
+     and <filename>foo--1.1--1.2.sql</>.
+     Before <productname>PostgreSQL</> 10, it was necessary to also create
+     new script files <filename>foo--1.1.sql</> and <filename>foo--1.2.sql</>
+     that directly build the newer extension versions, or else the newer
+     versions could not be installed directly, only by
+     installing <literal>1.0</> and then updating.  That was tedious and
+     duplicative, but now it's unnecessary, because <command>CREATE
+     EXTENSION</> can follow update chains automatically.
+     For example, if only the script
+     files <filename>foo--1.0.sql</>, <filename>foo--1.0--1.1.sql</>,
+     and <filename>foo--1.1--1.2.sql</> are available then a request to
+     install version <literal>1.2</> is honored by running those three
+     scripts in sequence.  The processing is the same as if you'd first
+     installed <literal>1.0</> and then updated to <literal>1.2</>.
+     (As with <command>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.
+    </para>
+
+    <para>
+     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 <filename>foo--1.0.control</> specifies <literal>requires
+     = 'bar'</> but <literal>foo</>'s other control files do not, the
+     extension's dependency on <literal>bar</> will be dropped when updating
+     from <literal>1.0</> to another version.
+    </para>
+   </sect2>
+
    <sect2 id="extend-extensions-example">
     <title>Extension Example</title>
 
index df49a78e2fa467e4429d32b2599983944cd9d56c..f6c2c8af91f0d3675eb2090df225d66bea89f4a2 100644 (file)
@@ -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);
+
+               /*
+                * 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);
 }
 
 /*
@@ -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);