diff --git a/contrib/pg_upgrade_support/pg_upgrade_support.c b/contrib/pg_upgrade_support/pg_upgrade_support.c
index 8b0e474dce51a5d51e89e3b15e3fb36ab41504d3..02d1512719ec2a7f11e938145b144df39be03f0f 100644
--- a/contrib/pg_upgrade_support/pg_upgrade_support.c
+++ b/contrib/pg_upgrade_support/pg_upgrade_support.c
@@ -150,16 +150,11 @@ create_empty_extension(PG_FUNCTION_ARGS)
 	text	   *extName = PG_GETARG_TEXT_PP(0);
 	text	   *schemaName = PG_GETARG_TEXT_PP(1);
 	bool		relocatable = PG_GETARG_BOOL(2);
-	char	   *extVersion;
+	text	   *extVersion = PG_GETARG_TEXT_PP(3);
 	Datum		extConfig;
 	Datum		extCondition;
 	List	   *requiredExtensions;
 
-	if (PG_ARGISNULL(3))
-		extVersion = NULL;
-	else
-		extVersion = text_to_cstring(PG_GETARG_TEXT_PP(3));
-
 	if (PG_ARGISNULL(4))
 		extConfig = PointerGetDatum(NULL);
 	else
@@ -195,7 +190,7 @@ create_empty_extension(PG_FUNCTION_ARGS)
 						 GetUserId(),
 						 get_namespace_oid(text_to_cstring(schemaName), false),
 						 relocatable,
-						 extVersion,
+						 text_to_cstring(extVersion),
 						 extConfig,
 						 extCondition,
 						 requiredExtensions);
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 24aa22cbced804c05df94a65aada91c6e69f4bf2..a373829d39d740adc6bca594e1ba301e80f3be16 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2927,7 +2927,7 @@
       <entry><structfield>extversion</structfield></entry>
       <entry><type>text</type></entry>
       <entry></entry>
-      <entry>Version string for the extension, or <literal>NULL</> if none</entry>
+      <entry>Version name for the extension</entry>
      </row>
 
      <row>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 31ea0487f1e52202d704da7a5ef20b9a39c39a7c..93bcba9a10cfb299074747eca8052e9451f1f05e 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -304,7 +304,7 @@
    </para>
 
    <para>
-    The advantage of using an extension, rather than just running the
+    The main advantage of using an extension, rather than just running the
     <acronym>SQL</> script to load a bunch of <quote>loose</> objects
     into your database, is that <productname>PostgreSQL</> will then
     understand that the objects of the extension go together.  You can
@@ -331,6 +331,17 @@
     data; see below.)
    </para>
 
+   <para>
+    The extension mechanism also has provisions for packaging modification
+    scripts that adjust the definitions of the SQL objects contained in an
+    extension.  For example, if version 1.1 of an extension adds one function
+    and changes the body of another function compared to 1.0, the extension
+    author can provide an <firstterm>update script</> that makes just those
+    two changes.  The <command>ALTER EXTENSION UPDATE</> command can then
+    be used to apply these changes and track which version of the extension
+    is actually installed in a given database.
+   </para>
+
    <para>
     The kinds of SQL objects that can be members of an extension are shown in
     the description of <xref linkend="sql-alterextension">.  Notably, objects
@@ -355,10 +366,13 @@
      file for each extension, which must be named the same as the extension
      with a suffix of <literal>.control</>, and must be placed in the
      installation's <literal>SHAREDIR/contrib</literal> directory.  There
-     must also be a <acronym>SQL</> script file, which typically is
-     named after the extension with a suffix of <literal>.sql</>, and is also
-     placed in the <literal>SHAREDIR/contrib</literal> directory; but these
-     defaults can be overridden by the control file.
+     must also be at least one <acronym>SQL</> script file, which follows the
+     naming pattern
+     <literal><replaceable>extension</>-<replaceable>version</>.sql</literal>
+     (for example, <literal>foo-1.0.sql</> for version <literal>1.0</> of
+     extension <literal>foo</>).  By default, the script file(s) are also
+     placed in the <literal>SHAREDIR/contrib</literal> directory; but the
+     control file can specify a different directory for the script file(s).
     </para>
 
     <para>
@@ -376,23 +390,25 @@
 
     <variablelist>
      <varlistentry>
-      <term><varname>script</varname> (<type>string</type>)</term>
+      <term><varname>directory</varname> (<type>string</type>)</term>
       <listitem>
        <para>
-        The filename of the extension's <acronym>SQL</> script.
-        Defaults to the same name as the control file, but with the
-        <literal>.sql</literal> extension.  Unless an absolute path is
-        given, the name is relative to the <literal>SHAREDIR/contrib</literal>
-        directory.
+        The directory containing the extension's <acronym>SQL</> script
+        file(s).  Unless an absolute path is given, the name is relative to
+        the <literal>SHAREDIR/contrib</literal> directory.
        </para>
       </listitem>
      </varlistentry>
 
      <varlistentry>
-      <term><varname>version</varname> (<type>string</type>)</term>
+      <term><varname>default_version</varname> (<type>string</type>)</term>
       <listitem>
        <para>
-        The version of the extension.  Any string can be given.
+        The default version of the extension (the one that will be installed
+        if no version is specified in <command>CREATE EXTENSION</>).  Although
+        this can be omitted, that will result in <command>CREATE EXTENSION</>
+        failing if no <literal>VERSION</> option appears, so you generally
+        don't want to do that.
        </para>
       </listitem>
      </varlistentry>
@@ -403,7 +419,7 @@
        <para>
         A comment (any string) about the extension.  Alternatively,
         the comment can be set by means of the <xref linkend="sql-comment">
-        command.
+        command in the script file.
        </para>
       </listitem>
      </varlistentry>
@@ -423,10 +439,9 @@
       <term><varname>encoding</varname> (<type>string</type>)</term>
       <listitem>
        <para>
-        The character set encoding used by the script file.  This should
-        be specified if the script file contains any non-ASCII characters.
-        Otherwise the script will be assumed to be in the encoding of the
-        database it is loaded into.
+        The character set encoding used by the script file(s).  This should
+        be specified if the script files contain any non-ASCII characters.
+        Otherwise the files will be assumed to be in the database encoding.
        </para>
       </listitem>
      </varlistentry>
@@ -457,22 +472,37 @@
     </variablelist>
 
     <para>
-     An extension's <acronym>SQL</> script file can contain any SQL commands,
+     In addition to the primary control file
+     <literal><replaceable>extension</>.control</literal>,
+     an extension can have secondary control files named in the style
+     <literal><replaceable>extension</>-<replaceable>version</>.control</literal>.
+     If supplied, these must be located in the script file directory.
+     Secondary control files follow the same format as the primary control
+     file.  Any parameters set in a secondary control file override the
+     primary control file when installing or updating to that version of
+     the extension.  However, the parameters <varname>directory</>,
+     <varname>default_version</>, and <varname>encoding</> cannot be set in
+     a secondary control file; in particular, the same encoding must be used
+     in all script files associated with the extension.
+    </para>
+
+    <para>
+     An extension's <acronym>SQL</> script files can contain any SQL commands,
      except for transaction control commands (<command>BEGIN</>,
      <command>COMMIT</>, etc) and commands that cannot be executed inside a
      transaction block (such as <command>VACUUM</>).  This is because the
-     script file is implicitly executed within a transaction block.
+     script files are implicitly executed within a transaction block.
     </para>
 
     <para>
