CREATE FUNCTION

CREATE FUNCTION — 定义一个新函数

Synopsis

CREATE [ OR REPLACE ] [ EDITIONABLE | NONEDITIONABLE ] FUNCTION
    name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } default_expr ] [, ...] ] )
    [ RETURNS rettype
      | RETURNS TABLE ( column_name column_type [, ...] ) ]
  { LANGUAGE lang_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
    | SUPPORT support_function
    | SET configuration_parameter { TO value | = value | FROM CURRENT }
    | AS 'definition'
    | AS 'obj_file', 'link_symbol'
  } ...

描述

CREATE FUNCTION定义一个新函数。CREATE OR REPLACE FUNCTION将创建一个新函数或者替换一个现有的函数。要定义一个函数,用户必须具有该语言上的USAGE特权。

如果包括了一个模式名,那么该函数会被创建在指定的模式中。否则,它会被创建在当前模式中。新函数的名称不能匹配同一个模式中具有相同输入参数类型的任何现有函数或过程。不过,不同参数类型的函数和过程能够共享一个名字(这被称作重载)。

要替换一个现有函数的当前定义,可以使用CREATE OR REPLACE FUNCTION。但不能用这种方式更改函数的名称或者参数类型(如果尝试这样做,实际上就会创建一个新的不同的函数)。还有,CREATE OR REPLACE FUNCTION允许让你更改一个现有函数的返回类型,前提是这个函数不被依赖。

CREATE OR REPLACE FUNCTION被用来替换一个现有的函数,该函数的拥有权和权限不会改变。所有其他的函数属性会按照该命令中所指定的或者隐含的来赋值。必须拥有(包括成为拥有角色的成员)该函数才能替换它。

如果你删除并且重建一个函数,新函数将和旧的不一样,你将必须删掉引用旧函数的现有规则、视图、触发器等。使用CREATE OR REPLACE FUNCTION更改一个函数定义不会破坏引用该函数的对象。还有,ALTER FUNCTION可以被用来更改一个现有函数的大部分辅助属性。

创建该函数的用户将成为该函数的拥有者。

要创建一个函数,你必须拥有参数类型和返回类型上的USAGE特权。

有关编写函数的详细信息,请参阅Section 38.3

参数

EDITIONABLE
NONEDITIONABLE

如果为 schema 中的架构对象 FUNCTION 启用了版本控制,则使用这两个属性可以指定函数是受版本控制的对象还是非受版本控制的对象。

目前只支持语法,未实现功能。

该用法仅支持在oracle兼容模式使用.

name

要创建的函数的名称(可以被模式限定)。

argmode

一个参数的模式:INOUTINOUT或者VARIADIC。如果省略,默认为IN。只有OUT参数能跟在一个VARIADIC参数后面。还有,OUTINOUT参数不能和RETURNS TABLE符号一起使用。

argname

一个参数的名称。一些语言(包括 SQL 和 PL/pgSQL)让你在函数体中使用该名称。对于其他语言,一个输入参数的名字只是额外的文字(就该函数本身所关心的来说)。但是你可以在调用一个函数时使用输入参数名来提高可读性(见Section 5.3)。在任何情况下,输出参数的名称是有意义的,因为它定义了结果行类型中的列名(如果忽略一个输出参数的名称,系统将选择一个默认的列名)。

argtype

该函数参数(如果有)的数据类型(可以是模式限定的)。参数类型可以是基本类型、组合类型或者域类型,或者可以引用一个表列的类型。在lightdb_oracle_type_date_to_oracle_date中介绍了date类型的使用。

根据实现语言,也可以允许指定cstring之类的伪类型。伪类型表示实际参数类型没有被完整指定或者不属于普通 SQL 数据类型集合。

可以写table_name.column_name%TYPE来引用一列的类型。使用这种特性有时可以帮助创建一个不受表定义更改影响的函数。

default_expr

如果参数没有被指定值时要用作默认值的表达式。该表达式必须能被强制为该参数的参数类型。只有输入(包括INOUT)参数可以具有默认值。在非oracle模式下,所有跟随在一个具有默认值的参数之后的输入参数也必须有默认值。

rettype

返回数据类型(可能被模式限定)。返回类型可以是一种基本类型、组合类型或者域类型,也可以引用一个表列的类型。根据实现语言,也可以允许指定cstring之类的伪类型。如果该函数不会返回一个值,可以指定返回类型为void

