]> granicus.if.org Git - postgresql/commitdiff
Add CASCADE support for CREATE EXTENSION.
authorAndres Freund <andres@anarazel.de>
Sat, 3 Oct 2015 16:19:37 +0000 (18:19 +0200)
committerAndres Freund <andres@anarazel.de>
Sat, 3 Oct 2015 16:23:40 +0000 (18:23 +0200)
Without CASCADE, if an extension has an unfullfilled dependency on
another extension, CREATE EXTENSION ERRORs out with "required extension
... is not installed". That is annoying, especially when that dependency
is an implementation detail of the extension, rather than something the
extension's user can make sense of.

In addition to CASCADE this also includes a small set of regression
tests around CREATE EXTENSION.

Author: Petr Jelinek, editorialized by Michael Paquier, Andres Freund
Reviewed-By: Michael Paquier, Andres Freund, Jeff Janes
Discussion: 557E0520.3040800@2ndquadrant.com

32 files changed:
contrib/hstore_plperl/expected/hstore_plperl.out
contrib/hstore_plperl/expected/hstore_plperlu.out
contrib/hstore_plperl/sql/hstore_plperl.sql
contrib/hstore_plperl/sql/hstore_plperlu.sql
contrib/hstore_plpython/expected/hstore_plpython.out
contrib/hstore_plpython/sql/hstore_plpython.sql
contrib/ltree_plpython/expected/ltree_plpython.out
contrib/ltree_plpython/sql/ltree_plpython.sql
doc/src/sgml/ref/create_extension.sgml
src/backend/commands/extension.c
src/backend/parser/gram.y
src/bin/psql/tab-complete.c
src/test/modules/Makefile
src/test/modules/test_extensions/.gitignore [new file with mode: 0644]
src/test/modules/test_extensions/Makefile [new file with mode: 0644]
src/test/modules/test_extensions/expected/test_extensions.out [new file with mode: 0644]
src/test/modules/test_extensions/sql/test_extensions.sql [new file with mode: 0644]
src/test/modules/test_extensions/test_ext1--1.0.sql [new file with mode: 0644]
src/test/modules/test_extensions/test_ext1.control [new file with mode: 0644]
src/test/modules/test_extensions/test_ext2--1.0.sql [new file with mode: 0644]
src/test/modules/test_extensions/test_ext2.control [new file with mode: 0644]
src/test/modules/test_extensions/test_ext3--1.0.sql [new file with mode: 0644]
src/test/modules/test_extensions/test_ext3.control [new file with mode: 0644]
src/test/modules/test_extensions/test_ext4--1.0.sql [new file with mode: 0644]
src/test/modules/test_extensions/test_ext4.control [new file with mode: 0644]
src/test/modules/test_extensions/test_ext5--1.0.sql [new file with mode: 0644]
src/test/modules/test_extensions/test_ext5.control [new file with mode: 0644]
src/test/modules/test_extensions/test_ext_cyclic1--1.0.sql [new file with mode: 0644]
src/test/modules/test_extensions/test_ext_cyclic1.control [new file with mode: 0644]
src/test/modules/test_extensions/test_ext_cyclic2--1.0.sql [new file with mode: 0644]
src/test/modules/test_extensions/test_ext_cyclic2.control [new file with mode: 0644]
src/tools/msvc/Mkvcbuild.pm

index cf384eba647bd941199ccf27b6fbbb229be0e794..25fc506c23f035b2e11f4f97d606617bd4cd968f 100644 (file)
@@ -1,6 +1,6 @@
-CREATE EXTENSION hstore;
-CREATE EXTENSION plperl;
-CREATE EXTENSION hstore_plperl;
+CREATE EXTENSION hstore_plperl CASCADE;
+NOTICE:  installing required extension "hstore"
+NOTICE:  installing required extension "plperl"
 SELECT transforms.udt_schema, transforms.udt_name,
        routine_schema, routine_name,
        group_name, transform_type
index c97fd3fae2d8c772e1a13321275a428502e2c25b..b09fb78af917bd3247c7e75f6e103416c33e70fa 100644 (file)
@@ -1,6 +1,6 @@
-CREATE EXTENSION hstore;
-CREATE EXTENSION plperlu;
-CREATE EXTENSION hstore_plperlu;
+CREATE EXTENSION hstore_plperlu CASCADE;
+NOTICE:  installing required extension "hstore"
+NOTICE:  installing required extension "plperlu"
 SELECT transforms.udt_schema, transforms.udt_name,
        routine_schema, routine_name,
        group_name, transform_type
