From 54529b7e8bf2e5554569369ff976befbac981a25 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Sat, 13 Jun 2015 19:49:10 +0200
Subject: [PATCH] CREATE EXTENSION CASCADE

---
 contrib/hstore_plperl/expected/hstore_plperl.out   |   6 +-
 contrib/hstore_plperl/expected/hstore_plperlu.out  |   6 +-
 contrib/hstore_plperl/sql/hstore_plperl.sql        |   4 +-
 contrib/hstore_plperl/sql/hstore_plperlu.sql       |   4 +-
 .../hstore_plpython/expected/hstore_plpython.out   |   4 +-
 contrib/hstore_plpython/sql/hstore_plpython.sql    |   3 +-
 contrib/ltree_plpython/expected/ltree_plpython.out |   4 +-
 contrib/ltree_plpython/sql/ltree_plpython.sql      |   3 +-
 doc/src/sgml/ref/create_extension.sgml             |  22 ++++-
 src/backend/commands/extension.c                   | 103 ++++++++++++++++++++-
 src/backend/parser/gram.y                          |   8 ++
 src/bin/psql/tab-complete.c                        |   7 +-
 src/test/modules/Makefile                          |   1 +
 src/test/modules/test_extensions/.gitignore        |   3 +
 src/test/modules/test_extensions/Makefile          |  22 +++++
 .../test_extensions/expected/test_extensions.out   |  24 +++++
 .../test_extensions/sql/test_extensions.sql        |  15 +++
 .../modules/test_extensions/test_ext1--1.0.sql     |   3 +
 src/test/modules/test_extensions/test_ext1.control |   4 +
 .../modules/test_extensions/test_ext2--1.0.sql     |   3 +
 src/test/modules/test_extensions/test_ext2.control |   4 +
 .../modules/test_extensions/test_ext3--1.0.sql     |   3 +
 src/test/modules/test_extensions/test_ext3.control |   3 +
 .../test_extensions/test_ext_cyclic1--1.0.sql      |   3 +
 .../test_extensions/test_ext_cyclic1.control       |   4 +
 .../test_extensions/test_ext_cyclic2--1.0.sql      |   3 +
 .../test_extensions/test_ext_cyclic2.control       |   4 +
 src/tools/msvc/Mkvcbuild.pm                        |   2 +-
 28 files changed, 247 insertions(+), 28 deletions(-)
 create mode 100644 src/test/modules/test_extensions/.gitignore
 create mode 100644 src/test/modules/test_extensions/Makefile
 create mode 100644 src/test/modules/test_extensions/expected/test_extensions.out
 create mode 100644 src/test/modules/test_extensions/sql/test_extensions.sql
 create mode 100644 src/test/modules/test_extensions/test_ext1--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext1.control
 create mode 100644 src/test/modules/test_extensions/test_ext2--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext2.control
 create mode 100644 src/test/modules/test_extensions/test_ext3--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext3.control
 create mode 100644 src/test/modules/test_extensions/test_ext_cyclic1--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_cyclic1.control
 create mode 100644 src/test/modules/test_extensions/test_ext_cyclic2--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_cyclic2.control

diff --git a/contrib/hstore_plperl/expected/hstore_plperl.out b/contrib/hstore_plperl/expected/hstore_plperl.out
index cf384eb..25fc506 100644
--- a/contrib/hstore_plperl/expected/hstore_plperl.out
+++ b/contrib/hstore_plperl/expected/hstore_plperl.out
@@ -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
diff --git a/contrib/hstore_plperl/expected/hstore_plperlu.out b/contrib/hstore_plperl/expected/hstore_plperlu.out
index 8c689ad..3fc3201 100644
--- a/contrib/hstore_plperl/expected/hstore_plperlu.out
+++ b/contrib/hstore_plperl/expected/hstore_plperlu.out
@@ -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
diff --git a/contrib/hstore_plperl/sql/hstore_plperl.sql b/contrib/hstore_plperl/sql/hstore_plperl.sql
index 0f70f14..9398aed 100644
--- a/contrib/hstore_plperl/sql/hstore_plperl.sql
+++ b/contrib/hstore_plperl/sql/hstore_plperl.sql
@@ -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,
diff --git a/contrib/hstore_plperl/sql/hstore_plperlu.sql b/contrib/hstore_plperl/sql/hstore_plperlu.sql
index 3cfb2fd..8d8508c 100644
--- a/contrib/hstore_plperl/sql/hstore_plperlu.sql
+++ b/contrib/hstore_plperl/sql/hstore_plperlu.sql
@@ -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,
diff --git a/contrib/hstore_plpython/expected/hstore_plpython.out b/contrib/hstore_plpython/expected/hstore_plpython.out
index b7a6a92..55f4efe 100644
--- a/contrib/hstore_plpython/expected/hstore_plpython.out
+++ b/contrib/hstore_plpython/expected/hstore_plpython.out
@@ -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
diff --git a/contrib/hstore_plpython/sql/hstore_plpython.sql b/contrib/hstore_plpython/sql/hstore_plpython.sql
index 9ff2ebc..d55beda 100644
--- a/contrib/hstore_plpython/sql/hstore_plpython.sql
+++ b/contrib/hstore_plpython/sql/hstore_plpython.sql
@@ -1,5 +1,4 @@
-CREATE EXTENSION plpython2u;
-CREATE EXTENSION hstore_plpython2u;
+CREATE EXTENSION hstore_plpython2u CASCADE;
 
 
 -- test hstore -> python
