]> granicus.if.org Git - postgresql/commitdiff
Add support for multiple versions of an extension and ALTER EXTENSION UPDATE.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 12 Feb 2011 02:25:20 +0000 (21:25 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 12 Feb 2011 02:25:57 +0000 (21:25 -0500)
This follows recent discussions, so it's quite a bit different from
Dimitri's original.  There will probably be more changes once we get a bit
of experience with it, but let's get it in and start playing with it.

This is still just core code.  I'll start converting contrib modules
shortly.

Dimitri Fontaine and Tom Lane

16 files changed:
contrib/pg_upgrade_support/pg_upgrade_support.c
doc/src/sgml/catalogs.sgml
doc/src/sgml/extend.sgml
doc/src/sgml/ref/alter_extension.sgml
doc/src/sgml/ref/create_extension.sgml
src/backend/commands/extension.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/parser/gram.y
src/backend/tcop/utility.c
src/bin/pg_dump/pg_dump.c
src/bin/psql/tab-complete.c
src/include/catalog/pg_extension.h
src/include/commands/extension.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h

index 8b0e474dce51a5d51e89e3b15e3fb36ab41504d3..02d1512719ec2a7f11e938145b144df39be03f0f 100644 (file)
@@ -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);
index 24aa22cbced804c05df94a65aada91c6e69f4bf2..a373829d39d740adc6bca594e1ba301e80f3be16 100644 (file)
       <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>
index 31ea0487f1e52202d704da7a5ef20b9a39c39a7c..93bcba9a10cfb299074747eca8052e9451f1f05e 100644 (file)
    </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
     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
      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>
 
     <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>
        <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>
       <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>
     </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
index e9eb1aafbb6f3552a096f3c872bcf0d3f1ad7f3f..a6c0062fe240905c4f5d010aad656c3a5725f49a 100644 (file)
@@ -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;
index 961cab3839e0ee2d9a4292e4f95cf34a03469e27..9e0e3c440b6d7b11461a095372c53cdbb327f0ef 100644 (file)
@@ -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>
index bc121808bec094299bdd17faf752f6db018bd6b1..5d8b36b0966962048221380e5b6f5bfd788c0e1c 100644 (file)
 #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
  */
index 46acaf8d701abc6be9b8683f30a3f04d066f596c..57d580208102c116323ffd9ff74dcba21fcd3ecc 100644 (file)
@@ -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;
index 2fbe99937d2e49d901058e43118a978d99201ffc..f57cf99ba2c1f473cbef55c7c46f78765ceb1ae3 100644 (file)
@@ -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;
index 82ff9accc72761c83afa5b6d7e6fc9cb2e1d8e9a..a99f8c6ca248168362834f2198f4475ca15e791a 100644 (file)
@@ -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));
                                }
                ;
 
index c942de3bf62cb7b47ab26c1e2b62ff78d808945a..8ca042024f34ef3408f80d430f864915c8b007c1 100644 (file)
@@ -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;
index 5561c7a6876baa5ff110a6b9bc9429e11679d4db..83c7157b2e8b99ed95f630415a3b81a3c6a5ee29 100644 (file)
@@ -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
index 2c656068f827db45bdb04d0792d79dcdff2072b3..a31281e431cbfeedf4d79fae9339c6559f7e60f5 100644 (file)
@@ -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);
        }
index 0ad47b01a4b0a8595b6d8cec13797074f5b5d228..63a1a0711f927125442d25815ab84add5dcf9152 100644 (file)
@@ -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;
index 7c94449a6cb0f5e853fe70bd79ae7ce0c87b5069..c6e69d5fd4216a0b3965b5f1df7251472d25bb2e 100644 (file)
@@ -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);
index 15bf0631e44c50fb2a94014fd02edf997adcb2b7..e0d05748dafb4d5c223bcc4cb2c17891b43f1d6e 100644 (file)
@@ -356,6 +356,7 @@ typedef enum NodeTag
        T_SecLabelStmt,
        T_CreateForeignTableStmt,
        T_CreateExtensionStmt,
+       T_AlterExtensionStmt,
        T_AlterExtensionContentsStmt,
 
        /*
index b54f0cfe02f7bb5d47d4c37171d12e0543cdbe1e..1aa3e913b5484446b8377d03cde779d98c4fde0d 100644 (file)
@@ -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;