index 0f70f149f500b9a3a34ec1d4fb16286a0ce0dd0e..9398aedfbbd5cd0178056c67bbf34546f1572f8e 100644 (file)
@@ -1,6 +1,4 @@
-CREATE EXTENSION hstore;
-CREATE EXTENSION plperl;
-CREATE EXTENSION hstore_plperl;
+CREATE EXTENSION hstore_plperl CASCADE;
 
 SELECT transforms.udt_schema, transforms.udt_name,
        routine_schema, routine_name,
index 3cfb2fdd77500a695c21b129f1604612cd4946b8..8d8508cf2940b76faa9f99f1bf771e01f08e3663 100644 (file)
@@ -1,6 +1,4 @@
-CREATE EXTENSION hstore;
-CREATE EXTENSION plperlu;
-CREATE EXTENSION hstore_plperlu;
+CREATE EXTENSION hstore_plperlu CASCADE;
 
 SELECT transforms.udt_schema, transforms.udt_name,
        routine_schema, routine_name,
index 23091d3729c7f78e25b0006d461544872245745d..b0025c04a81b409e8374f6b1f289899c7d912a75 100644 (file)
@@ -1,5 +1,5 @@
-CREATE EXTENSION plpython2u;
-CREATE EXTENSION hstore_plpython2u;
+CREATE EXTENSION hstore_plpython2u CASCADE;
+NOTICE:  installing required extension "plpython2u"
 -- test hstore -> python
 CREATE FUNCTION test1(val hstore) RETURNS int
 LANGUAGE plpythonu
index 9ff2ebcd833a54d16dd34366ac91c7b659265d4a..d55bedaf50569ee834dffb0bf926155f68479486 100644 (file)
@@ -1,5 +1,4 @@
-CREATE EXTENSION plpython2u;
-CREATE EXTENSION hstore_plpython2u;
+CREATE EXTENSION hstore_plpython2u CASCADE;
 
 
 -- test hstore -> python
index c6e8a7c087c34c1f7903bc80b0755c435b1fe03c..4779755fc80f64fe1bc8b475cc5540174855e284 100644 (file)
@@ -1,5 +1,5 @@
-CREATE EXTENSION plpython2u;
-CREATE EXTENSION ltree_plpython2u;
+CREATE EXTENSION ltree_plpython2u CASCADE;
+NOTICE:  installing required extension "plpython2u"
 CREATE FUNCTION test1(val ltree) RETURNS int
 LANGUAGE plpythonu
 TRANSFORM FOR TYPE ltree
index f08ff6a3f065ec3be016007f57a4655f396a76f1..210f5428a5a2772112d142e17c8eedf0101aeb09 100644 (file)
@@ -1,5 +1,4 @@
-CREATE EXTENSION plpython2u;
-CREATE EXTENSION ltree_plpython2u;
+CREATE EXTENSION ltree_plpython2u CASCADE;
 
 
 CREATE FUNCTION test1(val ltree) RETURNS int
index a1e7e4f812c0ca3eca233a77f98d90d7dec0c53b..d4cc310918e5ccd6b5d3a9bde465786ea05410ed 100644 (file)
@@ -25,6 +25,7 @@ CREATE EXTENSION [ IF NOT EXISTS ] <replaceable class="parameter">extension_name
     [ WITH ] [ SCHEMA <replaceable class="parameter">schema_name</replaceable> ]
              [ VERSION <replaceable class="parameter">version</replaceable> ]
              [ FROM <replaceable class="parameter">old_version</replaceable> ]