diff --git a/contrib/ltree_plpython/expected/ltree_plpython.out b/contrib/ltree_plpython/expected/ltree_plpython.out
index 934529e..9bee6be 100644
--- a/contrib/ltree_plpython/expected/ltree_plpython.out
+++ b/contrib/ltree_plpython/expected/ltree_plpython.out
@@ -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
diff --git a/contrib/ltree_plpython/sql/ltree_plpython.sql b/contrib/ltree_plpython/sql/ltree_plpython.sql
index f08ff6a..210f542 100644
--- a/contrib/ltree_plpython/sql/ltree_plpython.sql
+++ b/contrib/ltree_plpython/sql/ltree_plpython.sql
@@ -1,5 +1,4 @@
-CREATE EXTENSION plpython2u;
-CREATE EXTENSION ltree_plpython2u;
+CREATE EXTENSION ltree_plpython2u CASCADE;
 
 
 CREATE FUNCTION test1(val ltree) RETURNS int
diff --git a/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml
index a1e7e4f8..3d63c3e 100644
--- a/doc/src/sgml/ref/create_extension.sgml
+++ b/doc/src/sgml/ref/create_extension.sgml
@@ -22,9 +22,10 @@ PostgreSQL documentation
  <refsynopsisdiv>
 <synopsis>
 CREATE EXTENSION [ IF NOT EXISTS ] <replaceable class="parameter">extension_name</replaceable>
-    [ WITH ] [ SCHEMA <replaceable class="parameter">schema_name</replaceable> ]
+    [ WITH ] [ [ DEFAULT ] SCHEMA <replaceable class="parameter">schema_name</replaceable> ]
              [ VERSION <replaceable class="parameter">version</replaceable> ]
              [ FROM <replaceable class="parameter">old_version</replaceable> ]
+             [ CASCADE ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -95,6 +96,14 @@ CREATE EXTENSION [ IF NOT EXISTS ] <replaceable class="parameter">extension_name
         schema either, the current default object creation schema is used.
        </para>
        <para>
+        In case the extension specifies schema in its control file, the schema
+        can't be overriden using <literal>SCHEMA</> clause. If the optional
+        <literal>DEFAULT</> parameter is specified, the
+        <replaceable class="parameter">schema_name</replaceable> will only be
+        used for extensions which do not specify their schema in their control
+        file.
+       </para>
+       <para>
         Remember that the extension itself is not considered to be within any
         schema: extensions have unqualified names that must be unique
         database-wide.  But objects belonging to the extension can be within
@@ -139,6 +148,17 @@ 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>DEFAULT SCHEMA</> option will be propagated
+        to the required extensions.
+       </para>
+      </listitem>
+     </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 2b1dcd0..bb5bb37 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -48,6 +48,7 @@
 #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"
@@ -1173,10 +1174,14 @@ CreateExtension(CreateExtensionStmt *stmt)
 	DefElem    *d_schema = NULL;
 	DefElem    *d_new_version = NULL;
 	DefElem    *d_old_version = NULL;
+	DefElem	   *d_cascade = NULL;
+	DefElem    *d_default_schema = NULL;
 	char	   *schemaName;
 	Oid			schemaOid;
 	char	   *versionName;
 	char	   *oldVersionName;
+	bool		cascade = false;
+	List	   *cascade_parents = NIL;
 	Oid			extowner = GetUserId();
 	ExtensionControlFile *pcontrol;
 	ExtensionControlFile *control;
@@ -1238,7 +1243,7 @@ CreateExtension(CreateExtensionStmt *stmt)
 
 		if (strcmp(defel->defname, "schema") == 0)
 		{
-			if (d_schema)
+			if (d_schema || d_default_schema)
 				ereport(ERROR,
 						(errcode(ERRCODE_SYNTAX_ERROR),
 						 errmsg("conflicting or redundant options")));
@@ -1260,6 +1265,22 @@ 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;
+		}
+		else if (strcmp(defel->defname, "default_schema") == 0)
+		{
+			if (d_default_schema || d_schema)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			d_default_schema = defel;
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1380,6 +1401,19 @@ CreateExtension(CreateExtensionStmt *stmt)
 			schemaOid = get_namespace_oid(schemaName, false);
 		}
 	}
