PG提供了四种类型的函数
查询语言函数(用SQL编写的函数)
过程语言函数(用PLPGSQL、PLTCL等过程语言编写的)
内部函数
C语言函数
PostgreSQL允许用SQL和c之外的其他语言编写用户定义的函数。这些语言通常称为过程语言(PLS)。
下面我们就简单介绍一下如何利用CREATE FUNCTION 语法来创建一个新的函数。
通过执行CREATE FUNCTION 命令可以在服务器端创建一个函数。创建函数的语法在:PG创建函数之语法:部分。
简单用例:
CREATE FUNCTION somefunc(integer, text)RETURNS integerAS 'function body text'LANGUAGE plpgsql;
AS标识后面是一个完整的BLOCK块,可以被称为函数体块(我个人一般根据实现的语言来命名:如:PLPGSQL实现的可以成为PLPGSQL块),通常是由字符串构成。如果函数是由C语言编写的,AS则由C语言的文件和链接符构成。
下面三个例子中LANGUAGE分别指定了C语言和SQL语言以及PLPGSQL语言。当然你还可以定义其他的实现语言。可以看到SQL和PLPGSQL语言编写函数的AS后的块是类似的。



在CREATE FUNCTION时PG将AS后的函数体块分为两类,一类是C语言块,一类是非C语言块,图中的SQL和PLPGSQL属于非C语言类。
CreateFunction函数解析出的函数的信息包括:创建函数所使用的语言(PLPGSQL、SQL、C等),函数的参数信息(参数类型,参数名等),函数的返回值信息(返回类型等),函数的类型(函数或者存储过程),函数的属性信息(是否是窗口函数)等等。
填充系统表主要是将解析出来的信息存储到系统表pg_proc中。
在创建函数的过程中,AS后面的BLOCK块’function body text’不会被编译和执行,只有在调用的该函数的时候用才会被编译和执行它们。(当创建该函数的LANGUAGE中指定了languageValidator时,会对‘function body text’做一个简单的测试,例如:语法是否有简单的错误等)。最终函数解析出来的所有信息填充进pg_proc系统表中。
所以,CreateFunction简单的说:就是将创建函数所需要的各个信息解析出来填充系统表的过程。
CreateFunction(ParseState *pstate,CreateFunctionStmt *stmt)
因为在语法那节中讲过,stmt->funcname返回的是一个链表。该链表包括函数的名字空间和函数的名字,下面函数是将namespace和函数名称解析出来,并且返回该namespace在系统表中对应的OID信息。
namespaceId= QualifiedNameGetCreationNamespace(stmt->funcname, &funcname);
检查我们是否具有在解析出来的名字空间上具有创建权限
aclresult= pg_namespace_aclcheck(namespaceId, GetUserId(),ACL_CREATE);if(aclresult != ACLCHECK_OK)aclcheck_error(aclresult,OBJECT_SCHEMA,get_namespace_name(namespaceId));……
主要是解析出函数的各种属性,比如是用什么语言创建的该函数,是否是窗口函数,是否是严格函数,函数的块等信息,分别放到对应的变量中。
比如:
解析出该函数对应的LANGUAGE解析出来放到language变量中
AS后面的字符串解析出来,放到as_clause 对应的变量中。
判断是否是窗口函数,解析出来放到isWindowFunc变量中。
compute_function_attributes(pstate,stmt->is_procedure,stmt->options,&as_clause,&language, &transformDefElem,&isWindowFunc,&volatility,&isStrict,&security, &isLeakProof,&proconfig,&procost, &prorows,&prosupport,¶llel);
对上面解析出来的language做一些安全检查,language表示创建该函数所使用的语言。并且判断该语言是否是可信任的,可信的语言允许普通用户利用该语言进行函数和存储过程等的创建和使用。因为语言创建的对象是在服务器上运行的,所以,你要保证你所实现的对象不会影响服务器内核或者文件系统。否则的话,应该将该语言设置为不可信的。只能具有超户的权利才可以使用。例如:PL/PGSQL、PL/TCL、PL/PERL都认为是可信的。PL/TclU 、PL/PythonU等都是不可信的。
languageTuple= SearchSysCache1(LANGNAME, PointerGetDatum(language));languageOid = HeapTupleGetOid(languageTuple);languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);if (languageStruct->lanpltrusted){/* if trusted language, need USAGE privilege */AclResult aclresult;aclresult = pg_language_aclcheck(languageOid, GetUserId(),ACL_USAGE);if (aclresult != ACLCHECK_OK)aclcheck_error(aclresult, ACL_KIND_LANGUAGE,NameStr(languageStruct->lanname));}else{/* if untrusted language, must be superuser */if (!superuser())aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_LANGUAGE,NameStr(languageStruct->lanname));}
判断是否需要对函数的函数体进行检测,如果LanguageValidator不为空,则需要对As后面的BLOCK做一个检查。包括有效性和语法等。这部分是跟函数实现的具体语言相关的,在执行的时候数据库服务器没有关于如何解释函数源文本的内置知识。相反,任务被传递给一个知道语言细节的处理程序处理(该语言实现的处理程序)。
languageValidator = languageStruct->lanvalidator;……
解析函数的参数信息,包括函数的参数类型,参数名,默认值,模式IN,INOUT等
interpret_function_parameter_list(pstate,stmt->parameters,languageOid,stmt->is_procedure ?OBJECT_PROCEDURE :OBJECT_FUNCTION,¶meterTypes,&allParameterTypes,¶meterModes,¶meterNames,¶meterDefaults,&variadicArgType,&requiredResultType);……
解析AS后BLOCK块的信息,并填充相应变量,因为BLOCK块可以用C语言编写,也可以用非C语言编写,下面操作主要根据创建该函数的语言来确定如何填充相应的变量。
例如:
如果是C语言实现该函数,则AS后面是 AS <object file name> [, <link symbol name> ]这种情况下,probin_str保存<对象名称>,prosrc_str保存链接名称
如果是非C语言实现该函数,则AS后面是AS <object reference, or sql code>将AS后的字符串保存在prosrc_str中
interpret_AS_clause(languageOid,language, funcname, as_clause,&prosrc_str, &probin_str);……
最终ProcedureCreate函数根据我们解析出来的所有信息构建系统表信息,填充系统表,更新相应的依赖等,如果需要检查BLOCK块的有效性,则在该函数中调用相应的检查函数进行检查。
ProcedureCreate(funcname,namespaceId,stmt->replace,returnsSet,prorettype,GetUserId(),languageOid,languageValidator,prosrc_str,/*converted to text later */probin_str,/*converted to text later */stmt->is_procedure ? PROKIND_PROCEDURE :(isWindowFunc ? PROKIND_WINDOW : PROKIND_FUNCTION),security,isLeakProof,isStrict,volatility,parallel,parameterTypes,PointerGetDatum(allParameterTypes),PointerGetDatum(parameterModes),PointerGetDatum(parameterNames),parameterDefaults,PointerGetDatum(trftypes),PointerGetDatum(proconfig),prosupport,procost,prorows);}
所以:
简单的说,创建函数的过程,主要是根据传入的字符串,检查函数的创建语法的正确性,并检查创建函数传入信息的正确性,然后做一些安全检查后将传入信息填充到系统表的过程。