+             [ CASCADE ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -94,6 +95,35 @@ CREATE EXTENSION [ IF NOT EXISTS ] <replaceable class="parameter">extension_name
         If not specified, and the extension's control file does not specify a
         schema either, the current default object creation schema is used.
        </para>
+       <para>
+        If the extension specifies <literal>schema</> in its control file,
+        the schema cannot be overriden with <literal>SCHEMA</> clause.
+        The <literal>SCHEMA</> clause in this case works as follows:
+        <itemizedlist>
+         <listitem>
+          <para>
+           If <replaceable class="parameter">schema_name</replaceable> matches
+           the schema in control file, it will be used normally as there is no
+           conflict.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+           If the <literal>CASCADE</> clause is given, the
+           <replaceable class="parameter">schema_name</replaceable> will only
+           be used for the missing required extensions which do not specify
+           <literal>schema</> in their control files.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+           If <replaceable class="parameter">schema_name</replaceable> is not
+           the same as the one in extension's control file and the
+           <literal>CASCADE</> clause is not given, error will be thrown.
+          </para>
+         </listitem>
+        </itemizedlist>
+       </para>
        <para>
         Remember that the extension itself is not considered to be within any
         schema: extensions have unqualified names that must be unique
@@ -139,6 +169,18 @@ CREATE EXTENSION [ IF NOT EXISTS ] <replaceable class="parameter">extension_name
        </para>
       </listitem>
      </varlistentry>
+
+     <varlistentry>
+      <term><literal>CASCADE</></term>
+      <listitem>
+       <para>
+        Try to install extension including the required dependencies
+        recursively. The <literal>SCHEMA</> option will be propagated
+        to the required extensions.  Other options are not recursively
+        applied when using this clause.
+       </para>
+      </listitem>
+     </varlistentry>
   </variablelist>
  </refsect1>
 
index 6b92bdc5e0e821395604da976af2a0448e969b98..67b16a7a68d3d0a75e0eeda2da1db3769b3957bd 100644 (file)
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/comment.h"
+#include "commands/defrem.h"
 #include "commands/extension.h"
 #include "commands/schemacmds.h"
 #include "funcapi.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "storage/fd.h"
 #include "tcop/utility.h"
 #include "utils/builtins.h"
@@ -1165,18 +1167,25 @@ find_update_path(List *evi_list,
 }
 
 /*
- * CREATE EXTENSION
+ * CREATE EXTENSION worker
+ *
+ * When CASCADE is specified CreateExtensionInternal() recurses if required
+ * extensions need to be installed. To sanely handle cyclic dependencies
+ * cascade_parent contains the dependency chain leading to the current
+ * invocation; thus allowing to error out if there's a cyclic dependency.
  */
-ObjectAddress
-CreateExtension(CreateExtensionStmt *stmt)
+static ObjectAddress
+CreateExtensionInternal(CreateExtensionStmt *stmt, List *parents)
 {
        DefElem    *d_schema = NULL;
        DefElem    *d_new_version = NULL;
        DefElem    *d_old_version = NULL;
-       char       *schemaName;
-       Oid                     schemaOid;
+       DefElem    *d_cascade = NULL;
+       char       *schemaName = NULL;
+       Oid                     schemaOid = InvalidOid;
        char       *versionName;
        char       *oldVersionName;
+       bool            cascade = false;
        Oid                     extowner = GetUserId();
        ExtensionControlFile *pcontrol;
        ExtensionControlFile *control;
@@ -1187,41 +1196,6 @@ CreateExtension(CreateExtensionStmt *stmt)
        ListCell   *lc;
        ObjectAddress address;
 
-       /* 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
-        * in case of race conditions; but this is a friendlier error message, and
-        * besides we need a check to support IF NOT EXISTS.
-        */
-       if (get_extension_oid(stmt->extname, true) != InvalidOid)
-       {
-               if (stmt->if_not_exists)
-               {
-                       ereport(NOTICE,
-                                       (errcode(ERRCODE_DUPLICATE_OBJECT),
-                                        errmsg("extension \"%s\" already exists, skipping",
-                                                       stmt->extname)));
-                       return InvalidObjectAddress;
-               }
-               else
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_DUPLICATE_OBJECT),
-                                        errmsg("extension \"%s\" already exists",
-                                                       stmt->extname)));
-       }
-
-       /*
-        * We use global variables to track the extension being created, so we can
-        * create only one extension at the same time.
-        */
-       if (creating_extension)
-               ereport(ERROR,
-                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                errmsg("nested CREATE EXTENSION is not supported")));
-
        /*
         * 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
@@ -1260,6 +1234,15 @@ CreateExtension(CreateExtensionStmt *stmt)
                                                 errmsg("conflicting or redundant options")));
                        d_old_version = defel;
                }
+               else if (strcmp(defel->defname, "cascade") == 0)
+               {
+                       if (d_cascade)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("conflicting or redundant options")));
+                       d_cascade = defel;
+                       cascade = defGetBoolean(d_cascade);
+               }
                else
                        elog(ERROR, "unrecognized option: %s", defel->defname);
        }
@@ -1337,33 +1320,37 @@ CreateExtension(CreateExtensionStmt *stmt)
        {
                /*
                 * User given schema, CREATE EXTENSION ... WITH SCHEMA ...
-                *
-                * It's an error to give a schema different from control->schema if
-                * control->schema is specified.
                 */
                schemaName = strVal(d_schema->arg);
 