当有OUT或者INOUT参数时,可以省略RETURNS子句。如果存在,该子句必须和输出参数所表示的结果类型一致:如果有多个输出参数,则为RECORD,否则与单个输出参数的类型相同。

SETOF修饰符表示该函数将返回一个项的集合而不是一个单一项。

可以写table_name.column_name%TYPE来引用一列的类型。

column_name

RETURNS TABLE语法中一个输出列的名称。这实际上是另一种声明OUT参数的方法,不过RETURNS TABLE也隐含了RETURNS SETOF

column_type

RETURNS TABLE语法中的输出列的数据类型。

lang_name

用以实现该函数的语言的名称。可以是sqlcinternal或者一个用户定义的过程语言的名称,例如plpgsql。 如果指定了sql_body, 则默认值为sql。不推荐使用单引号将名称括起来,并要求大小写匹配。

如果 lang_name 为 plorasql, 且运行在oracle模式,创建出来的函数将使用类型名字引用的模式, 此模式下,函数参数类型与函数没有依赖关系,用户可以显式地drop类型,且保有函数,用户也可以先创建函数,此时函数处于不可调用状态,当类型创建成功后,函数会变为可调用状态。

WINDOW

WINDOW表示该函数是一个窗口函数而不是一个普通函数。当前只用于用 C 编写的函数。在替换一个现有函数定义时,不能更改WINDOW属性。

IMMUTABLE
STABLE
VOLATILE

这些属性告知查询优化器该函数的行为。最多只能指定其中一个。如果这些都不出现,则会默认为VOLATILE

IMMUTABLE表示该函数不能修改数据库并且对于给定的参数值总是会返回相同的值。也就是说,它不会做数据库查找或者使用没有在其参数列表中直接出现的信息。如果给定合格选项,任何用全常量参数对该函数的额调用可以立刻用该函数值替换。

STABLE表示该函数不能修改数据库,并且对于相同的参数值,它在一次表扫描中将返回相同的结果。但是这种结果在不同的 SQL 语句执行期间可能会变化。对于那些结果依赖于数据库查找、参数变量(例如当前时区)等的函数来说,这是合适的(对希望查询被当前命令修改的行的AFTER触发器不适合)。还要注意current_timestamp函数族适合被标记为稳定,因为它们的值在一个事务内不会改变。

VOLATILE表示该函数的值在一次表扫描中都有可能改变,因此不能做优化。在这种意义上,相对较少的数据库函数是不稳定的,一些例子是random()currval()timeofday()。但是注意任何有副作用的函数都必须被分类为不稳定的,即便其结果是可以预测的,这是为了调用被优化掉。一个例子是setval()

更多细节可见Section 38.7

LEAKPROOF

LEAKPROOF表示该函数没有副作用。它不会泄露有关其参数的信息(除了通过返回值)。例如,一个只对某些参数值抛出错误消息而对另外一些却不抛出错误的函数不是防泄漏的,一个把参数值包括在任何错误消息中的函数也不是防泄漏的。这会影响系统如何执行在使用security_barrier选项创建的视图或者开启了行级安全性的表上执行查询。对于包含有非防泄漏函数的查询,系统将在任何来自查询本身的用户提供条件之前强制来自安全策略或者安全屏障的条件,防止无意中的数据暴露。被标记为防泄漏的函数和操作符被假定是可信的,并且可以在安全性策略和安全性屏障视图的条件之前被执行。此外,没有参数的函数或者不从安全屏障视图或表传递任何参数的函数不一定要被标记为防泄漏的。详见CREATE VIEWSection 41.4。这个选项只能由超级用户设置。

CALLED ON NULL INPUT
RETURNS NULL ON NULL INPUT
STRICT

CALLED ON NULL INPUT(默认)表示在某些参数为空值时应正常调用该函数。如果有必要,函数的作者应该负责检查空值并且做出适当的相应。

RETURNS NULL ON NULL INPUTSTRICT表示只要其任意参数为空值,该函数就会返回空值。如果指定了这个参数,当有空值参数时该函数不会被执行,而是自动返回一个空值结果。

[EXTERNAL] SECURITY INVOKER
[EXTERNAL] SECURITY DEFINER

SECURITY INVOKER表示要用调用该函数的用户的特权来执行它。这是默认值。SECURITY DEFINER指定要用拥有该函数的用户的特权来执行该函数。

