diff --git a/contrib/hstore_plperl/expected/hstore_plperl.out b/contrib/hstore_plperl/expected/hstore_plperl.out
index cf384eba647bd941199ccf27b6fbbb229be0e794..25fc506c23f035b2e11f4f97d606617bd4cd968f 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 c97fd3fae2d8c772e1a13321275a428502e2c25b..b09fb78af917bd3247c7e75f6e103416c33e70fa 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 0f70f149f500b9a3a34ec1d4fb16286a0ce0dd0e..9398aedfbbd5cd0178056c67bbf34546f1572f8e 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 3cfb2fdd77500a695c21b129f1604612cd4946b8..8d8508cf2940b76faa9f99f1bf771e01f08e3663 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 23091d3729c7f78e25b0006d461544872245745d..b0025c04a81b409e8374f6b1f289899c7d912a75 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 9ff2ebcd833a54d16dd34366ac91c7b659265d4a..d55bedaf50569ee834dffb0bf926155f68479486 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 c6e8a7c087c34c1f7903bc80b0755c435b1fe03c..4779755fc80f64fe1bc8b475cc5540174855e284 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 f08ff6a3f065ec3be016007f57a4655f396a76f1..210f5428a5a2772112d142e17c8eedf0101aeb09 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 a1e7e4f812c0ca3eca233a77f98d90d7dec0c53b..d4cc310918e5ccd6b5d3a9bde465786ea05410ed 100644
--- a/doc/src/sgml/ref/create_extension.sgml
+++ b/doc/src/sgml/ref/create_extension.sgml
@@ -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>
 
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 6b92bdc5e0e821395604da976af2a0448e969b98..67b16a7a68d3d0a75e0eeda2da1db3769b3957bd 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -43,11 +43,13 @@
 #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
  *
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index fb84937b626042f379e04faaf5883cbb1bdc017f..417fb55bc4879e8681c99c2a49ae876459d88704 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3876,6 +3876,10 @@ create_extension_opt_item:
 				{
 					$$ = makeDefElem("old_version", (Node *)makeString($2));
 				}
+			| CASCADE
+				{
+					$$ = makeDefElem("cascade", (Node *)makeInteger(TRUE));
+				}
 		;
 
 /*****************************************************************************
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 4294ffd0520ebc4ca7bedfe6c74b28532b070ab9..1619de52aeadec5a71cc914329053409f7c53267 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -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 &&
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 9b966542793c778d4e0d0a9087bc7ed8ac2b0323..6167ec13446bbf9883e6e93baa99fcfbb397ef0c 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -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
index 0000000000000000000000000000000000000000..5dcb3ff9723501c3fe639bee1c1435e47a580a6f
--- /dev/null
+++ b/src/test/modules/test_extensions/.gitignore
@@ -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
index 0000000000000000000000000000000000000000..5691357c070682a481afe0d65d1b6082e221c610
--- /dev/null
+++ b/src/test/modules/test_extensions/Makefile
@@ -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
index 0000000000000000000000000000000000000000..a57bb4b7ff014c90e4f041380b6c2d060e78d3ec
--- /dev/null
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -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
index 0000000000000000000000000000000000000000..9076c02807cfe11dda6553fcfd34b4bcbac4705d
--- /dev/null
+++ b/src/test/modules/test_extensions/sql/test_extensions.sql
@@ -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
index 0000000000000000000000000000000000000000..9a4bb1be789193150cf897ad822b3e1bf8497ce6
--- /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 0000000000000000000000000000000000000000..9c069dfcaf8fb8b84b2e897396d27710ed3e1643
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext1.control
@@ -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
index 0000000000000000000000000000000000000000..0f6d4ec2fb491000b6d890545300b2c758492680
--- /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 0000000000000000000000000000000000000000..946b7d58febe37176d4ed855067662346f89d682
--- /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,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
index 0000000000000000000000000000000000000000..7dec684dcb55a097f5d1ec3aa857825d7389b07c
--- /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 0000000000000000000000000000000000000000..5f1afe72fc3b6e71739090dea8dc7917e668ae97
--- /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_ext4--1.0.sql b/src/test/modules/test_extensions/test_ext4--1.0.sql
new file mode 100644
index 0000000000000000000000000000000000000000..19f051f6f7874422f2706b862cbf2a676db519bf
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext4--1.0.sql
@@ -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
index 0000000000000000000000000000000000000000..fc6259195099250e23329ca02cbd36213bf533bf
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext4.control
@@ -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
index 0000000000000000000000000000000000000000..baf6ef85dd6ae89450663e9faf0ad567ca35b107
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext5--1.0.sql
@@ -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
index 0000000000000000000000000000000000000000..51bc57efeaa7b4b011b39142f4a5f1f634194541
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext5.control
@@ -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
index 0000000000000000000000000000000000000000..81bdaf48d2139e28cef5c15eca5052002b8cc0d8
--- /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 0000000000000000000000000000000000000000..aaab4034636842f55f43c99ef681fce48f3c40e2
--- /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 0000000000000000000000000000000000000000..ae2b3e9a9676e32739d68fda7c9a12957e244028
--- /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 0000000000000000000000000000000000000000..1e28f96391c771fd30ec9307ac8b0c649dbd258d
--- /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 3abbb4caf4023de6c897500662233195b0f677c7..c122f0259c9c677b36d34d243cfa4c7d9d5344c7 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -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' };