>From 7d5db7faae0b30a70ceae020638fa7779810339d 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 RECURSIVE

---
 doc/src/sgml/ref/create_extension.sgml             | 11 ++++
 src/backend/commands/extension.c                   | 61 ++++++++++++++++++++--
 src/backend/parser/gram.y                          |  4 ++
 src/test/modules/test_extensions/.gitignore        |  3 ++
 src/test/modules/test_extensions/Makefile          | 22 ++++++++
 .../test_extensions/expected/test_extensions.out   | 16 ++++++
 .../test_extensions/sql/test_extensions.sql        | 11 ++++
 .../modules/test_extensions/test_ext1--1.0.sql     |  0
 src/test/modules/test_extensions/test_ext1.control |  4 ++
 .../modules/test_extensions/test_ext2--1.0.sql     |  0
 src/test/modules/test_extensions/test_ext2.control |  4 ++
 .../modules/test_extensions/test_ext3--1.0.sql     |  0
 src/test/modules/test_extensions/test_ext3.control |  3 ++
 .../test_extensions/test_ext_cyclic1--1.0.sql      |  0
 .../test_extensions/test_ext_cyclic1.control       |  4 ++
 .../test_extensions/test_ext_cyclic2--1.0.sql      |  0
 .../test_extensions/test_ext_cyclic2.control       |  4 ++
 17 files changed, 143 insertions(+), 4 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/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml
index a1e7e4f8..d151fd6 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> ]
+             [ RECURSIVE ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -139,6 +140,16 @@ CREATE EXTENSION [ IF NOT EXISTS ] <replaceable class="parameter">extension_name
        </para>
       </listitem>
      </varlistentry>
+
+     <varlistentry>
+      <term><literal>RECURSIVE</></term>
+      <listitem>
+       <para>
+        Try to install extension including the required dependencies
+        recursively.
+       </para>
+      </listitem>
+     </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 5cc74d0..4395519 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -46,6 +46,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"
@@ -1176,10 +1177,13 @@ CreateExtension(CreateExtensionStmt *stmt)
 	DefElem    *d_schema = NULL;
 	DefElem    *d_new_version = NULL;
 	DefElem    *d_old_version = NULL;
+	DefElem	   *d_recursive = NULL;
 	char	   *schemaName;
 	Oid			schemaOid;
 	char	   *versionName;
 	char	   *oldVersionName;
+	bool		recursive = false;
+	List	   *recursive_parents = NIL;
 	Oid			extowner = GetUserId();
 	ExtensionControlFile *pcontrol;
 	ExtensionControlFile *control;
@@ -1263,6 +1267,14 @@ CreateExtension(CreateExtensionStmt *stmt)
 						 errmsg("conflicting or redundant options")));
 			d_old_version = defel;
 		}
+		else if (strcmp(defel->defname, "recursive") == 0)
+		{
+			if (d_recursive)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			d_recursive = defel;
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1405,6 +1417,15 @@ CreateExtension(CreateExtensionStmt *stmt)
 		list_free(search_path);
 	}
 
+	/* Validate the RECURSIVE option. */
+	if (d_recursive)
+	{
+		recursive = true;
+		if (d_recursive->arg)
+			recursive_parents = (List *) d_recursive->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
@@ -1432,10 +1453,42 @@ 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 (recursive)
+			{
+				CreateExtensionStmt *ces;
+				List	   *parents;
+				ListCell   *lc;
+
+				/* Check for cyclic dependency between extension. */
+				foreach(lc, recursive_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\"",
+										pname, stmt->extname)));
+				}
+
+				/* Create and execute new CREATE EXTENSION statement. */
+				ces = makeNode(CreateExtensionStmt);
+				ces->extname = curreq;
+				ces->if_not_exists = false;
+				parents = lappend(list_copy(recursive_parents), stmt->extname);
+				ces->options = list_make1(makeDefElem("recursive",
+													  (Node *) parents));
+				CreateExtension(ces);
+				list_free(parents);
+			}
+			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 e0ff6f1..7eaf080 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3856,6 +3856,10 @@ create_extension_opt_item:
 				{
 					$$ = makeDefElem("old_version", (Node *)makeString($2));
 				}
+			| RECURSIVE
+				{
+					$$ = makeDefElem("recursive", NULL);
+				}
 		;
 
 /*****************************************************************************
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..ba90eea
--- /dev/null
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -0,0 +1,16 @@
+CREATE EXTENSION test_ext1;
+ERROR:  required extension "test_ext2" is not installed
+CREATE EXTENSION test_ext1 RECURSIVE;
+SELECT * FROM pg_extension WHERE extname LIKE 'test_ext%';
+  extname  | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition 
+-----------+----------+--------------+----------------+------------+-----------+--------------
+ test_ext3 |       10 |         2200 | t              | 1.0        |           | 
+ test_ext2 |       10 |         2200 | t              | 1.0        |           | 
+ test_ext1 |       10 |         2200 | t              | 1.0        |           | 
+(3 rows)
+
+CREATE EXTENSION test_ext_cyclic1 RECURSIVE;
+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;
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..7c0c542
--- /dev/null
+++ b/src/test/modules/test_extensions/sql/test_extensions.sql
@@ -0,0 +1,11 @@
+CREATE EXTENSION test_ext1;
+
+
+CREATE EXTENSION test_ext1 RECURSIVE;
+SELECT * FROM pg_extension WHERE extname LIKE 'test_ext%';
+
+CREATE EXTENSION test_ext_cyclic1 RECURSIVE;
+
+DROP EXTENSION test_ext1;
+DROP EXTENSION test_ext2;
+DROP EXTENSION test_ext3;
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..e69de29
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..6b1049b
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext1.control
@@ -0,0 +1,4 @@
+comment = 'Test extension 1'
+default_version = '1.0'
+relocatable = true
+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..e69de29
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..e69de29
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..e69de29
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..e69de29
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'
-- 
1.9.1