-     While the script file can contain any characters allowed by the specified
-     encoding, the control file should contain only plain ASCII, because there
-     is no way for <productname>PostgreSQL</> to know what encoding the
+     While the script files can contain any characters allowed by the specified
+     encoding, control files should contain only plain ASCII, because there
+     is no way for <productname>PostgreSQL</> to know what encoding a
      control file is in.  In practice this is only an issue if you want to
      use non-ASCII characters in the extension's comment.  Recommended
-     practice in that case is to not use the <varname>comment</> parameter
-     in the control file, but instead use <command>COMMENT ON EXTENSION</>
-     within the script file to set the comment.
+     practice in that case is to not use the control file <varname>comment</>
+     parameter, but instead use <command>COMMENT ON EXTENSION</>
+     within a script file to set the comment.
     </para>
 
    </sect2>
@@ -629,6 +659,91 @@ SELECT pg_catalog.pg_extension_config_dump('my_config', 'WHERE NOT standard_entr
     </para>
    </sect2>
 
+   <sect2>
+    <title>Extension Updates</title>
+
+    <para>
+     One advantage of the extension mechanism is that it provides convenient
+     ways to manage updates to the SQL commands that define an extension's
+     objects.  This is done by associating a version name or number with
+     each released version of the extension's installation script.
+     In addition, if you want users to be able to update their databases
+     dynamically from one version to the next, you should provide
+     <firstterm>update scripts</> that make the necessary changes to go from
+     one version to the next.  Update scripts have names following the pattern
+     <literal><replaceable>extension</>-<replaceable>oldversion</>-<replaceable>newversion</>.sql</literal>
+     (for example, <literal>foo-1.0-1.1.sql</> contains the commands to modify
+     version <literal>1.0</> of extension <literal>foo</> into version
+     <literal>1.1</>).
+    </para>
+
+    <para>
+     Given that a suitable update script is available, the command
+     <command>ALTER EXTENSION ... UPDATE</> will update an installed extension
+     to the specified new version.  The update script is run in the same
+     environment that <command>CREATE EXTENSION</> provides for installation
+     scripts: in particular, <varname>search_path</> is set up in the same
+     way, and any new objects created by the script are automatically added
+     to the extension.
+    </para>
+
+    <para>
+     The update mechanism can be used to solve an important special case:
+     converting a <quote>loose</> collection of objects into an extension.
+     Before the extension mechanism was added to
+     <productname>PostgreSQL</productname> (in 9.1), many people wrote
+     extension modules that simply created assorted unpackaged objects.
+     Given an existing database containing such objects, how can we convert
+     the objects into a properly packaged extension?  Dropping them and then
+     doing a plain <command>CREATE EXTENSION</> is one way, but it's not
+     desirable if the objects have dependencies (for example, if there are
+     table columns of a data type created by the extension).  The way to fix
+     this situation is to create an empty extension, then use <command>ALTER
+     EXTENSION ADD</> to attach each pre-existing object to the extension,
+     then finally create any new objects that are in the current extension
+     version but were not in the unpackaged release.  <command>CREATE
+     EXTENSION</> supports this case with its <literal>FROM</> <replaceable
+     class="parameter">old_version</> option, which causes it to not run the
+     normal installation script for the target version, but instead the update
+     script named
+     <literal><replaceable>extension</>-<replaceable>old_version</>-<replaceable>target_version</>.sql</literal>.
+     The choice of the dummy version name to use as <replaceable
+     class="parameter">old_version</> is up to the extension author, though
+     <literal>unpackaged</> is a common convention.  If you have multiple
+     prior versions you need to be able to update into extension style, use
+     multiple dummy version names to identify them.
+    </para>
+
+    <para>
+     <command>ALTER EXTENSION</> is able to execute sequences of update
+     script files to achieve a requested update.  For example, if only
+     <literal>foo-1.0-1.1.sql</> and <literal>foo-1.1-2.0.sql</> are
+     available, <command>ALTER EXTENSION</> will apply them in sequence if an
+     update to version <literal>2.0</> is requested when <literal>1.0</> is
+     currently installed.
+    </para>
+
+    <para>
+     <productname>PostgreSQL</> doesn't assume anything about the properties
+     of version names: for example, it does not know whether <literal>1.1</>
+     follows <literal>1.0</>.  It just matches up the available version names
+     and follows the path that requires applying the fewest update scripts.
+    </para>
+
+    <para>
+     Sometimes it is useful to provide <quote>downgrade</> scripts, for
+     example <literal>foo-1.1-1.0.sql</> to allow reverting the changes
+     associated with version <literal>1.1</>.  If you do that, be careful
+     of the possibility that a downgrade script might unexpectedly
+     get applied because it yields a shorter path.  The risky case is where
+     there is a <quote>fast path</> update script that jumps ahead several
+     versions as well as a downgrade script to the fast path's start point.
+     It might take fewer steps to apply the downgrade and then the fast
+     path than to move ahead one version at a time.  If the downgrade script
+     drops any irreplaceable objects, this will yield undesirable results.
+    </para>
+   </sect2>
+
    <sect2>
     <title>Extension Example</title>
 
@@ -640,7 +755,7 @@ SELECT pg_catalog.pg_extension_config_dump('my_config', 'WHERE NOT standard_entr
     </para>
 
     <para>
-     The script file <filename>pair.sql</> looks like this:
+     The script file <filename>pair-1.0.sql</> looks like this:
 
 <programlisting><![CDATA[
 CREATE TYPE pair AS ( k text, v text );
@@ -671,7 +786,7 @@ CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = text, PROCEDURE = pair);
 <programlisting>
 # pair extension
 comment = 'A key/value pair data type'
-version = '0.1.2'
+default_version = '1.0'
 relocatable = true
 </programlisting>
     </para>
@@ -682,7 +797,7 @@ relocatable = true
 
 <programlisting>
 EXTENSION = pair
-DATA = pair.sql
+DATA = pair-1.0.sql
 
 PG_CONFIG = pg_config
 PGXS := $(shell $(PG_CONFIG) --pgxs)
@@ -739,7 +854,7 @@ include $(PGXS)
 <programlisting>
 MODULES = isbn_issn
 EXTENSION = isbn_issn
-DATA_built = isbn_issn.sql
+DATA_built = isbn_issn-1.0.sql
 DOCS = README.isbn_issn
 
 PG_CONFIG = pg_config
diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml
index e9eb1aafbb6f3552a096f3c872bcf0d3f1ad7f3f..a6c0062fe240905c4f5d010aad656c3a5725f49a 100644
--- a/doc/src/sgml/ref/alter_extension.sgml
+++ b/doc/src/sgml/ref/alter_extension.sgml
@@ -23,6 +23,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
+ALTER EXTENSION <replaceable class="PARAMETER">extension_name</replaceable> UPDATE [ TO <replaceable class="PARAMETER">new_version</replaceable> ]
 ALTER EXTENSION <replaceable class="PARAMETER">extension_name</replaceable> SET SCHEMA <replaceable class="PARAMETER">new_schema</replaceable>
 ALTER EXTENSION <replaceable class="PARAMETER">extension_name</replaceable> ADD <replaceable class="PARAMETER">member_object</replaceable>
 ALTER EXTENSION <replaceable class="PARAMETER">extension_name</replaceable> DROP <replaceable class="PARAMETER">member_object</replaceable>
@@ -61,6 +62,17 @@ ALTER EXTENSION <replaceable class="PARAMETER">extension_name</replaceable> DROP
    extension.  There are several subforms:
 
    <variablelist>
+   <varlistentry>
+    <term><literal>UPDATE</literal></term>
+    <listitem>
+     <para>
+      This form updates the extension to a newer version.  The extension
+      must supply a suitable update script (or series of scripts) that can
+      modify the currently-installed version into the requested version.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET SCHEMA</literal></term>
     <listitem>
@@ -77,7 +89,7 @@ ALTER EXTENSION <replaceable class="PARAMETER">extension_name</replaceable> DROP
     <listitem>
      <para>
       This form adds an existing object to the extension.  This is mainly
-      useful in extension upgrade scripts.  The object will subsequently
+      useful in extension update scripts.  The object will subsequently
       be treated as a member of the extension; notably, it can only be
       dropped by dropping the extension.
      </para>
@@ -89,7 +101,7 @@ ALTER EXTENSION <replaceable class="PARAMETER">extension_name</replaceable> DROP
     <listitem>
      <para>
       This form removes a member object from the extension.  This is mainly
-      useful in extension upgrade scripts.  The object is not dropped, only
+      useful in extension update scripts.  The object is not dropped, only
       disassociated from the extension.
      </para>
     </listitem>
@@ -119,6 +131,18 @@ ALTER EXTENSION <replaceable class="PARAMETER">extension_name</replaceable> DROP
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><replaceable class="PARAMETER">new_version</replaceable></term>
+     <listitem>
+      <para>
+       The desired new version of the extension.  This can be written as
+       either an identifier or a string literal.  If not specified,
+       <command>ALTER EXTENSION UPDATE</> attempts to update to whatever is
+       shown as the default version in the extension's control file.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><replaceable class="PARAMETER">new_schema</replaceable></term>
      <listitem>
@@ -231,7 +255,14 @@ ALTER EXTENSION <replaceable class="PARAMETER">extension_name</replaceable> DROP
   <title>Examples</title>
 
   <para>
-   To change the schema of the extension <literal>hstore</literal>
+   To update the <literal>hstore</literal> extension to version 2.0:
+<programlisting>
+ALTER EXTENSION hstore UPDATE TO '2.0';
+</programlisting>
+  </para>
+
+  <para>
+   To change the schema of the <literal>hstore</literal> extension
    to <literal>utils</literal>:
 <programlisting>
 ALTER EXTENSION hstore SET SCHEMA utils;
diff --git a/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml
index 961cab3839e0ee2d9a4292e4f95cf34a03469e27..9e0e3c440b6d7b11461a095372c53cdbb327f0ef 100644
--- a/doc/src/sgml/ref/create_extension.sgml
+++ b/doc/src/sgml/ref/create_extension.sgml
@@ -22,7 +22,9 @@ PostgreSQL documentation
  <refsynopsisdiv>
 <synopsis>
 CREATE EXTENSION <replaceable class="parameter">extension_name</replaceable>
-    [ WITH ] [ SCHEMA [=] <replaceable class="parameter">schema</replaceable> ]
+    [ WITH ] [ SCHEMA <replaceable class="parameter">schema</replaceable> ]
+             [ VERSION <replaceable class="parameter">version</replaceable> ]
+             [ FROM <replaceable class="parameter">old_version</replaceable> ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -82,6 +84,44 @@ CREATE EXTENSION <replaceable class="parameter">extension_name</replaceable>
        </para>
       </listitem>
      </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="parameter">version</replaceable></term>
+      <listitem>
+       <para>
+        The version of the extension to install.  This can be written as
+        either an identifier or a string literal.  The default version is
+        whatever is specified in the extension's control file.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="parameter">old_version</replaceable></term>
+      <listitem>
+       <para>
+        <literal>FROM</> <replaceable class="parameter">old_version</>
+        must be specified when, and only when, you are attempting to install
+        an extension that replaces an <quote>old style</> module that is just
+        a collection of objects not packaged into an extension.  This option
+        causes <command>CREATE EXTENSION</> to run an alternative installation
+        script that absorbs the existing objects into the extension, instead
+        of creating new objects.  Be careful that <literal>SCHEMA</> specifies
+        the schema containing these pre-existing objects.
+       </para>
+
+       <para>
+        The value to use for <replaceable
+        class="parameter">old_version</replaceable> is determined by the
+        extension's author, and might vary if there is more than one version
+        of the old-style module that can be upgraded into an extension.
+        For the standard additional modules supplied with pre-9.1
+        <productname>PostgreSQL</productname>, use <literal>unpackaged</>
+        for <replaceable class="parameter">old_version</replaceable> when
+        updating a module to extension style.
+       </para>
+      </listitem>
+     </varlistentry>
   </variablelist>
  </refsect1>
 
@@ -95,6 +135,16 @@ CREATE EXTENSION <replaceable class="parameter">extension_name</replaceable>
 CREATE EXTENSION hstore;
 </programlisting>
   </para>
+
+  <para>
+   Update a pre-9.1 installation of <literal>hstore</> into
+   extension style:
+<programlisting>
+CREATE EXTENSION hstore SCHEMA public FROM unpackaged;
+</programlisting>
+   Be careful to specify the schema in which you installed the existing
+   <literal>hstore</> objects.
+  </para>
  </refsect1>
 
  <refsect1>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index bc121808bec094299bdd17faf752f6db018bd6b1..5d8b36b0966962048221380e5b6f5bfd788c0e1c 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -53,17 +53,21 @@
 #include "utils/tqual.h"
 
 
+/* Globally visible state variables */
 bool			creating_extension = false;
 Oid				CurrentExtensionObject = InvalidOid;
 
+/* Character that separates extension & version names in a script filename */
+#define EXT_VERSION_SEP  '-'
+
 /*
  * Internal data structure to hold the results of parsing a control file
  */
 typedef struct ExtensionControlFile
 {
 	char	   *name;			/* name of the extension */
-	char	   *script;			/* filename of the installation script */
-	char	   *version;	    /* version ID, if any */
+	char	   *directory;		/* directory for script files */
+	char	   *default_version; /* default install target version, if any */
 	char	   *comment;		/* comment, if any */
 	char	   *schema;			/* target schema (allowed if !relocatable) */
 	bool		relocatable;	/* is ALTER EXTENSION SET SCHEMA supported? */
@@ -71,6 +75,19 @@ typedef struct ExtensionControlFile
 	List	   *requires;		/* names of prerequisite extensions */
 } ExtensionControlFile;
 
+/*
+ * Internal data structure for update path information
+ */
+typedef struct ExtensionVersionInfo
+{
+	char	   *name;			/* name of the starting version */
+	List	   *reachable;		/* List of ExtensionVersionInfo's */
+	/* working state for Dijkstra's algorithm: */
+	bool		distance_known;	/* is distance from start known yet? */
+	int			distance;		/* current worst-case distance estimate */
+	struct ExtensionVersionInfo *previous; /* current best predecessor */
+} ExtensionVersionInfo;
+
 
 /*
  * get_extension_oid - given an extension name, look up the OID
@@ -196,6 +213,44 @@ get_extension_schema(Oid ext_oid)
 	return result;
 }
 
+/*
+ * Utility functions to check validity of extension and version names
+ */
+static void
+check_valid_extension_name(const char *extensionname)
+{
+	/*
+	 * No directory separators (this is sufficient to prevent ".." style
+	 * attacks).
+	 */
+	if (first_dir_separator(extensionname) != NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid extension name: \"%s\"", extensionname),
+				 errdetail("Extension names must not contain directory separator characters.")));
+}
+
+static void
+check_valid_version_name(const char *versionname)
+{
+	/* No separators --- would risk confusion of install vs update scripts */
+	if (strchr(versionname, EXT_VERSION_SEP))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid extension version name: \"%s\"", versionname),
+				 errdetail("Version names must not contain the character \"%c\".",
+						   EXT_VERSION_SEP)));
+	/*
+	 * No directory separators (this is sufficient to prevent ".." style
+	 * attacks).
+	 */
+	if (first_dir_separator(versionname) != NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid extension version name: \"%s\"", versionname),
+				 errdetail("Version names must not contain directory separator characters.")));
+}
+
 /*
  * Utility functions to handle extension-related path names
  */
