From 40b449ae84dcf71177d7749a7b0c582b64dc15f0 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
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 | 608 ++++++++++++++++++++-----------
 2 files changed, 439 insertions(+), 210 deletions(-)

diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index df88380a230..e19c657d8fe 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -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>
 
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index df49a78e2fa..f6c2c8af91f 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);
+
+		/*
+		 * 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);
-- 
GitLab