-               if (control->schema != NULL &&
-                       strcmp(control->schema, schemaName) != 0)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                               errmsg("extension \"%s\" must be installed in schema \"%s\"",
-                                          control->name,
-                                          control->schema)));
-
-               /* If the user is giving us the schema name, it must exist already */
+               /* If the user is giving us the schema name, it must exist already. */
                schemaOid = get_namespace_oid(schemaName, false);
        }
-       else if (control->schema != NULL)
+
+       if (control->schema != NULL)
        {
                /*
                 * The extension is not relocatable and the author gave us a schema
-                * for it.  We create the schema here if it does not already exist.
+                * for it.
+                *
+                * Unless CASCADE parameter was given, it's an error to give a schema
+                * different from control->schema if control->schema is specified.
                 */
+               if (schemaName && strcmp(control->schema, schemaName) != 0 &&
+                       !cascade)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                               errmsg("extension \"%s\" must be installed in schema \"%s\"",
+                                          control->name,
+                                          control->schema)));
+
+               /* Always use the schema from control file for current extension. */
                schemaName = control->schema;
+
+               /* Find or create the schema in case it does not exist. */
                schemaOid = get_namespace_oid(schemaName, true);
 
-               if (schemaOid == InvalidOid)
+               if (!OidIsValid(schemaOid))
                {
                        CreateSchemaStmt *csstmt = makeNode(CreateSchemaStmt);
 
@@ -1375,16 +1362,17 @@ CreateExtension(CreateExtensionStmt *stmt)
 
                        /*
                         * CreateSchemaCommand includes CommandCounterIncrement, so new
-                        * schema is now visible
+                        * schema is now visible.
                         */
                        schemaOid = get_namespace_oid(schemaName, false);
                }
        }
-       else
+       else if (!OidIsValid(schemaOid))
        {
                /*
-                * Else, use the current default creation namespace, which is the
-                * first explicit entry in the search_path.
+                * Neither user nor author of the extension specified schema, use the
+                * current default creation namespace, which is the first explicit
+                * entry in the search_path.
                 */
                List       *search_path = fetch_search_path(false);
 
@@ -1423,16 +1411,65 @@ CreateExtension(CreateExtensionStmt *stmt)
                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)));
+               {
+                       if (cascade)
+                       {
+                               CreateExtensionStmt *ces;
+                               ListCell   *lc;
+                               ObjectAddress addr;
+                               List *cascade_parents;
+
+                               /* Check extension name validity before trying to cascade */
+                               check_valid_extension_name(curreq);
+
+                               /* Check for cyclic dependency between extensions. */
+                               foreach(lc, parents)
+                               {
+                                       char       *pname = (char *) lfirst(lc);
+
+                                       if (strcmp(pname, curreq) == 0)
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_INVALID_RECURSION),
+                                                                errmsg("cyclic dependency detected between extensions \"%s\" and \"%s\"",
+                                                                               curreq, stmt->extname)));
+                               }
+
+                               ereport(NOTICE,
+                                               (errmsg("installing required extension \"%s\"",
+                                                               curreq)));
+
+                               /* Create and execute new CREATE EXTENSION statement. */
+                               ces = makeNode(CreateExtensionStmt);
+                               ces->extname = curreq;
+
+                               /* Propagate the CASCADE option */
+                               ces->options = list_make1(d_cascade);
+
+                               /* Propagate the SCHEMA option if given. */
+                               if (d_schema && d_schema->arg)
+                                       ces->options = lappend(ces->options, d_schema);
+
+                               /*
+                                * Pass the current list of parents + the current extension to
+                                * the "child" CreateExtensionInternal().
+                                */
+                               cascade_parents =
+                                       lappend(list_copy(parents), stmt->extname);
+
+                               /* Create the required extension. */
+                               addr = CreateExtensionInternal(ces, cascade_parents);
+                               reqext = addr.objectId;
+                       }
+                       else
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                                errmsg("required extension \"%s\" is not installed",
+                                                               curreq),
+                                                errhint("Use CREATE EXTENSION CASCADE to install required extensions too.")));
+               }
+
                reqschema = get_extension_schema(reqext);
                requiredExtensions = lappend_oid(requiredExtensions, reqext);
                requiredSchemas = lappend_oid(requiredSchemas, reqschema);