@@ -207,6 +262,14 @@ is_extension_control_filename(const char *filename)
 	return (extension != NULL) && (strcmp(extension, ".control") == 0);
 }
 
+static bool
+is_extension_script_filename(const char *filename)
+{
+	const char *extension = strrchr(filename, '.');
+
+	return (extension != NULL) && (strcmp(extension, ".sql") == 0);
+}
+
 static char *
 get_extension_control_directory(void)
 {
@@ -228,83 +291,150 @@ get_extension_control_filename(const char *extname)
 
 	get_share_path(my_exec_path, sharepath);
 	result = (char *) palloc(MAXPGPATH);
-	snprintf(result, MAXPGPATH, "%s/contrib/%s.control", sharepath, extname);
+	snprintf(result, MAXPGPATH, "%s/contrib/%s.control",
+			 sharepath, extname);
 
 	return result;
 }
 
-/*
- * Given a relative pathname such as "name.sql", return the full path to
- * the script file.  If given an absolute name, just return it.
- */
 static char *
-get_extension_absolute_path(const char *filename)
+get_extension_script_directory(ExtensionControlFile *control)
 {
 	char		sharepath[MAXPGPATH];
 	char	   *result;
 
-	if (is_absolute_path(filename))
-		return pstrdup(filename);
+	/*
+	 * The directory parameter can be omitted, absolute, or relative to the
+	 * control-file directory.
+	 */
+	if (!control->directory)
+		return get_extension_control_directory();
+
+	if (is_absolute_path(control->directory))
+		return pstrdup(control->directory);
 
 	get_share_path(my_exec_path, sharepath);
 	result = (char *) palloc(MAXPGPATH);
-    snprintf(result, MAXPGPATH, "%s/contrib/%s", sharepath, filename);
+    snprintf(result, MAXPGPATH, "%s/contrib/%s",
+			 sharepath, control->directory);
+
+	return result;
+}
+
+static char *
+get_extension_aux_control_filename(ExtensionControlFile *control,
+								   const char *version)
+{
+	char	   *result;
+	char	   *scriptdir;
+
+	scriptdir = get_extension_script_directory(control);
+
+	result = (char *) palloc(MAXPGPATH);
+	snprintf(result, MAXPGPATH, "%s/%s%c%s.control",
+			 scriptdir, control->name, EXT_VERSION_SEP, version);
+
+	pfree(scriptdir);
+
+	return result;
+}
+
+static char *
+get_extension_script_filename(ExtensionControlFile *control,
+							  const char *from_version, const char *version)
+{
+	char	   *result;
+	char	   *scriptdir;
+
+	scriptdir = get_extension_script_directory(control);
+
+	result = (char *) palloc(MAXPGPATH);
+	if (from_version)
+		snprintf(result, MAXPGPATH, "%s/%s%c%s%c%s.sql",
+				 scriptdir, control->name, EXT_VERSION_SEP, from_version,
+				 EXT_VERSION_SEP, version);
+	else
+		snprintf(result, MAXPGPATH, "%s/%s%c%s.sql",
+				 scriptdir, control->name, EXT_VERSION_SEP, version);
+
+	pfree(scriptdir);
 
 	return result;
 }
 
 
 /*
- * Read the control file for the specified extension.
+ * Parse contents of primary or auxiliary control file, and fill in
+ * fields of *control.  We parse primary file if version == NULL,
+ * else the optional auxiliary file for that version.
  *
- * The control file is supposed to be very short, half a dozen lines, and
- * reading it is only allowed to superuser, so we don't worry about
- * memory allocation risks here.  Also note that we don't worry about
- * what encoding it's in; all values are expected to be ASCII.
+ * Control files are supposed to be very short, half a dozen lines,
+ * so we don't worry about memory allocation risks here.  Also we don't
+ * worry about what encoding it's in; all values are expected to be ASCII.
  */
