From 0599a547173c4781c3edc447af4e1adc06e52973 Mon Sep 17 00:00:00 2001
From: erthalion <9erthalion6@gmail.com>
Date: Fri, 15 Sep 2017 21:33:07 +0200
Subject: [PATCH 5/5] Function depends on

---
 doc/src/sgml/ref/create_function.sgml           |  12 +++
 src/backend/commands/functioncmds.c             | 119 ++++++++++++++++++------
 src/backend/parser/gram.y                       |  23 ++++-
 src/test/regress/expected/create_function_3.out |  15 +++
 src/test/regress/sql/create_function_3.sql      |  12 +++
 5 files changed, 151 insertions(+), 30 deletions(-)

diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index 072e033687..7996a02322 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -27,6 +27,7 @@ CREATE [ OR REPLACE ] FUNCTION
   { LANGUAGE <replaceable class="parameter">lang_name</replaceable>
     | TRANSFORM { FOR TYPE <replaceable class="parameter">type_name</replaceable> } [, ... ]
     | WINDOW
+    | DEPENDS ON <replaceable class="parameter">func_name</replaceable> [, ... ]
     | IMMUTABLE | STABLE | VOLATILE | [ NOT ] LEAKPROOF
     | CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
     | [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
@@ -293,6 +294,17 @@ CREATE [ OR REPLACE ] FUNCTION
     </varlistentry>
 
     <varlistentry>
+     <term><literal>DEPENDS ON</literal></term>
+
+     <listitem>
+      <para><literal>DEPENDS ON</literal> indicates that the function depends
+      on other functions. This is useful when you want to prevent this
+      function to be invalid because some of its dependencies were deleted.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
      <term><literal>IMMUTABLE</literal></term>
      <term><literal>STABLE</literal></term>
      <term><literal>VOLATILE</literal></term>
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 7de844b2ca..dff9d59822 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -64,6 +64,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/regproc.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
 
@@ -608,6 +609,7 @@ compute_attributes_sql_style(ParseState *pstate,
 							 char **language,
 							 Node **transform,
 							 bool *windowfunc_p,
+							 Node **depends_on,
 							 char *volatility_p,
 							 bool *strict_p,
 							 bool *security_definer,
@@ -622,6 +624,7 @@ compute_attributes_sql_style(ParseState *pstate,
 	DefElem    *language_item = NULL;
 	DefElem    *transform_item = NULL;
 	DefElem    *windowfunc_item = NULL;
+	DefElem    *depends_item = NULL;
 	DefElem    *volatility_item = NULL;
 	DefElem    *strict_item = NULL;
 	DefElem    *security_item = NULL;
@@ -685,6 +688,15 @@ compute_attributes_sql_style(ParseState *pstate,
 			/* recognized common option */
 			continue;
 		}
+		else if (strcmp(defel->defname, "depends") == 0)
+		{
+			if (depends_item)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options"),
+						 parser_errposition(pstate, defel->location)));
+			depends_item = defel;
+		}
 		else
 			elog(ERROR, "option \"%s\" not recognized",
 				 defel->defname);
@@ -716,6 +728,8 @@ compute_attributes_sql_style(ParseState *pstate,
 		*transform = transform_item->arg;
 	if (windowfunc_item)
 		*windowfunc_p = intVal(windowfunc_item->arg);
+	if (depends_item)
+		*depends_on = depends_item->arg;
 	if (volatility_item)
 		*volatility_p = interpret_func_volatility(volatility_item);
 	if (strict_item)
@@ -868,6 +882,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 	Oid			languageOid;
 	Oid			languageValidator;
 	Node	   *transformDefElem = NULL;
+	Node	   *dependsOnDefElem = NULL;
 	char	   *funcname;
 	Oid			namespaceId;
 	AclResult	aclresult;
@@ -876,6 +891,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 	ArrayType  *parameterModes;
 	ArrayType  *parameterNames;
 	List	   *parameterDefaults;
+	List	   *dependencies = NIL;
 	Oid			variadicArgType;
 	List	   *trftypes_list = NIL;
 	ArrayType  *trftypes;
@@ -892,6 +908,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 	Form_pg_language languageStruct;
 	List	   *as_clause;
 	char		parallel;
+	ObjectAddress result;
+	ListCell   *lc;
 
 	/* Convert list of names to a name and namespace */
 	namespaceId = QualifiedNameGetCreationNamespace(stmt->funcname,
@@ -918,7 +936,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 	compute_attributes_sql_style(pstate,
 								 stmt->options,
 								 &as_clause, &language, &transformDefElem,
-								 &isWindowFunc, &volatility,
+								 &isWindowFunc, &dependsOnDefElem, &volatility,
 								 &isStrict, &security, &isLeakProof,
 								 &proconfig, &procost, &prorows, &parallel);
 
@@ -983,6 +1001,33 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 		}
 	}
 
+	if (dependsOnDefElem)
+	{
+		ListCell   *lc;
+
+		foreach(lc, castNode(List, dependsOnDefElem))
+		{
+			DefElem    *defel = (DefElem *) lfirst(lc);
+			Value	   *func = (Value *) defel->arg;
+			List	   *names;
+			FuncCandidateList clist;
+
+			/*
+			 * Parse the name into components and see if it matches any pg_proc
+			 * entries in the current search path.
+			 */
+			names = stringToQualifiedNameList(strVal(func));
+			clist = FuncnameGetCandidates(names, -1, NIL, false, false, true);
+
+			if (clist == NULL || clist->next != NULL)
+				ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_FUNCTION),
+				 errmsg("Function %s does not exist", strVal(func))));
+
+			dependencies = lappend_oid(dependencies, clist->oid);
+		}
+	}
+
 	/*
 	 * Convert remaining parameters of CREATE to form wanted by
 	 * ProcedureCreate.
@@ -1080,32 +1125,52 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 	 * And now that we have all the parameters, and know we're permitted to do
 	 * so, go ahead and create the function.
 	 */