@@ -1473,6 +1510,52 @@ CreateExtension(CreateExtensionStmt *stmt)
        return address;
 }
 
+/*
+ * CREATE EXTENSION
+ */
+ObjectAddress
+CreateExtension(CreateExtensionStmt *stmt)
+{
+       /* 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
+        * in case of race conditions; but this is a friendlier error message, and
+        * besides we need a check to support IF NOT EXISTS.
+        */
+       if (get_extension_oid(stmt->extname, true) != InvalidOid)
+       {
+               if (stmt->if_not_exists)
+               {
+                       ereport(NOTICE,
+                                       (errcode(ERRCODE_DUPLICATE_OBJECT),
+                                        errmsg("extension \"%s\" already exists, skipping",
+                                                       stmt->extname)));
+                       return InvalidObjectAddress;
+               }
+               else
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DUPLICATE_OBJECT),
+                                        errmsg("extension \"%s\" already exists",
+                                                       stmt->extname)));
+       }
+
+       /*
+        * We use global variables to track the extension being created, so we can
+        * create only one extension at the same time.
+        */
+       if (creating_extension)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("nested CREATE EXTENSION is not supported")));
+
+
+       /* Finally create the extension. */
+       return CreateExtensionInternal(stmt, NIL);
+}
+
 /*
  * InsertExtensionTuple
  *
index fb84937b626042f379e04faaf5883cbb1bdc017f..417fb55bc4879e8681c99c2a49ae876459d88704 100644 (file)
@@ -3876,6 +3876,10 @@ create_extension_opt_item:
                                {
                                        $$ = makeDefElem("old_version", (Node *)makeString($2));
                                }
+                       | CASCADE
+                               {
+                                       $$ = makeDefElem("cascade", (Node *)makeInteger(TRUE));
+                               }
                ;
 
 /*****************************************************************************
index 4294ffd0520ebc4ca7bedfe6c74b28532b070ab9..1619de52aeadec5a71cc914329053409f7c53267 100644 (file)
@@ -2264,7 +2264,12 @@ psql_completion(const char *text, int start, int end)
        /* CREATE EXTENSION <name> */
        else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
                         pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
-               COMPLETE_WITH_CONST("WITH SCHEMA");
+       {
+               static const char *const list_CREATE_EXTENSION[] =
+               {"WITH SCHEMA", "CASCADE", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATE_EXTENSION);
+       }
 
        /* CREATE FOREIGN */
        else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
index 9b966542793c778d4e0d0a9087bc7ed8ac2b0323..6167ec13446bbf9883e6e93baa99fcfbb397ef0c 100644 (file)
@@ -9,6 +9,7 @@ SUBDIRS = \
                  commit_ts \
                  dummy_seclabel \
                  test_ddl_deparse \
+                 test_extensions \
                  test_parser \
                  test_rls_hooks \
                  test_shm_mq \