-static ExtensionControlFile *
-read_extension_control_file(const char *extname)
+static void
+parse_extension_control_file(ExtensionControlFile *control,
+							 const char *version)
 {
-	char	   *filename = get_extension_control_filename(extname);
+	char	   *filename;
 	FILE	   *file;
-	ExtensionControlFile *control;
 	ConfigVariable *item,
 				   *head = NULL,
 				   *tail = NULL;
 
 	/*
-	 * Parse the file content, using GUC's file parsing code
+	 * Locate the file to read.  Auxiliary files are optional.
 	 */
+	if (version)
+		filename = get_extension_aux_control_filename(control, version);
+	else
+		filename = get_extension_control_filename(control->name);
+
 	if ((file = AllocateFile(filename, "r")) == NULL)
+	{
+		if (version && errno == ENOENT)
+		{
+			/* no auxiliary file for this version */
+			pfree(filename);
+			return;
+		}
 		ereport(ERROR,
 				(errcode_for_file_access(),
 				 errmsg("could not open extension control file \"%s\": %m",
 						filename)));
+	}
 
+	/*
+	 * Parse the file content, using GUC's file parsing code
+	 */
 	ParseConfigFp(file, filename, 0, ERROR, &head, &tail);
 
 	FreeFile(file);
 
-	/*
-	 * Set up default values.  Pointer fields are initially null.
-	 */
-	control = (ExtensionControlFile *) palloc0(sizeof(ExtensionControlFile));
-	control->name = pstrdup(extname);
-	control->relocatable = false;
-	control->encoding = -1;
-
 	/*
 	 * Convert the ConfigVariable list into ExtensionControlFile entries.
 	 */
 	for (item = head; item != NULL; item = item->next)
 	{
-		if (strcmp(item->name, "script") == 0)
+		if (strcmp(item->name, "directory") == 0)
 		{
-			control->script = pstrdup(item->value);
+			if (version)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("parameter \"%s\" cannot be set in a per-version extension control file",
+								item->name)));
+
+			control->directory = pstrdup(item->value);
 		}
-		else if (strcmp(item->name, "version") == 0)
+		else if (strcmp(item->name, "default_version") == 0)
 		{
-			control->version = pstrdup(item->value);
+			if (version)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("parameter \"%s\" cannot be set in a per-version extension control file",
+								item->name)));
+
+			control->default_version = pstrdup(item->value);
 		}
 		else if (strcmp(item->name, "comment") == 0)
 		{
@@ -324,6 +454,12 @@ read_extension_control_file(const char *extname)
 		}
 		else if (strcmp(item->name, "encoding") == 0)
 		{
+			if (version)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("parameter \"%s\" cannot be set in a per-version extension control file",
+								item->name)));
+
 			control->encoding = pg_valid_server_encoding(item->value);
 			if (control->encoding < 0)
 				ereport(ERROR,
@@ -360,22 +496,35 @@ read_extension_control_file(const char *extname)
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("parameter \"schema\" cannot be specified when \"relocatable\" is true")));
 