-	return ProcedureCreate(funcname,
-						   namespaceId,
-						   stmt->replace,
-						   returnsSet,
-						   prorettype,
-						   GetUserId(),
-						   languageOid,
-						   languageValidator,
-						   prosrc_str,	/* converted to text later */
-						   probin_str,	/* converted to text later */
-						   false,	/* not an aggregate */
-						   isWindowFunc,
-						   security,
-						   isLeakProof,
-						   isStrict,
-						   volatility,
-						   parallel,
-						   parameterTypes,
-						   PointerGetDatum(allParameterTypes),
-						   PointerGetDatum(parameterModes),
-						   PointerGetDatum(parameterNames),
-						   parameterDefaults,
-						   PointerGetDatum(trftypes),
-						   PointerGetDatum(proconfig),
-						   procost,
-						   prorows);
+	result = ProcedureCreate(funcname,
+													 namespaceId,
+													 stmt->replace,
+													 returnsSet,
+													 prorettype,
+													 GetUserId(),
+													 languageOid,
+													 languageValidator,
+													 prosrc_str,	/* converted to text later */
+													 probin_str,	/* converted to text later */
+													 false,	/* not an aggregate */
+													 isWindowFunc,
+													 security,
+													 isLeakProof,
+													 isStrict,
+													 volatility,
+													 parallel,
+													 parameterTypes,
+													 PointerGetDatum(allParameterTypes),
+													 PointerGetDatum(parameterModes),
+													 PointerGetDatum(parameterNames),
+													 parameterDefaults,
+													 PointerGetDatum(trftypes),
+													 PointerGetDatum(proconfig),
+													 procost,
+													 prorows);
+
+
+	/*
+	 * Dependencies already verified, so for every dependent function
+	 * we can create an internal dependency record to prevent them
+	 * from being deleted.
+	 */
+	foreach(lc, dependencies)
+	{
+		ObjectAddress dependencyAddress;
+
+		dependencyAddress.classId = ProcedureRelationId;
+		dependencyAddress.objectId = lfirst_oid(lc);
+		/* no subitems, since it's a function */
+		dependencyAddress.objectSubId = 0;
+
+		recordDependencyOn(&dependencyAddress, &result, DEPENDENCY_INTERNAL);
+	}
+
+	return result;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5eb398118e..411cce0855 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -395,7 +395,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				TriggerTransitions TriggerReferencing
-				publication_name_list
+				publication_name_list depends_on_func_list
+
+%type <defelt>	func_elem
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -7697,6 +7699,10 @@ createfunc_opt_item:
 				{
 					$$ = makeDefElem("window", (Node *)makeInteger(TRUE), @1);
 				}
