Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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);