+	pfree(filename);
+}
+
+/*
+ * Read the primary control file for the specified extension.
+ */
+static ExtensionControlFile *
+read_extension_control_file(const char *extname)
+{
+	ExtensionControlFile *control;
+
 	/*
-	 * script defaults to ${extension-name}.sql
+	 * Set up default values.  Pointer fields are initially null.
 	 */
-	if (control->script == NULL)
-	{
-		char	script[MAXPGPATH];
+	control = (ExtensionControlFile *) palloc0(sizeof(ExtensionControlFile));
+	control->name = pstrdup(extname);
+	control->relocatable = false;
+	control->encoding = -1;
 
-		snprintf(script, MAXPGPATH, "%s.sql", control->name);
-		control->script = pstrdup(script);
-	}
+	/*
+	 * Parse the primary control file.
+	 */
+	parse_extension_control_file(control, NULL);
 
 	return control;
 }
 
 /*
- * Read the SQL script into a string, and convert to database encoding
+ * Read a SQL script file into a string, and convert to database encoding
  */
 static char *
 read_extension_script_file(const ExtensionControlFile *control,
@@ -513,20 +662,26 @@ execute_sql_string(const char *sql, const char *filename)
 }
 
 /*
- * Execute the extension's script file
+ * Execute the appropriate script file for installing or updating the extension
+ *
+ * If from_version isn't NULL, it's an update
  */
 static void
 execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
+						 const char *from_version,
+						 const char *version,
 						 List *requiredSchemas,
 						 const char *schemaName, Oid schemaOid)
 {
-	char       *filename = get_extension_absolute_path(control->script);
+	char       *filename;
 	char	   *save_client_min_messages,
 			   *save_log_min_messages,
 			   *save_search_path;
 	StringInfoData pathbuf;
 	ListCell   *lc;
 
+	filename = get_extension_script_filename(control, from_version, version);
+
 	/*
 	 * Force client_min_messages and log_min_messages to be at least WARNING,
 	 * so that we won't spam the user with useless NOTICE messages from common
@@ -635,6 +790,186 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 							 GUC_ACTION_LOCAL, true);
 }
 
+/*
+ * Find or create an ExtensionVersionInfo for the specified version name
+ *
+ * Currently, we just use a List of the ExtensionVersionInfo's.  Searching
+ * for them therefore uses about O(N^2) time when there are N versions of
+ * the extension.  We could change the data structure to a hash table if
+ * this ever becomes a bottleneck.
+ */
+static ExtensionVersionInfo *
+get_ext_ver_info(const char *versionname, List **evi_list)
+{
+	ExtensionVersionInfo *evi;
+	ListCell   *lc;
+
+	foreach(lc, *evi_list)
+	{
+		evi = (ExtensionVersionInfo *) lfirst(lc);
+		if (strcmp(evi->name, versionname) == 0)
+			return evi;
+	}
+
+	evi = (ExtensionVersionInfo *) palloc(sizeof(ExtensionVersionInfo));
+	evi->name = pstrdup(versionname);
+	evi->reachable = NIL;
+	/* initialize for later application of Dijkstra's algorithm */
+	evi->distance_known = false;
+	evi->distance = INT_MAX;
+	evi->previous = NULL;
+
+	*evi_list = lappend(*evi_list, evi);
+
+	return evi;
+}
+
+/*
+ * Locate the nearest unprocessed ExtensionVersionInfo
+ *
+ * This part of the algorithm is also about O(N^2).  A priority queue would
+ * make it much faster, but for now there's no need.
+ */
+static ExtensionVersionInfo *
+get_nearest_unprocessed_vertex(List *evi_list)
+{
+	ExtensionVersionInfo *evi = NULL;
+	ListCell   *lc;
+
+	foreach(lc, evi_list)
+	{
+		ExtensionVersionInfo *evi2 = (ExtensionVersionInfo *) lfirst(lc);
+
+		/* only vertices whose distance is still uncertain are candidates */
+		if (evi2->distance_known)
+			continue;
+		/* remember the closest such vertex */
+		if (evi == NULL ||
+			evi->distance > evi2->distance)
+			evi = evi2;
+	}
+
+	return evi;
+}
+
+/*
+ * Obtain information about the set of update scripts available for the
+ * specified extension.  The result is a List of ExtensionVersionInfo
+ * structs, each with a subsidiary list of the ExtensionVersionInfos for
+ * the versions that can be reached in one step from that version.
+ */
+static List *
+get_ext_ver_list(ExtensionControlFile *control)
+{
+	List	   *evi_list = NIL;
+	int			extnamelen = strlen(control->name);
+	char	   *location;
+	DIR		   *dir;
+	struct dirent *de;
+
+	location = get_extension_script_directory(control);
+	dir  = AllocateDir(location);
+	while ((de = ReadDir(dir, location)) != NULL)
+	{
+		char	   *vername;
+		char	   *vername2;
+		ExtensionVersionInfo *evi;
+		ExtensionVersionInfo *evi2;
+
+		/* 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, control->name, extnamelen) != 0 ||
+			de->d_name[extnamelen] != EXT_VERSION_SEP)
+			continue;
+
+		/* extract version names from 'extname-something.sql' filename */
+		vername = pstrdup(de->d_name + extnamelen + 1);
+		*strrchr(vername, '.') = '\0';
+		vername2 = strchr(vername, EXT_VERSION_SEP);
+		if (!vername2)
+			continue;			/* it's not an update script */
+		*vername2++ = '\0';
+
+		/* Create ExtensionVersionInfos and link them together */
+		evi = get_ext_ver_info(vername, &evi_list);
+		evi2 = get_ext_ver_info(vername2, &evi_list);
+		evi->reachable = lappend(evi->reachable, evi2);
+	}
+	FreeDir(dir);
+
+	return evi_list;
+}
+
+/*
+ * Given an initial and final version name, identify the sequence of update
+ * scripts that have to be applied to perform that update.
+ *
+ * Result is a List of names of versions to transition through.
+ */
+static List *
+identify_update_path(ExtensionControlFile *control,
+					 const char *oldVersion, const char *newVersion)
+{
+	List	   *result;
+	List	   *evi_list;
+	ExtensionVersionInfo *evi_start;
+	ExtensionVersionInfo *evi_target;
+	ExtensionVersionInfo *evi;
+
+	if (strcmp(oldVersion, newVersion) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("version to install or update to must be different from old version")));
+
+	/* Extract the version update graph from the script directory */
+	evi_list = get_ext_ver_list(control);
+
+	/* Initialize start and end vertices */
+	evi_start = get_ext_ver_info(oldVersion, &evi_list);
+	evi_target = get_ext_ver_info(newVersion, &evi_list);
+
+	evi_start->distance = 0;
+
+	while ((evi = get_nearest_unprocessed_vertex(evi_list)) != NULL)
+	{
+		ListCell   *lc;
+
+		if (evi->distance == INT_MAX)
+			break;				/* all remaining vertices are unreachable */
+		evi->distance_known = true;
+		if (evi == evi_target)
+			break;				/* found shortest path to target */
+		foreach(lc, evi->reachable)
+		{
+			ExtensionVersionInfo *evi2 = (ExtensionVersionInfo *) lfirst(lc);
+			int		newdist;
+
+			newdist = evi->distance + 1;
+			if (newdist < evi2->distance)
+			{
+				evi2->distance = newdist;
+				evi2->previous = evi;
+			}
+		}
+	}
+
+	if (!evi_target->distance_known)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("extension \"%s\" has no update path from version \"%s\" to version \"%s\"",
+						control->name, oldVersion, newVersion)));
+
+	/* Build and return list of version names representing the update path */
+	result = NIL;
+	for (evi = evi_target; evi != evi_start; evi = evi->previous)
+		result = lcons(evi->name, result);
+
+	return result;
+}
+
 /*
  * CREATE EXTENSION
  */
@@ -642,8 +977,12 @@ void
 CreateExtension(CreateExtensionStmt *stmt)
 {
 	DefElem    *d_schema = NULL;
+	DefElem    *d_new_version = NULL;
+	DefElem    *d_old_version = NULL;
 	char       *schemaName;
 	Oid			schemaOid;
+	char       *versionName;
+	char       *oldVersionName;
 	Oid			extowner = GetUserId();
 	ExtensionControlFile *control;
 	List	   *requiredExtensions;
@@ -668,6 +1007,9 @@ CreateExtension(CreateExtensionStmt *stmt)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("nested CREATE EXTENSION is not supported")));
 