+			| DEPENDS depends_on_func_list
+				{
+					$$ = makeDefElem("depends", (Node *)$2, @1);
+				}
 			| common_func_opt_item
 				{
 					$$ = $1;
@@ -7711,8 +7717,19 @@ func_as:	Sconst						{ $$ = list_make1(makeString($1)); }
 		;
 
 transform_type_list:
-			FOR TYPE_P Typename { $$ = list_make1($3); }
-			| transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
+											 FOR TYPE_P Typename { $$ = list_make1($3); }
+											 | transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
+							 ;
+
+depends_on_func_list:	ON func_elem													 { $$ = list_make1($2); }
+											 | depends_on_func_list ',' func_elem   { $$ = lappend($1, $3); }
+							 ;
+
+func_elem:
+			type_function_name
+				{
+					$$ = makeDefElem("func_name", (Node *)makeString($1), @1);
+				}
 		;
 
 opt_definition:
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index b5e19485e5..15e0022ae7 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -248,3 +248,18 @@ drop cascades to function functext_f_4(integer)
 drop cascades to function functest_b_2(bigint)
 DROP USER regress_unpriv_user;
 RESET search_path;
+-- Depends on
+CREATE FUNCTION dependency_func1() RETURNS INTEGER AS 'SELECT 1;' LANGUAGE SQL;
+CREATE FUNCTION dependency_func2() RETURNS INTEGER AS 'SELECT 1;' LANGUAGE SQL;
+CREATE FUNCTION dependency_func3() RETURNS INTEGER AS 'SELECT 1;' LANGUAGE SQL;
+CREATE FUNCTION complex_func1() RETURNS INTEGER AS 'SELECT 1;' LANGUAGE SQL DEPENDS ON dependency_func1;
+CREATE FUNCTION complex_func2() RETURNS INTEGER AS 'SELECT 1;' LANGUAGE SQL DEPENDS ON dependency_func2, dependency_func3;
+DROP FUNCTION dependency_func1;
+ERROR:  cannot drop function dependency_func1() because function complex_func1() requires it
+HINT:  You can drop function complex_func1() instead.
+DROP FUNCTION dependency_func2;
+ERROR:  cannot drop function dependency_func2() because function complex_func2() requires it
+HINT:  You can drop function complex_func2() instead.
+DROP FUNCTION dependency_func3;
+ERROR:  cannot drop function dependency_func3() because function complex_func2() requires it
+HINT:  You can drop function complex_func2() instead.
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index 0a0e407aab..363cefe62e 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -171,3 +171,15 @@ DROP FUNCTION functest_b_2;  -- error, ambiguous
 DROP SCHEMA temp_func_test CASCADE;
 DROP USER regress_unpriv_user;
 RESET search_path;
+
+
+-- Depends on
+CREATE FUNCTION dependency_func1() RETURNS INTEGER AS 'SELECT 1;' LANGUAGE SQL;
+CREATE FUNCTION dependency_func2() RETURNS INTEGER AS 'SELECT 1;' LANGUAGE SQL;
+CREATE FUNCTION dependency_func3() RETURNS INTEGER AS 'SELECT 1;' LANGUAGE SQL;
+CREATE FUNCTION complex_func1() RETURNS INTEGER AS 'SELECT 1;' LANGUAGE SQL DEPENDS ON dependency_func1;
+CREATE FUNCTION complex_func2() RETURNS INTEGER AS 'SELECT 1;' LANGUAGE SQL DEPENDS ON dependency_func2, dependency_func3;
+
+DROP FUNCTION dependency_func1;
+DROP FUNCTION dependency_func2;
+DROP FUNCTION dependency_func3;
-- 
2.13.0