+	else if (d_default_schema && d_default_schema->arg)
+	{
+		/*
+		 * User given default schema,
+		 * CREATE EXTENSION ... WITH DEFAULT SCHEMA ...
+		 *
+		 * It only affects relocatable extensions.
+		 */
+		schemaName = strVal(d_default_schema->arg);
+
+		/* If the user is giving us the schema name, it must exist already */
+		schemaOid = get_namespace_oid(schemaName, false);
+	}
 	else
 	{
 		/*
@@ -1402,6 +1436,15 @@ CreateExtension(CreateExtensionStmt *stmt)
 		list_free(search_path);
 	}
 
+	/* Handle the CASCADE option. */
+	if (d_cascade)
+	{
+		cascade = true;
+		if (d_cascade->arg)
+			cascade_parents = (List *) d_cascade->arg;
+	}
+
+
 	/*
 	 * We don't check creation rights on the target namespace here.  If the
 	 * extension script actually creates any objects there, it will fail if
@@ -1429,10 +1472,60 @@ CreateExtension(CreateExtensionStmt *stmt)
 		 */
 		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;
+
+				ereport(NOTICE,
+						(errmsg("installing required extension \"%s\"",
+								curreq)));
+
+				/* Check for cyclic dependency between extensions. */
+				foreach(lc, cascade_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)));
+				}
+
+				/* Create and execute new CREATE EXTENSION statement. */
+				ces = makeNode(CreateExtensionStmt);
+				ces->extname = curreq;
+				ces->if_not_exists = false;
+
+				/*
+				 * Propagate the CASCADE option and add current extension
+				 * to the list for checking the cyclic dependencies.
+				 */
+				cascade_parents = lappend(cascade_parents, stmt->extname);
+				ces->options = list_make1(makeDefElem("cascade",
+													  (Node *) cascade_parents));
+
+				/* Propagate the DEFAULT SCHEMA option if given. */
+				if (d_default_schema && d_default_schema->arg)
+					ces->options = lappend(ces->options, d_default_schema);
+
+				addr = CreateExtension(ces);
+				reqext = addr.objectId;
+
+				/* We leak the DeflElems but probably not worth worrying about */
+				list_free(ces->options);
+				pfree(ces);
+			}
+			else
+				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);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2b02a2e..9da4b7d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3849,6 +3849,10 @@ create_extension_opt_item:
 				{
 					$$ = makeDefElem("schema", (Node *)makeString($2));
 				}