+	/* Check extension name validity before any filesystem access */
+	check_valid_extension_name(stmt->extname);
+
 	/*
 	 * Check for duplicate extension name.  The unique index on
 	 * pg_extension.extname would catch this anyway, and serves as a backstop
@@ -679,9 +1021,9 @@ CreateExtension(CreateExtensionStmt *stmt)
 				 errmsg("extension \"%s\" already exists", stmt->extname)));
 
 	/*
-	 * Read the 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.
+	 * 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.
 	 */
 	control = read_extension_control_file(stmt->extname);
 
@@ -700,10 +1042,58 @@ CreateExtension(CreateExtensionStmt *stmt)
 						 errmsg("conflicting or redundant options")));
 			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")));
+			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")));
+			d_old_version = defel;
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
 
+	/*
+	 * Determine the version to install
+	 */
+	if (d_new_version && d_new_version->arg)
+		versionName = strVal(d_new_version->arg);
+	else if (control->default_version)
+		versionName = control->default_version;
+	else
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("version to install must be specified")));
+		versionName = NULL;		/* keep compiler quiet */
+	}
+	check_valid_version_name(versionName);
+
+	/*
+	 * Modify control parameters for specific new version
+	 */
+	parse_extension_control_file(control, versionName);
+
+	/*
+	 * Determine the (unpackaged) version to update from, if any
+	 */
+	if (d_old_version && d_old_version->arg)
+	{
+		oldVersionName = strVal(d_old_version->arg);
+		check_valid_version_name(oldVersionName);
+	}
+	else
+		oldVersionName = NULL;
+
 	/*
 	 * Determine the target schema to install the extension into
 	 */
@@ -799,7 +1189,7 @@ CreateExtension(CreateExtensionStmt *stmt)
 	 */
 	extensionOid = InsertExtensionTuple(control->name, extowner,
 										schemaOid, control->relocatable,
-										control->version,
+										versionName,
 										PointerGetDatum(NULL),
 										PointerGetDatum(NULL),
 										requiredExtensions);
@@ -811,10 +1201,36 @@ CreateExtension(CreateExtensionStmt *stmt)
 		CreateComments(extensionOid, ExtensionRelationId, 0, control->comment);
 
 	/*
-	 * Finally, execute the extension script to create the member objects
+	 * Finally, execute the extension's script file(s)
 	 */
-	execute_extension_script(extensionOid, control, requiredSchemas,
-							 schemaName, schemaOid);
+	if (oldVersionName == NULL)
+	{
+		/* Simple install */
+		execute_extension_script(extensionOid, control,
+								 oldVersionName, versionName,
+								 requiredSchemas,
+								 schemaName, schemaOid);
+	}
+	else
+	{
+		/* Update from unpackaged objects --- find update-file path */
+		List	   *updateVersions;
+
+		updateVersions = identify_update_path(control,
+											  oldVersionName,
+											  versionName);
+
+		foreach(lc, updateVersions)
+		{
+			char   *vname = (char *) lfirst(lc);
+
+			execute_extension_script(extensionOid, control,
+									 oldVersionName, vname,
+									 requiredSchemas,
+									 schemaName, schemaOid);
+			oldVersionName = vname;
+		}
+	}
 }
 
 /*
@@ -858,12 +1274,7 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	values[Anum_pg_extension_extowner - 1] = ObjectIdGetDatum(extOwner);
 	values[Anum_pg_extension_extnamespace - 1] = ObjectIdGetDatum(schemaOid);
 	values[Anum_pg_extension_extrelocatable - 1] = BoolGetDatum(relocatable);
-
-	if (extVersion == NULL)
-		nulls[Anum_pg_extension_extversion - 1] = true;
-	else
-		values[Anum_pg_extension_extversion - 1] =
-			CStringGetTextDatum(extVersion);
+	values[Anum_pg_extension_extversion - 1] = CStringGetTextDatum(extVersion);
 
 	if (extConfig == PointerGetDatum(NULL))
 		nulls[Anum_pg_extension_extconfig - 1] = true;
@@ -1102,11 +1513,11 @@ pg_available_extensions(PG_FUNCTION_ARGS)
 			/* name */
 			values[0] = DirectFunctionCall1(namein,
 											CStringGetDatum(control->name));
-			/* version */
-			if (control->version == NULL)
+			/* default_version */
+			if (control->default_version == NULL)
 				nulls[1] = true;
 			else
-				values[1] = CStringGetTextDatum(control->version);
+				values[1] = CStringGetTextDatum(control->default_version);
 			/* relocatable */
 			values[2] = BoolGetDatum(control->relocatable);
 			/* comment */
@@ -1435,6 +1846,230 @@ AlterExtensionNamespace(List *names, const char *newschema)
 						NamespaceRelationId, oldNspOid, nspOid);
 }
 