为了符合 SQL,允许使用关键词EXTERNAL。但是它是可选的,因为与 SQL 中不同,这个特性适用于所有函数而不仅是那些外部函数。

PARALLEL

PARALLEL UNSAFE表示该函数不能在并行模式中运行并且 SQL 语句中存在一个这样的函数会强制使用顺序执行计划。这是默认选项。PARALLEL RESTRICTED表示该函数能在并行模式中运行,但是其执行被限制在并行组的领导者中。PARALLEL SAFE表示该函数对于在并行模式中运行是安全的并且不受限制。

如果函数修改任何数据库状态、会使用子事务之类的方式改变事务、访问序列或者对设置(如setval)做出持久性的更改,它们就应该被标记为并行不安全。如果它们访问临时表、客户端连接状态、游标、预备语句或者系统无法在并行模式中同步的本地后端状态(例如setseed只能在组领导者中执行,因为另一个进程所作的更改不会在领导者中被反映出来),它们应该被标为并行受限。通常,如果一个函数是受限的或者不安全的却被标成了安全,或者它本来是不安全的却被标成了受限,在并行查询中执行时它可能会抛出错误或者产生错误的答案。如果被错误的标记, C 语言函数理论上可能展现出完全无法定义的行为,因为系统没有办法保护自己不受任意的 C 代码影响,但是在大部分情况下其结果也不会比任何其他函数差到哪里去。如果有疑问,函数应该被标为UNSAFE,这也是默认值。

COST execution_cost

一个给出该函数的估计执行代价的正数,单位是cpu_operator_cost。如果该函数返回一个集合,这就是每个被返回行的代价。如果没有指定代价,对 C 语言和内部函数会指定为 1 个单位,对其他语言的函数则会指定为 100 单位。更大的值会导致优化器尝试避免对该函数的不必要的过多计算。

ROWS result_rows

一个正数,它给出优化器期望该函数返回的行数估计。只有当该函数被声明为返回一个集合时才允许这个参数。默认假设为 1000 行。

SUPPORT support_function

用于此函数的planner support function的名称(可选的模式限定)。 详请参见Section 38.11。你必须是超级用户才能使用此选项。

configuration_parameter
value

SET子句导致进入该函数时指定配置参数将被设置为指定值。并且在该函数退出时恢复到该参数之前的值。SET FROM CURRENT会把CREATE FUNCTION被执行时该参数的当前值保存为进入该函数时将被应用的值。

如果一个SET子句被附加到一个函数,那么在该函数内为同一个变量执行的SET LOCAL命令会被限制于该函数:在函数退出时该配置参数之前的值仍会被恢复。不过,一个普通的SET命令(没有LOCAL)会覆盖SET子句,更像一个之前的SET LOCAL命令所做的那样:这种命令的效果在函数退出后将会持续,除非当前事务被回滚。

更多有关允许的参数名和参数值的信息请见SETChapter 18

definition

一个定义该函数的字符串常量,其含义取决于语言。它可以是一个内部函数名、一个对象文件的路径、一个 SQL 命令或者用一种过程语言编写的文本。

美元引用Section 5.1.2.4通常对书写函数定义字符串有所帮助,而普通单引号语法则不会有用。如果没有美元引用,函数定义中的任何单引号或者反斜线必须用双写来转义。

obj_file, link_symbol

当 C 语言源代码中该函数的名称与 SQL 函数的名称不同时,这种形式的AS子句被用于动态可载入 C 语言函数。 字符串obj_file是包含编译好的C函数的动态库文件的名称, 它会由LOAD命令解析。 字符串link_symbol是该函数的链接符号,也就是该函数在 C 语言源代码中的名称。如果省略链接符号,它将被假定为要定义的 SQL 函数的名称。所有函数的C名称都必须不同,因此必须为重载的C函数给出不同的C名称(例如把参数类型作为C名称的一部分)。

在重复调用引用同一对象文件的CREATE FUNCTION时,对每个会话该文件只会被载入一次。要卸载并且重新装载该文件(可能是在开发期间),需要开始一个新会话。

重载

LightDB允许函数重载,也就是说同一个名称可以被用于多个不同的函数,只要它们具有可区分的输入参数类型。不管是否使用它,在有些用户不信任另一些用户的数据库中调用函数时,这种兼容性需要安全性的预防措施,请参考Section 11.3

