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 lang_name | TRANSFORM { FOR TYPE type_name } [, ... ] | WINDOW + | DEPENDS ON func_name [, ... ] | 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 + DEPENDS ON + + + DEPENDS ON 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. + + + + + IMMUTABLE STABLE VOLATILE 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, ¶llel); @@ -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 func_elem %type group_by_list %type 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