diff --git a/src/test/modules/test_extensions/.gitignore b/src/test/modules/test_extensions/.gitignore
new file mode 100644 (file)
index 0000000..5dcb3ff
--- /dev/null
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_extensions/Makefile b/src/test/modules/test_extensions/Makefile
new file mode 100644 (file)
index 0000000..5691357
--- /dev/null
@@ -0,0 +1,23 @@
+# src/test/modules/test_extensions/Makefile
+
+MODULE = test_extensions
+PGFILEDESC = "test_extensions - regression testing for EXTENSION support"
+
+EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 \
+                       test_ext_cyclic1 test_ext_cyclic2
+DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
+          test_ext4--1.0.sql test_ext5--1.0.sql test_ext_cyclic1--1.0.sql \
+          test_ext_cyclic2--1.0.sql
+
+REGRESS = test_extensions
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_extensions
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out
new file mode 100644 (file)
index 0000000..a57bb4b
--- /dev/null
@@ -0,0 +1,37 @@
+-- test some errors
+CREATE EXTENSION test_ext1;
+ERROR:  required extension "test_ext2" is not installed
+HINT:  Use CREATE EXTENSION CASCADE to install required extensions too.
+CREATE EXTENSION test_ext1 SCHEMA test_ext1;
+ERROR:  schema "test_ext1" does not exist
+CREATE EXTENSION test_ext1 SCHEMA test_ext;
+ERROR:  schema "test_ext" does not exist
+CREATE SCHEMA test_ext;
+CREATE EXTENSION test_ext1 SCHEMA test_ext;
+ERROR:  extension "test_ext1" must be installed in schema "test_ext1"
+-- finally success
+CREATE EXTENSION test_ext1 SCHEMA test_ext CASCADE;
+NOTICE:  installing required extension "test_ext2"
+NOTICE:  installing required extension "test_ext3"
+NOTICE:  installing required extension "test_ext5"
+NOTICE:  installing required extension "test_ext4"
+SELECT extname, nspname, extversion, extrelocatable FROM pg_extension e, pg_namespace n WHERE extname LIKE 'test_ext%' AND e.extnamespace = n.oid ORDER BY 1;
+  extname  |  nspname  | extversion | extrelocatable 
+-----------+-----------+------------+----------------
+ test_ext1 | test_ext1 | 1.0        | f
+ test_ext2 | test_ext  | 1.0        | t
+ test_ext3 | test_ext  | 1.0        | t
+ test_ext4 | test_ext  | 1.0        | t
+ test_ext5 | test_ext  | 1.0        | t
+(5 rows)
+
+CREATE EXTENSION test_ext_cyclic1 CASCADE;
+NOTICE:  installing required extension "test_ext_cyclic2"
+ERROR:  cyclic dependency detected between extensions "test_ext_cyclic1" and "test_ext_cyclic2"
+DROP SCHEMA test_ext CASCADE;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to extension test_ext3
+drop cascades to extension test_ext5
+drop cascades to extension test_ext2
+drop cascades to extension test_ext4
+drop cascades to extension test_ext1
diff --git a/src/test/modules/test_extensions/sql/test_extensions.sql b/src/test/modules/test_extensions/sql/test_extensions.sql
new file mode 100644 (file)
index 0000000..9076c02
--- /dev/null
@@ -0,0 +1,15 @@
+-- test some errors
+CREATE EXTENSION test_ext1;
+CREATE EXTENSION test_ext1 SCHEMA test_ext1;
+CREATE EXTENSION test_ext1 SCHEMA test_ext;
+CREATE SCHEMA test_ext;
+CREATE EXTENSION test_ext1 SCHEMA test_ext;
+
+-- finally success
+CREATE EXTENSION test_ext1 SCHEMA test_ext CASCADE;
+
+SELECT extname, nspname, extversion, extrelocatable FROM pg_extension e, pg_namespace n WHERE extname LIKE 'test_ext%' AND e.extnamespace = n.oid ORDER BY 1;
+
+CREATE EXTENSION test_ext_cyclic1 CASCADE;
+
+DROP SCHEMA test_ext CASCADE;
diff --git a/src/test/modules/test_extensions/test_ext1--1.0.sql b/src/test/modules/test_extensions/test_ext1--1.0.sql
new file mode 100644 (file)
index 0000000..9a4bb1b
--- /dev/null
@@ -0,0 +1,3 @@
+/* src/test/modules/test_extensions/test_ext1--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext1" to load this file. \quit
diff --git a/src/test/modules/test_extensions/test_ext1.control b/src/test/modules/test_extensions/test_ext1.control
new file mode 100644 (file)
index 0000000..9c069df
--- /dev/null
@@ -0,0 +1,5 @@
+comment = 'Test extension 1'
+default_version = '1.0'
+schema = 'test_ext1'
+relocatable = false
+requires = 'test_ext2,test_ext4'
diff --git a/src/test/modules/test_extensions/test_ext2--1.0.sql b/src/test/modules/test_extensions/test_ext2--1.0.sql
new file mode 100644 (file)
index 0000000..0f6d4ec
--- /dev/null
@@ -0,0 +1,3 @@
+/* src/test/modules/test_extensions/test_ext2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext2" to load this file. \quit
diff --git a/src/test/modules/test_extensions/test_ext2.control b/src/test/modules/test_extensions/test_ext2.control
new file mode 100644 (file)
index 0000000..946b7d5
--- /dev/null
@@ -0,0 +1,4 @@
+comment = 'Test extension 2'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext3,test_ext5'
diff --git a/src/test/modules/test_extensions/test_ext3--1.0.sql b/src/test/modules/test_extensions/test_ext3--1.0.sql
new file mode 100644 (file)
index 0000000..7dec684
--- /dev/null
@@ -0,0 +1,3 @@
+/* src/test/modules/test_extensions/test_ext3--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext3" to load this file. \quit
diff --git a/src/test/modules/test_extensions/test_ext3.control b/src/test/modules/test_extensions/test_ext3.control
new file mode 100644 (file)
index 0000000..5f1afe7
--- /dev/null
@@ -0,0 +1,3 @@
+comment = 'Test extension 3'
+default_version = '1.0'
+relocatable = true
diff --git a/src/test/modules/test_extensions/test_ext4--1.0.sql b/src/test/modules/test_extensions/test_ext4--1.0.sql
new file mode 100644 (file)
index 0000000..19f051f
--- /dev/null
@@ -0,0 +1,3 @@
+/* src/test/modules/test_extensions/test_ext4--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext4" to load this file. \quit
diff --git a/src/test/modules/test_extensions/test_ext4.control b/src/test/modules/test_extensions/test_ext4.control
new file mode 100644 (file)
index 0000000..fc62591
--- /dev/null
@@ -0,0 +1,4 @@
+comment = 'Test extension 4'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext5'
diff --git a/src/test/modules/test_extensions/test_ext5--1.0.sql b/src/test/modules/test_extensions/test_ext5--1.0.sql
new file mode 100644 (file)
index 0000000..baf6ef8
--- /dev/null
@@ -0,0 +1,3 @@
+/* src/test/modules/test_extensions/test_ext5--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext5" to load this file. \quit
diff --git a/src/test/modules/test_extensions/test_ext5.control b/src/test/modules/test_extensions/test_ext5.control
new file mode 100644 (file)
index 0000000..51bc57e
--- /dev/null
@@ -0,0 +1,3 @@
+comment = 'Test extension 5'
+default_version = '1.0'
+relocatable = true
diff --git a/src/test/modules/test_extensions/test_ext_cyclic1--1.0.sql b/src/test/modules/test_extensions/test_ext_cyclic1--1.0.sql
new file mode 100644 (file)
index 0000000..81bdaf4
--- /dev/null
@@ -0,0 +1,3 @@
+/* src/test/modules/test_extensions/test_ext_cyclic1--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_cyclic1" to load this file. \quit
diff --git a/src/test/modules/test_extensions/test_ext_cyclic1.control b/src/test/modules/test_extensions/test_ext_cyclic1.control
new file mode 100644 (file)
index 0000000..aaab403
--- /dev/null
@@ -0,0 +1,4 @@
+comment = 'Test extension cyclic 1'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_cyclic2'
diff --git a/src/test/modules/test_extensions/test_ext_cyclic2--1.0.sql b/src/test/modules/test_extensions/test_ext_cyclic2--1.0.sql
new file mode 100644 (file)
index 0000000..ae2b3e9
--- /dev/null
@@ -0,0 +1,3 @@
+/* src/test/modules/test_extensions/test_ext_cyclic2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_cyclic2" to load this file. \quit
diff --git a/src/test/modules/test_extensions/test_ext_cyclic2.control b/src/test/modules/test_extensions/test_ext_cyclic2.control
new file mode 100644 (file)
index 0000000..1e28f96
--- /dev/null
@@ -0,0 +1,4 @@
+comment = 'Test extension cyclic 2'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_cyclic1'
index 3abbb4caf4023de6c897500662233195b0f677c7..c122f0259c9c677b36d34d243cfa4c7d9d5344c7 100644 (file)
@@ -41,7 +41,8 @@ my $contrib_extrasource = {
        'seg'  => [ 'contrib/seg/segscan.l',   'contrib/seg/segparse.y' ], };
 my @contrib_excludes = (
        'commit_ts',      'hstore_plperl', 'hstore_plpython', 'intagg',
-       'ltree_plpython', 'pgcrypto',      'sepgsql',         'brin');
+       'ltree_plpython', 'pgcrypto',      'sepgsql',         'brin',
+       'test_extensions');
 
 # Set of variables for frontend modules
 my $frontend_defines = { 'initdb' => 'FRONTEND' };