如果两个函数具有相同的名称和输入参数类型,它们被认为相同(不考虑任何OUT参数, PL/SQL函数除外,PL/SQL函数的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参数,会严格检查该可变数组作为一个整体是否为非空。如果该数组有空值元素,该函数仍将被调用。

示例

使用SQL函数对两个整数相加:

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函数与之前的例子不同,因为它实际返回了一个记录集合而不只是一个记录。

在 oracle 模式下,在有默认值的参数后面的参数不需要有默认值。 pl/sql:

CREATE OR REPLACE FUNCTION increment(i integer, j integer default 1, k integer) RETURNS integer AS $$
        BEGIN
                RETURN i + j + k;
        END;
$$ LANGUAGE plorasql;

select increment(1,k=>1);
 increment 
-----------
         3
(1 row)

In oracle mode, parameters types has no dependency relation with the function. pl/sql:


create table t_fp(a int, b char);
insert into t_fp(a,b) values(1,'a');
insert into t_fp(a,b) values(2,'b');
insert into t_fp(a,b) values(3,'c');
-- terminate row
insert into t_fp(a,b) values(999999,'t'); 

CREATE OR REPLACE FUNCTION f_bar(
  v_cursor ora_pkg.ref_cursor_t
)
return NUMBER
IS
declare
	total_rows int := 0;
	cursor_row t_fp%rowtype;
begin
	LOOP
		FETCH v_cursor INTO cursor_row;
			if cursor_row.a = 999999 and cursor_row.b = 't' then
				exit;
			end if;
			total_rows := total_rows+1;
			dbms_output.put_line('t_fp%rowtype (a,b) -- ' || '(' || cursor_row.a || ',' || cursor_row.b || ')' );
	END LOOP;
	dbms_output.put_line('total rows: ' || total_rows );
	return total_rows;
end f_bar;
/
WARNING:  [functioncmds]type ora_pkg.ref_cursor_t does not exist
CREATE FUNCTION

declare
v_cursor ora_pkg.ref_cursor_t;
  rt int;
begin
  open v_cursor for select * from t_fp order by a asc;
  rt=f_bar(v_cursor);
  close v_cursor;
end;
/
ERROR:  schema "ora_pkg" does not exist
LINE 2: v_cursor ora_pkg.ref_cursor_t;
                 ^
QUERY:  declare
v_cursor ora_pkg.ref_cursor_t;
  rt int;
begin
  open v_cursor for select * from t_fp order by a asc;
  rt=f_bar(v_cursor);
  close v_cursor;
end;

create or replace package ora_pkg is
  type ref_cursor_t is ref cursor;
end;
/
CREATE PACKAGE

declare
v_cursor ora_pkg.ref_cursor_t;
  rt int;
begin
  open v_cursor for select * from t_fp order by a asc;
  rt=f_bar(v_cursor);
  close v_cursor;
end;
/
t_fp%rowtype (a,b) -- (1,a)
t_fp%rowtype (a,b) -- (1,a)
t_fp%rowtype (a,b) -- (2,b)
t_fp%rowtype (a,b) -- (2,b)
t_fp%rowtype (a,b) -- (3,c)
t_fp%rowtype (a,b) -- (3,c)
total rows: 6
DO

安全地编写 SECURITY DEFINER函数

因为一个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的临时表。

LightDB 版本 8.3 之前,SET子句不可用,因而较老的函数可能包含相当复杂的逻辑来保存、设置以及恢复search_path。对于这种目的,SET子句更容易。

另一点要记住的是默认情况下,会为新创建的函数给PUBLIC授予执行特权(详见Section 6.7)。你常常会希望把安全定义器函数的使用限制在某些用户中。要这样做,你必须收回默认的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;

兼容性

SQL标准中定义了CREATE FUNCTION命令。 LightDB实现可以以兼容的方式使用,但有许多扩展。 相反,SQL标准指定了许多未在LightDB中实现的可选功能。

为了与其他一些数据库系统兼容,argmode 可以在argname之前或之后写入。 但只有第一种方式符合标准。

对于参数默认值,SQL标准仅指定带有DEFAULT关键字的语法。 =的语法是在T-SQL和Firebird中被使用的。

另见

ALTER FUNCTION, DROP FUNCTION, GRANT, LOAD, REVOKE