+			| DEFAULT SCHEMA name
+				{
+					$$ = makeDefElem("default_schema", (Node *)makeString($3));
+				}
 			| VERSION_P NonReservedWord_or_Sconst
 				{
 					$$ = makeDefElem("new_version", (Node *)makeString($2));
@@ -3857,6 +3861,10 @@ create_extension_opt_item:
 				{
 					$$ = makeDefElem("old_version", (Node *)makeString($2));
 				}
+			| CASCADE
+				{
+					$$ = makeDefElem("cascade", NULL);
+				}
 		;
 
 /*****************************************************************************
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0683548..ef54216 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2232,7 +2232,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", "WITH DEFAULT SCHEMA", "CASCADE", NULL};
+
+		COMPLETE_WITH_LIST(list_CREATE_EXTENSION);
+	}
 
 	/* CREATE FOREIGN */
 	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 8213e23..84b4423 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -8,6 +8,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
index 0000000..c08326f
--- /dev/null
+++ b/src/test/modules/test_extensions/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_extensions/Makefile b/src/test/modules/test_extensions/Makefile
new file mode 100644
index 0000000..32f6aab
--- /dev/null
+++ b/src/test/modules/test_extensions/Makefile
@@ -0,0 +1,22 @@
+# 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_ext_cyclic1 test_ext_cyclic2
+DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
+	   test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql
+
+# test_ddl_deparse must be first
+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
index 0000000..f4a5ba1
--- /dev/null
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -0,0 +1,24 @@
+CREATE EXTENSION test_ext1;
+ERROR:  required extension "test_ext2" is not installed
+CREATE SCHEMA test_ext;
+CREATE EXTENSION test_ext1 CASCADE SCHEMA test_ext;
+ERROR:  extension "test_ext1" must be installed in schema "test_ext1"
+CREATE EXTENSION test_ext1 CASCADE DEFAULT SCHEMA test_ext;
+NOTICE:  installing required extension "test_ext2"
+NOTICE:  installing required extension "test_ext3"
+SELECT extname, nspname, extversion, extrelocatable FROM pg_extension e, pg_namespace n WHERE extname LIKE 'test_ext%' AND e.extnamespace = n.oid;
+  extname  |  nspname  | extversion | extrelocatable 
+-----------+-----------+------------+----------------
+ test_ext2 | test_ext  | 1.0        | t
+ test_ext3 | test_ext  | 1.0        | t
+ test_ext1 | test_ext1 | 1.0        | f
+(3 rows)
+
+CREATE EXTENSION test_ext_cyclic1 CASCADE;
+NOTICE:  installing required extension "test_ext_cyclic2"
+NOTICE:  installing required extension "test_ext_cyclic1"
+ERROR:  cyclic dependency detected between extensions "test_ext_cyclic1" and "test_ext_cyclic2"
+DROP EXTENSION test_ext1;
+DROP EXTENSION test_ext2;
+DROP EXTENSION test_ext3;
+DROP SCHEMA test_ext;
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
index 0000000..23a1991
--- /dev/null
+++ b/src/test/modules/test_extensions/sql/test_extensions.sql
@@ -0,0 +1,15 @@
+CREATE EXTENSION test_ext1;
+
+CREATE SCHEMA test_ext;
+
+CREATE EXTENSION test_ext1 CASCADE SCHEMA test_ext;
+CREATE EXTENSION test_ext1 CASCADE DEFAULT SCHEMA test_ext;
+
+SELECT extname, nspname, extversion, extrelocatable FROM pg_extension e, pg_namespace n WHERE extname LIKE 'test_ext%' AND e.extnamespace = n.oid;
+
+CREATE EXTENSION test_ext_cyclic1 CASCADE;
+
+DROP EXTENSION test_ext1;
+DROP EXTENSION test_ext2;
+DROP EXTENSION test_ext3;
+DROP SCHEMA test_ext;
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
index 0000000..9a4bb1b
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext1--1.0.sql
@@ -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
index 0000000..c7ef323
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext1.control
@@ -0,0 +1,4 @@
+comment = 'Test extension 1'
+default_version = '1.0'
+schema = 'test_ext1'
+requires = 'test_ext2'
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
index 0000000..0f6d4ec
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext2--1.0.sql
@@ -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
index 0000000..788337e
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext2.control
@@ -0,0 +1,4 @@
+comment = 'Test extension 2'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext3'
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
index 0000000..7dec684
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext3--1.0.sql
@@ -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
index 0000000..5f1afe7
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext3.control
@@ -0,0 +1,3 @@
+comment = 'Test extension 3'
+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
index 0000000..81bdaf4
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cyclic1--1.0.sql
@@ -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
index 0000000..aaab403
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cyclic1.control
@@ -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
index 0000000..ae2b3e9
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cyclic2--1.0.sql
@@ -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
index 0000000..1e28f96
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cyclic2.control
@@ -0,0 +1,4 @@
+comment = 'Test extension cyclic 2'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_cyclic1'
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index d70db40..9b3e31e 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -41,7 +41,7 @@ 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');
+	'ltree_plpython', 'pgcrypto',      'sepgsql',         'test_extensions');
 
 # Set of variables for frontend modules
 my $frontend_defines = { 'initdb' => 'FRONTEND' };
-- 
1.9.1