+/*
+ * Execute ALTER EXTENSION UPDATE
+ */
+void
+ExecAlterExtensionStmt(AlterExtensionStmt *stmt)
+{
+	DefElem    *d_new_version = NULL;
+	char       *schemaName;
+	Oid			schemaOid;
+	char       *versionName;
+	char       *oldVersionName;
+	ExtensionControlFile *control;
+	List	   *requiredExtensions;
+	List	   *requiredSchemas;
+	Oid			extensionOid;
+	Relation	extRel;
+	ScanKeyData	key[1];
+	SysScanDesc	extScan;
+	HeapTuple	extTup;
+	Form_pg_extension extForm;
+	Datum		values[Natts_pg_extension];
+	bool		nulls[Natts_pg_extension];
+	bool		repl[Natts_pg_extension];
+	List	   *updateVersions;
+	ObjectAddress myself;
+	Datum		datum;
+	bool		isnull;
+	ListCell   *lc;
+
+	/*
+	 * For now, insist on superuser privilege.  Later we might want to
+	 * relax this to ownership of the extension.
+	 */
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to use ALTER EXTENSION"))));
+
+	/*
+	 * We use global variables to track the extension being created, so we
+	 * can create/alter only one extension at the same time.
+	 */
+	if (creating_extension)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("nested ALTER EXTENSION is not supported")));
+
+	/* Look up the extension --- it must already exist in pg_extension */
+	extRel = heap_open(ExtensionRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_extension_extname,
+				BTEqualStrategyNumber, F_NAMEEQ,
+				CStringGetDatum(stmt->extname));
+
+	extScan = systable_beginscan(extRel, ExtensionNameIndexId, true,
+								 SnapshotNow, 1, key);
+
+	extTup = systable_getnext(extScan);
+
+	if (!HeapTupleIsValid(extTup))
+        ereport(ERROR,
+                (errcode(ERRCODE_UNDEFINED_OBJECT),
+                 errmsg("extension \"%s\" does not exist",
+                        stmt->extname)));
+
+	/* Copy tuple so we can modify it below */
+	extTup = heap_copytuple(extTup);
+	extForm = (Form_pg_extension) GETSTRUCT(extTup);
+	extensionOid = HeapTupleGetOid(extTup);
+
+	systable_endscan(extScan);
+
+	/*
+	 * 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.
+	 */
+	control = read_extension_control_file(stmt->extname);
+
+	/*
+	 * Read the statement option list
+	 */
+	foreach(lc, stmt->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "new_version") == 0)
+		{
+			if (d_new_version)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			d_new_version = defel;
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+
+	/*
+	 * Determine the version to install
+	 */
+	if (d_new_version && d_new_version->arg)
+		versionName = strVal(d_new_version->arg);
+	else if (control->default_version)
+		versionName = control->default_version;
+	else
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("version to install must be specified")));
+		versionName = NULL;		/* keep compiler quiet */
+	}
+	check_valid_version_name(versionName);
+
+	/*
+	 * Modify control parameters for specific new version
+	 */
+	parse_extension_control_file(control, versionName);
+
+	/*
+	 * Determine the existing version we are upgrading from
+	 */
+	datum = heap_getattr(extTup, Anum_pg_extension_extversion,
+						 RelationGetDescr(extRel), &isnull);
+	if (isnull)
+		elog(ERROR, "extversion is null");
+	oldVersionName = text_to_cstring(DatumGetTextPP(datum));
+
+	/*
+	 * Determine the target schema (already set by original install)
+	 */
+	schemaOid = extForm->extnamespace;
+	schemaName = get_namespace_name(schemaOid);
+
+	/*
+	 * Look up the prerequisite extensions, and build lists of their OIDs
+	 * and the OIDs of their target schemas.  We assume that the requires
+	 * list is version-specific, so the dependencies can change across
+	 * versions.  But note that only the final version's requires list
+	 * is being consulted here!
+	 */
+	requiredExtensions = NIL;
+	requiredSchemas = NIL;
+	foreach(lc, control->requires)
+	{
+		char	   *curreq = (char *) lfirst(lc);
+		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)));
+		reqschema = get_extension_schema(reqext);
+		requiredExtensions = lappend_oid(requiredExtensions, reqext);
+		requiredSchemas = lappend_oid(requiredSchemas, reqschema);
+	}
+
+	/*
+	 * Modify extversion in the pg_extension tuple
+	 */
+	memset(values, 0, sizeof(values));
+	memset(nulls, 0, sizeof(nulls));
+	memset(repl, 0, sizeof(repl));
+
+	values[Anum_pg_extension_extversion - 1] = CStringGetTextDatum(versionName);
+	repl[Anum_pg_extension_extversion - 1] = true;
+
+	extTup = heap_modify_tuple(extTup, RelationGetDescr(extRel),
+							   values, nulls, repl);
+
+	simple_heap_update(extRel, &extTup->t_self, extTup);
+	CatalogUpdateIndexes(extRel, extTup);
+
+	heap_close(extRel, RowExclusiveLock);
+
+	/*
+	 * Remove and recreate dependencies on prerequisite extensions
+	 */
+	deleteDependencyRecordsForClass(ExtensionRelationId, extensionOid,
+									ExtensionRelationId, DEPENDENCY_NORMAL);
+
+	myself.classId = ExtensionRelationId;
+	myself.objectId = extensionOid;
+	myself.objectSubId = 0;
+
+	foreach(lc, requiredExtensions)
+	{
+		Oid			reqext = lfirst_oid(lc);
+		ObjectAddress otherext;
+
+		otherext.classId = ExtensionRelationId;
+		otherext.objectId = reqext;
+		otherext.objectSubId = 0;
+
+		recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Finally, execute the extension's script file(s)
+	 */
+	updateVersions = identify_update_path(control,
+										  oldVersionName,
+										  versionName);
+
+	foreach(lc, updateVersions)
+	{
+		char   *vname = (char *) lfirst(lc);
+
+		execute_extension_script(extensionOid, control,
+								 oldVersionName, vname,
+								 requiredSchemas,
+								 schemaName, schemaOid);
+		oldVersionName = vname;
+	}
+}
+
 /*
  * Execute ALTER EXTENSION ADD/DROP
  */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 46acaf8d701abc6be9b8683f30a3f04d066f596c..57d580208102c116323ffd9ff74dcba21fcd3ecc 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3251,6 +3251,17 @@ _copyCreateExtensionStmt(CreateExtensionStmt *from)
 	return newnode;
 }
 
+static AlterExtensionStmt *
+_copyAlterExtensionStmt(AlterExtensionStmt *from)
+{
+	AlterExtensionStmt *newnode = makeNode(AlterExtensionStmt);
+
+	COPY_STRING_FIELD(extname);
+	COPY_NODE_FIELD(options);
+
+	return newnode;
+}
+
 static AlterExtensionContentsStmt *
 _copyAlterExtensionContentsStmt(AlterExtensionContentsStmt *from)
 {
@@ -4267,6 +4278,9 @@ copyObject(void *from)
 		case T_CreateExtensionStmt:
 			retval = _copyCreateExtensionStmt(from);
 			break;
+		case T_AlterExtensionStmt:
+			retval = _copyAlterExtensionStmt(from);
+			break;
 		case T_AlterExtensionContentsStmt:
 			retval = _copyAlterExtensionContentsStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2fbe99937d2e49d901058e43118a978d99201ffc..f57cf99ba2c1f473cbef55c7c46f78765ceb1ae3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1654,6 +1654,15 @@ _equalCreateExtensionStmt(CreateExtensionStmt *a, CreateExtensionStmt *b)
 	return true;
 }
 
