CREATE [ OR REPLACE ] FUNCTION name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } default_expr ] [, ...] ] ) [ RETURNS rettype | RETURNS TABLE ( column_name column_type [, ...] ) ] { LANGUAGE lang_name | TRANSFORM { FOR TYPE type_name } [, ... ] | WINDOW | IMMUTABLE | STABLE | VOLATILE | [ NOT ] LEAKPROOF | CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT | [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER | PARALLEL { UNSAFE | RESTRICTED | SAFE } | COST execution_cost | ROWS result_rows | SET configuration_parameter { TO value | = value | FROM CURRENT } | AS 'definition' | AS 'obj_file', 'link_symbol' } ... [ WITH ( attribute [, ...] ) ]
CREATE FUNCTION定义一个新函数。CREATE OR REPLACE FUNCTION将创建一个新函数或者替换一个现有的函数。要定义一个函数,用户必须具有该语言上的USAGE特权。
如果包括了一个模式名,那么该函数会被创建在指定的模式中。否则,它会被创建在当前模式中。新函数的名称不能匹配同一个模式中具有相同输入参数类型的任何现有函数。不过,不同参数类型的函数能够共享一个名字(这被称作重载)。
要替换一个现有函数的当前定义,可以使用CREATE OR REPLACE FUNCTION。但不能用这种方式更改函数的名称或者参数类型(如果尝试这样做,实际上就会创建一个新的不同的函数)。还有,CREATE OR REPLACE FUNCTION将不会让你更改一个现有函数的返回类型。要这样做,你必须先删除再重建该函数(在使用OUT参数时,这意味着除了删除函数之外无法更改任何OUT参数的类型)。
当CREATE OR REPLACE FUNCTION被用来替换一个现有的函数,该函数的拥有权和权限不会改变。所有其他的函数属性会按照该命令中所指定的或者隐含的来赋值。必须拥有(包括成为拥有角色的成员)该函数才能替换它。
如果你删除并且重建一个函数,新函数将和旧的不一样,你将必须删掉引用旧函数的现有规则、视图、触发器等。使用CREATE OR REPLACE FUNCTION更改一个函数定义不会破坏引用该函数的对象。还有,ALTER FUNCTION可以被用来更改一个现有函数的大部分辅助属性。
创建该函数的用户将成为该函数的拥有者。
要创建一个函数,你必须拥有参数类型和返回类型上的USAGE特权。
要创建的函数的名称(可以被模式限定)。
一个参数的模式:IN、OUT、INOUT或者VARIADIC。如果省略,默认为IN。只有OUT参数能跟在一个VARIADIC参数后面。还有,OUT和INOUT参数不能和RETURNS TABLE符号一起使用。
一个参数的名称。一些语言(包括 SQL 和 PL/pgSQL)让你在函数体中使用该名称。对于其他语言,一个输入参数的名字只是额外的文字(就该函数本身所关心的来说)。但是你可以在调用一个函数时使用输入参数名来提高可读性(见第 4.3 节)。在任何情况下,输出参数的名称是有意义的,因为它定义了结果行类型中的列名(如果忽略一个输出参数的名称,系统将选择一个默认的列名)。
该函数参数(如果有)的数据类型(可以是模式限定的)。参数类型可以是基本类型、组合类型或者域类型,或者可以引用一个表列的类型。
根据实现语言,也可以允许指定cstring之类的"伪类型"。伪类型表示实际参数类型没有被完整指定或者不属于普通 SQL 数据类型集合。
可以写table_name.column_name%TYPE来引用一列的类型。使用这种特性有时可以帮助创建一个不受表定义更改影响的函数。
如果参数没有被指定值时要用作默认值的表达式。该表达式必须能被强制为该参数的参数类型。只有输入(包括INOUT)参数可以具有默认值。所有跟随在一个具有默认值的参数之后的输入参数也必须有默认值。
返回数据类型(可能被模式限定)。返回类型可以是一种基本类型、组合类型或者域类型,也可以引用一个表列的类型。根据实现语言,也可以允许指定cstring之类的"伪类型"。如果该函数不会返回一个值,可以指定返回类型为void。
当有OUT或者INOUT参数时,可以省略RETURNS子句。如果存在,该子句必须和输出参数所表示的结果类型一致:如果有多个输出参数,则为RECORD,否则与单个输出参数的类型相同。
SETOF修饰符表示该函数将返回一个项的集合而不是一个单一项。
可以写table_name.column_name%TYPE来引用一列的类型。
RETURNS TABLE语法中一个输出列的名称。这实际上是另一种声明OUT参数的方法,不过RETURNS TABLE也隐含了RETURNS SETOF。
RETURNS TABLE语法中的输出列的数据类型。
用以实现该函数的语言的名称。可以是sql、c、internal或者一个用户定义的过程语言的名称,例如plpgsql。不推荐用单引号包围该名称,并且要求区分大小写。
一个由转换构成的列表,对该函数的调用适用于它们。转换在 SQL 类型和语言相关的数据类型之间进行变换,详见CREATE TRANSFORM。过程语言实现通常把有关内建类型的知识硬编码在代码中,因此那些不需要列举在这里。如果一种过程语言实现不知道如何处理一种类型并且没有转换被提供,它将回退到一种默认的行为来转换数据类型,但是这取决于具体实现。
WINDOW表示该函数是一个窗口函数而不是一个普通函数。当前只用于用 C 编写的函数。在替换一个现有函数定义时,不能更改WINDOW属性。
这些属性告知查询优化器该函数的行为。最多只能指定其中一个。如果这些都不出现,则会默认为VOLATILE。
IMMUTABLE表示该函数不能修改数据库并且对于给定的参数值总是会返回相同的值。也就是说,它不会做数据库查找或者使用没有在其参数列表中直接出现的信息。如果给定合格选项,任何用全常量参数对该函数的额调用可以立刻用该函数值替换。
STABLE表示该函数不能修改数据库,并且对于相同的参数值,它在一次表扫描中将返回相同的结果。但是这种结果在不同的 SQL 语句执行期间可能会变化。对于那些结果依赖于数据库查找、参数变量(例如当前时区)等的函数来说,这是合适的(对希望查询被当前命令修改的行的AFTER触发器不适合)。还要注意current_timestamp
函数族适合被标记为稳定,因为它们的值在一个事务内不会改变。
VOLATILE表示该函数的值在一次表扫描中都有可能改变,因此不能做优化。在这种意义上,相对较少的数据库函数是不稳定的,一些例子是random()、currval()、timeofday()。但是注意任何有副作用的函数都必须被分类为不稳定的,即便其结果是可以预测的,这是为了调用被优化掉。一个例子是setval()。
更多细节可见第 36.6 节。
LEAKPROOF表示该函数没有副作用。它不会泄露有关其参数的信息(除了通过返回值)。例如,一个只对某些参数值抛出错误消息而对另外一些却不抛出错误的函数不是防泄漏的,一个把参数值包括在任何错误消息中的函数也不是防泄漏的。这会影响系统如何执行在使用security_barrier选项创建的视图或者开启了行级安全性的表上执行查询。对于包含有非防泄漏函数的查询,系统将在任何来自查询本身的用户提供条件之前强制来自安全策略或者安全屏障的条件,防止无意中的数据暴露。被标记为防泄漏的函数和操作符被假定是可信的,并且可以在安全性策略和安全性屏障视图的条件之前被执行。此外,没有参数的函数或者不从安全屏障视图或表传递任何参数的函数不一定要被标记为防泄漏的。详见CREATE VIEW和第 39.5 节。这个选项只能由超级用户设置。
CALLED ON NULL INPUT(默认)表示在某些参数为空值时应正常调用该函数。如果有必要,函数的作者应该负责检查空值并且做出适当的相应。
RETURNS NULL ON NULL INPUT或STRICT表示只要其任意参数为空值,该函数就会返回空值。如果指定了这个参数,当有空值参数时该函数不会被执行,而是自动返回一个空值结果。
SECURITY INVOKER表示要用调用该函数的用户的特权来执行它。这是默认值。SECURITY DEFINER指定要用创建函数的用户的特权来执行该函数。
为了符合 SQL,允许使用关键词EXTERNAL。但是它是可选的,因为与 SQL 中不同,这个特性适用于所有函数而不仅是那些外部函数。
PARALLEL UNSAFE表示该函数不能在并行模式中运行并且 SQL 语句中存在一个这样的函数会强制使用顺序执行计划。这是默认选项。PARALLEL RESTRICTED表示该函数能在并行模式中运行,但是其执行被限制在并行组的领导者中。PARALLEL SAFE表示该函数对于在并行模式中运行是安全的并且不受限制。
如果函数修改任何数据库状态、会使用子事务之类的方式改变事务、访问序列或者对设置(如setval)做出持久性的更改,它们就应该被标记为并行不安全。如果它们访问临时表、客户端连接状态、游标、预备语句或者系统无法在并行模式中同步的本地后端状态(例如setseed只能在组领导者中执行,因为另一个进程所作的更改不会在领导者中被反映出来),它们应该被标为并行受限。通常,如果一个函数是受限的或者不安全的却被标成了安全,或者它本来是不安全的却被标成了受限,在并行查询中执行时它可能会抛出错误或者产生错误的答案。如果被错误的标记, C 语言函数理论上可能展现出完全无法定义的行为,因为系统没有办法保护自己不受任意的 C 代码影响,但是在大部分情况下其结果也不会比任何其他函数差到哪里去。如果有疑问,函数应该被标为UNSAFE,这也是默认值。
一个给出该函数的估计执行代价的正数,单位是cpu_operator_cost。如果该函数返回一个集合,这就是每个被返回行的代价。如果没有指定代价,对 C 语言和内部函数会指定为 1 个单位,对其他语言的函数则会指定为 100 单位。更大的值会导致规划器尝试避免对该函数的不必要的过多计算。
一个正数,它给出规划器期望该函数返回的行数估计。只有当该函数被声明为返回一个集合时才允许这个参数。默认假设为 1000 行。
SET子句导致进入该函数时指定配置参数将被设置为指定值。并且在该函数退出时恢复到该参数之前的值。SET FROM CURRENT会把CREATE FUNCTION被执行时该参数的当前值保存为进入该函数时将被应用的值。
如果一个SET子句被附加到一个函数,那么在该函数内为同一个变量执行的SET LOCAL命令会被限制于该函数:在函数退出时该配置参数之前的值仍会被恢复。不过,一个普通的SET命令(没有LOCAL)会覆盖SET子句,更像一个之前的SET LOCAL命令所做的那样:这种命令的效果在函数退出后将会持续,除非当前事务被回滚。
一个定义该函数的字符串常量,其含义取决于语言。它可以是一个内部函数名、一个对象文件的路径、一个 SQL 命令或者用一种过程语言编写的文本。
美元引用第 4.1.2.4 节通常对书写函数定义字符串有所帮助,而普通单引号语法则不会有用。如果没有美元引用,函数定义中的任何单引号或者反斜线必须用双写来转义。
当 C 语言源代码中该函数的名称与 SQL 函数的名称不同时,这种形式的AS子句被用于动态可载入 C 语言函数。字符串obj_file是包含动态可载入对象的文件名,并且link_symbol是该函数的链接符号,也就是该函数在 C 语言源代码中的名称。如果省略链接符号,它将被假定为要定义的 SQL 函数的名称。
在重复调用引用同一对象文件的CREATE FUNCTION时,对每个会话该文件只会被载入一次。要卸载并且重新装载该文件(可能是在开发期间),需要开始一个新会话。
历史遗留的指定有关该函数可选信息的方法。可以出现下列属性:
等效于STRICT或者RETURNS NULL ON NULL INPUT。
isCachable是IMMUTABLE的一个已被废弃的等效体。为了向后兼容的原因,它仍被接受。
属性名大小写无关。
编写函数的进一步信息可以参考第 36.3 节。
PostgreSQL允许函数重载,也就是说同一个名称可以被用于多个不同的函数,只要它们具有可区分的输入参数类型。不过,所有函数的 C 名称必须不同,因此你必须给被重载的 C 函数指定不同的 C 名称(例如,把参数类型作为 C 名称的一部分)。
如果两个函数具有相同的名称和输入参数类型,它们被认为相同(不考虑任何OUT参数)。因此这些声明会冲突:
CREATE FUNCTION foo(int) ... CREATE FUNCTION foo(int, out text) ...
具有不同参数类型列表的函数在创建时将不会被认为是冲突的,但是如果默认值被提供,在使用时它们有可能会冲突。例如,考虑
CREATE FUNCTION foo(int) ... CREATE FUNCTION foo(int, int default 42) ...
调用foo(10)将会失败,因为在要决定应该调用哪个函数时会有歧义。
允许把完整的SQL类型语法用于声明一个函数的参数和返回值。不过,CREATE FUNCTION会抛弃带圆括号的类型修饰符(例如类型numeric的精度域)。例如CREATE FUNCTION foo (varchar(10)) ...和CREATE FUNCTION foo (varchar) ...完全一样。
在用CREATE OR REPLACE FUNCTION替换一个现有函数时,对于更改参数名是有限制的。不能更改已经分配给任何输入参数的名称(不过可以给之前没有名称的参数增加名称)。如果有多于一个输出参数,不能更改输出参数的名称,因为可能会改变描述函数结果的匿名组合类型的列名。这些限制是为了确保函数被替换时,已有的对该函数的调用不会停止工作。
如果一个被声明为STRICT的函数带有一个VARIADIC参数,会严格检查该可变数组作为一个整体是否为非空。如果该数组有空值元素,该函数仍将被调用。
这里是一些小例子,它们可以帮你了解函数创建。更多信息和例子可见 第 36.3 节。
CREATE FUNCTION add(integer, integer) RETURNS integer AS 'select $1 + $2;' LANGUAGE SQL IMMUTABLE RETURNS NULL ON NULL INPUT;
在PL/pgSQL中,使用一个参数名称增加一个整数:
CREATE OR REPLACE FUNCTION increment(i integer) RETURNS integer AS $$ BEGIN RETURN i + 1; END; $$ LANGUAGE plpgsql;
返回一个包含多个输出参数的记录:
CREATE FUNCTION dup(in int, out f1 int, out f2 text) AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$ LANGUAGE SQL; SELECT * FROM dup(42);
你可以用更复杂的方式(用一个显式命名的组合类型)来做同样的事情:
CREATE TYPE dup_result AS (f1 int, f2 text); CREATE FUNCTION dup(int) RETURNS dup_result AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$ LANGUAGE SQL; SELECT * FROM dup(42);
另一种返回多列的方法是使用一个TABLE函数:
CREATE FUNCTION dup(int) RETURNS TABLE(f1 int, f2 text) AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$ LANGUAGE SQL; SELECT * FROM dup(42);
不过,TABLE函数与之前的例子不同,因为它实际返回了一个记录集合而不只是一个记录。
因为一个SECURITY DEFINER函数会被以创建它的用户的特权来执行,需要小心地确保该函数不会被误用。为了安全,search_path应该被设置为排除任何不可信用户可写的模式。这可以阻止恶意用户创建对象(例如表、函数以及操作符)来掩饰该函数所要用到的对象。在这方面特别重要的是临时表模式,默认情况下它会第一个被搜索并且通常对任何用户都是可写的。可以通过强制最后搜索临时模式来得到一种安全的布局。要这样做,把pg_temp写成search_path中的最后一项。这个函数展示了安全的用法:
CREATE FUNCTION check_password(uname TEXT, pass TEXT) RETURNS BOOLEAN AS $$ DECLARE passed BOOLEAN; BEGIN SELECT (pwd = $2) INTO passed FROM pwds WHERE username = $1; RETURN passed; END; $$ LANGUAGE plpgsql SECURITY DEFINER -- 设置一个安全的 search_path:受信的模式,然后是 'pg_temp'。 SET search_path = admin, pg_temp;
这个函数的目的是为了访问表admin.pwds。但是如果没有SET子句或者带有SET子句却只提到admin,该函数会变成创建一个名为pwds的临时表。
在PostgreSQL 版本 8.3 之前,SET子句不可用,因而较老的函数可能包含相当复杂的逻辑来保存、设置以及恢复search_path。对于这种目的,SET子句更容易。
另一点要记住的是默认情况下,会为新创建的函数给PUBLIC授予执行特权(详见GRANT)。你常常会希望把安全定义器函数的使用限制在某些用户中。要这样做,你必须收回默认的PUBLIC特权,然后选择性地授予执行特权。为了避免出现新函数能被所有人访问的时间窗口,应在一个事务中创建它并且设置特权。例如:
BEGIN; CREATE FUNCTION check_password(uname TEXT, pass TEXT) ... SECURITY DEFINER; REVOKE ALL ON FUNCTION check_password(uname TEXT, pass TEXT) FROM PUBLIC; GRANT EXECUTE ON FUNCTION check_password(uname TEXT, pass TEXT) TO admins; COMMIT;