+static bool
+_equalAlterExtensionStmt(AlterExtensionStmt *a, AlterExtensionStmt *b)
+{
+	COMPARE_STRING_FIELD(extname);
+	COMPARE_NODE_FIELD(options);
+
+	return true;
+}
+
 static bool
 _equalAlterExtensionContentsStmt(AlterExtensionContentsStmt *a, AlterExtensionContentsStmt *b)
 {
@@ -2869,6 +2878,9 @@ equal(void *a, void *b)
 		case T_CreateExtensionStmt:
 			retval = _equalCreateExtensionStmt(a, b);
 			break;
+		case T_AlterExtensionStmt:
+			retval = _equalAlterExtensionStmt(a, b);
+			break;
 		case T_AlterExtensionContentsStmt:
 			retval = _equalAlterExtensionContentsStmt(a, b);
 			break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 82ff9accc72761c83afa5b6d7e6fc9cb2e1d8e9a..a99f8c6ca248168362834f2198f4475ca15e791a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -185,7 +185,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
 		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
 		AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
 		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
-		AlterExtensionContentsStmt AlterForeignTableStmt
+		AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
 		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
 		AlterRoleStmt AlterRoleSetStmt
 		AlterDefaultPrivilegesStmt DefACLAction
@@ -227,9 +227,11 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
 %type <dbehavior>	opt_drop_behavior
 
 %type <list>	createdb_opt_list alterdb_opt_list copy_opt_list
-				transaction_mode_list create_extension_opt_list
+				transaction_mode_list
+				create_extension_opt_list alter_extension_opt_list
 %type <defelt>	createdb_opt_item alterdb_opt_item copy_opt_item
-				transaction_mode_item create_extension_opt_item
+				transaction_mode_item
+				create_extension_opt_item alter_extension_opt_item
 
 %type <ival>	opt_lock lock_type cast_context
 %type <ival>	vacuum_option_list vacuum_option_elem
@@ -664,6 +666,7 @@ stmt :
 			| AlterDefaultPrivilegesStmt
 			| AlterDomainStmt
 			| AlterEnumStmt
+			| AlterExtensionStmt
 			| AlterExtensionContentsStmt
 			| AlterFdwStmt
 			| AlterForeignServerStmt
@@ -3222,7 +3225,7 @@ DropTableSpaceStmt: DROP TABLESPACE name
  *
  * 		QUERY:
  *             CREATE EXTENSION extension
- *             [ WITH ] [ SCHEMA [=] schema ]
+ *             [ WITH ] [ SCHEMA schema ] [ VERSION version ] [ FROM oldversion ]
  *
  *****************************************************************************/
 
@@ -3243,9 +3246,46 @@ create_extension_opt_list:
 		;
 
 create_extension_opt_item:
-			SCHEMA opt_equal name
+			SCHEMA name
 				{
-					$$ = makeDefElem("schema", (Node *)makeString($3));
+					$$ = makeDefElem("schema", (Node *)makeString($2));
+				}
+			| VERSION_P ColId_or_Sconst
+				{
+					$$ = makeDefElem("new_version", (Node *)makeString($2));
+				}
+			| FROM ColId_or_Sconst
+				{
+					$$ = makeDefElem("old_version", (Node *)makeString($2));
+				}
+		;
+
+/*****************************************************************************
+ *
+ * ALTER EXTENSION name UPDATE [ TO version ]
+ *
+ *****************************************************************************/
+
+AlterExtensionStmt: ALTER EXTENSION name UPDATE alter_extension_opt_list
+				{
+					AlterExtensionStmt *n = makeNode(AlterExtensionStmt);
+					n->extname = $3;
+					n->options = $5;
+					$$ = (Node *) n;
+				}
+		;
+
+alter_extension_opt_list:
+			alter_extension_opt_list alter_extension_opt_item
+				{ $$ = lappend($1, $2); }
+			| /* EMPTY */
+				{ $$ = NIL; }
+		;
+
+alter_extension_opt_item:
+			TO ColId_or_Sconst
+				{
+					$$ = makeDefElem("new_version", (Node *)makeString($2));
 				}
 		;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c942de3bf62cb7b47ab26c1e2b62ff78d808945a..8ca042024f34ef3408f80d430f864915c8b007c1 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -212,6 +212,7 @@ check_xact_readonly(Node *parsetree)
 		case T_AlterTSDictionaryStmt:
 		case T_AlterTSConfigurationStmt:
 		case T_CreateExtensionStmt:
+		case T_AlterExtensionStmt:
 		case T_AlterExtensionContentsStmt:
 		case T_CreateFdwStmt:
 		case T_AlterFdwStmt:
@@ -601,6 +602,10 @@ standard_ProcessUtility(Node *parsetree,
 			CreateExtension((CreateExtensionStmt *) parsetree);
 			break;
 
+		case T_AlterExtensionStmt:
+			ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+			break;
+
 		case T_AlterExtensionContentsStmt:
 			ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
 			break;
@@ -1680,6 +1685,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "CREATE EXTENSION";
 			break;
 
+		case T_AlterExtensionStmt:
+			tag = "ALTER EXTENSION";
+			break;
+
 		case T_AlterExtensionContentsStmt:
 			tag = "ALTER EXTENSION";
 			break;
@@ -2307,6 +2316,7 @@ GetCommandLogLevel(Node *parsetree)
 			break;
 
 		case T_CreateExtensionStmt:
+		case T_AlterExtensionStmt:
 		case T_AlterExtensionContentsStmt:
 			lev = LOGSTMT_DDL;
 			break;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5561c7a6876baa5ff110a6b9bc9429e11679d4db..83c7157b2e8b99ed95f630415a3b81a3c6a5ee29 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2725,10 +2725,7 @@ getExtensions(int *numExtensions)
 		extinfo[i].dobj.name = strdup(PQgetvalue(res, i, i_extname));
 		extinfo[i].namespace = strdup(PQgetvalue(res, i, i_nspname));
 		extinfo[i].relocatable = *(PQgetvalue(res, i, i_extrelocatable)) == 't';
-		if (PQgetisnull(res, i, i_extversion))
-			extinfo[i].extversion = NULL;
-		else
-			extinfo[i].extversion = strdup(PQgetvalue(res, i, i_extversion));
+		extinfo[i].extversion = strdup(PQgetvalue(res, i, i_extversion));
 		extinfo[i].extconfig = strdup(PQgetvalue(res, i, i_extconfig));
 		extinfo[i].extcondition = strdup(PQgetvalue(res, i, i_extcondition));
 	}
@@ -6942,10 +6939,7 @@ dumpExtension(Archive *fout, ExtensionInfo *extinfo)
 		appendStringLiteralAH(q, extinfo->namespace, fout);
 		appendPQExpBuffer(q, ", ");
 		appendPQExpBuffer(q, "%s, ", extinfo->relocatable ? "true" : "false");
-		if (extinfo->extversion)
-			appendStringLiteralAH(q, extinfo->extversion, fout);
-		else
-			appendPQExpBuffer(q, "NULL");
+		appendStringLiteralAH(q, extinfo->extversion, fout);
 		appendPQExpBuffer(q, ", ");
 		/*
 		 * Note that we're pushing extconfig (an OID array) back into
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2c656068f827db45bdb04d0792d79dcdff2072b3..a31281e431cbfeedf4d79fae9339c6559f7e60f5 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -868,7 +868,7 @@ psql_completion(char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
 	{
 		static const char *const list_ALTEREXTENSION[] =
-		{"ADD", "DROP", "SET SCHEMA", NULL};
+		{"ADD", "DROP", "UPDATE", "SET SCHEMA", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTEREXTENSION);
 	}
diff --git a/src/include/catalog/pg_extension.h b/src/include/catalog/pg_extension.h
index 0ad47b01a4b0a8595b6d8cec13797074f5b5d228..63a1a0711f927125442d25815ab84add5dcf9152 100644
--- a/src/include/catalog/pg_extension.h
+++ b/src/include/catalog/pg_extension.h
@@ -36,9 +36,11 @@ CATALOG(pg_extension,3079)
 	bool        extrelocatable; /* if true, allow ALTER EXTENSION SET SCHEMA */
 
 	/*
-	 * VARIABLE LENGTH FIELDS start here.  These fields may be NULL, too.
+	 * VARIABLE LENGTH FIELDS start here.
+	 *
+	 * extversion should never be null, but the others can be.
 	 */
-	text		extversion;			/* extension version ID, if any */
+	text		extversion;			/* extension version name */
 	Oid			extconfig[1];		/* dumpable configuration tables */
 	text		extcondition[1];	/* WHERE clauses for config tables */
 } FormData_pg_extension;
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 7c94449a6cb0f5e853fe70bd79ae7ce0c87b5069..c6e69d5fd4216a0b3965b5f1df7251472d25bb2e 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -37,6 +37,8 @@ extern Oid	InsertExtensionTuple(const char *extName, Oid extOwner,
 					 Datum extConfig, Datum extCondition,
 					 List *requiredExtensions);
 
+extern void ExecAlterExtensionStmt(AlterExtensionStmt *stmt);
+
 extern void ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt);
 
 extern Oid	get_extension_oid(const char *extname, bool missing_ok);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 15bf0631e44c50fb2a94014fd02edf997adcb2b7..e0d05748dafb4d5c223bcc4cb2c17891b43f1d6e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -356,6 +356,7 @@ typedef enum NodeTag
 	T_SecLabelStmt,
 	T_CreateForeignTableStmt,
 	T_CreateExtensionStmt,
+	T_AlterExtensionStmt,
 	T_AlterExtensionContentsStmt,
 
 	/*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b54f0cfe02f7bb5d47d4c37171d12e0543cdbe1e..1aa3e913b5484446b8377d03cde779d98c4fde0d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1546,6 +1546,14 @@ typedef struct CreateExtensionStmt
 	List	   *options;		/* List of DefElem nodes */
 } CreateExtensionStmt;
 
+/* Only used for ALTER EXTENSION UPDATE; later might need an action field */
+typedef struct AlterExtensionStmt
+{
+	NodeTag		type;
+	char	   *extname;
+	List	   *options;		/* List of DefElem nodes */
+} AlterExtensionStmt;
+
 typedef struct AlterExtensionContentsStmt
 {
 	NodeTag		type;