11.3. 函数

被一个函数调用引用的特定函数使用下面的过程来决定。

函数类型决定

  1. pg_proc系统目录中选择要被考虑的函数。 如果使用一个非模式限定的函数名称,那么函数被认为是那些在当前搜索路径中可见并有匹配的名字和参数个数的函数(参见Section 6.9.3)。如果给出一个被限定的函数名,那么只考虑指定模式中的函数。

    1. 如果搜索路径发现多个参数类型相同的函数,那么只考虑最早在搜索路径中出现的那个。 不同参数类型的函数被平等对待,不受在搜索路径中位置的影响。

    2. 如果使用一个VARIADIC数组参数声明一个函数,并且调用不使用关键字VARIADIC, 那么该函数就好像其数组参数被它的元素类型的一次或多次出现所替换,根据需要去匹配调用。 这样的扩展之后,函数可能会有和非可变函数相同的参数类型。在这种情况下,在搜索路径中出现比较早的函数将被使用,或者如果两个函数在相同的模式中时首选非可变的那一个。

      在通过限定名称调用在一个允许不可信用户创建对象的方案中找到的可变函数时,会导致安全性危害 [10]。 恶意用户可以拿到控制权并且执行任意SQL函数(就好像你在执行它们一样)。将涉及VARIADIC关键词的调用替换掉就可以绕过这种危害。涉及到VARIADIC "any"参数的调用通常没有等效的包含VARIADIC关键词的形式。为了安全地发出那些调用,函数的方案必须只允许可信用户创建对象。

    3. 考虑使用有默认参数值的函数来匹配任何省略了零个或者多个可默认参数位置的调用。如果有超出一个的这种函数匹配一个调用,那么使用最早出现在搜索路径中的那个。如果同一个模式中在同一个非默认位置上有两个或者更多这样的函数(如果它们有 不同的默认参数设置,这是可能的),系统将不能确定去选择哪一个,并且如果不能找到该调用更好的匹配,将会导致一个有歧义的函数调用 错误。

      在通过限定名称[10]调用在一个允许不可信用户创建对象的方案中找到的任意函数时,会导致可用性危害。恶意用户可以用一个已有函数的名称创建一个函数,复制该函数的参数并且追加新的具有默认值的参数。这会妨碍对原始函数的新调用。为了防止这种危害,应将函数放在仅允许可信用户创建对象的方案中。

  2. 检查一个函数正好接受输入参数类型。如果存在一个(在所考虑的一组函数中只能有一个准确匹配),则使用之。在通过限定名称[10]调用在一个允许不可信用户创建对象的方案中找到的函数时,精确匹配的缺失会导致安全性危害。在这样的情况下,应该造型参数以便强制一次精确匹配(在该步骤中,涉及unknown的情况将永远找不到一个匹配)。

  3. 如果没有发现准确匹配,那么查看函数调用是否作为一个特定的类型转换请求出现。 如果函数调用仅有一个参数并且函数名和一些数据类型的(内部)名称相同,那么该情况将会发生。 并且,该函数参数必须是一个未知类型的文字,或者是一个可以被二进制强制转换到命名数据类型的类型, 或者是一个可以通过应用其I/O函数被转换为命名数据类型的类型(也就是,转换是转到标准字符串类型或者从标准字符串类型转来)。当满足这些条件的时候,函数调用被当做CAST声明的一种形式来对待。 [11]

  4. 查找最佳匹配。

    1. 如果候选函数的输入类型不匹配并且不能通过转换(使用一个隐式转换)达到匹配,则丢弃它。为了这个目的,unknown文字被假定可被转换成任何东西。如果仅有一个候选项,则使用之;否则继续下一步。

    2. 如果任何输入参数是一种域类型,在所有后续步骤中都把它当做 该域的基类型。这确保在做有歧义的操作符解析时,域的举止像它们 的基类型。

    3. 遍历所有候选函数并保留那些最匹配输入类型的。如果没有准确匹配,则保留所有候选项。 如果仅有一个候选项,则使用之;否则继续下一步。

    4. 遍历所有候选函数并保留那些在最多要求类型转换的位置上接受首选类型(属于输入数据类型的类型分类)的候选项。如果没有接受首选类型的候选项,则保留所有候选项。如果仅有一个候选项,则使用之;否则继续下一步。

    5. 如果任何输入参数是unknown,那么检查那些被剩余候选项在那些参数位置上接受的类型分类。在每一个位置上,如果任何候选项接受该分类则选择string分类 (这个偏向于字符串是恰当的,因为一个未知类型文字看起来像字符)。 否则,如果所有剩余的候选项接受相同的类型分类,那么选择那个分类; 否则将失败,因为缺乏更多线索来推断出正确的选择。现在,丢弃不接受被选中类型分类的候选项。此外,如果任何候选项接受那个分类中的一个首选类型,则丢弃对该参数接受非首选类型的候选项。如果没有候选项能通过这些测试,则保留所有候选项。如果只剩下一个候选项,则使用之;否则继续下一步。

    6. 如果既有unknown参数也有已知类型的参数,并且所有已知类型参数具有相同的类型,则假定该unknown参数也是那种类型的,并且检查哪些候选函数可以在该unknown参数的位置上接受那个类型。如果正好有一个候选者通过了这个测试,则使用之;否则失败。

    7. 请参考Section 11.7中描述的类型兼容匹配方式。

注意,对于操作符和函数类型决定来说最优匹配规则是完全相同的。下面是一些例子。

Example 11.6. 圆整函数参数类型决定

只有一个带有两个参数的圆整函数; 它采用第一个参数类型为numeric和第二个参数类型为integer。这样下面的查询自动将第一个类型为integer参数转换为numeric

SELECT round(4, 4);

 round
--------
 4.0000
(1 row)

该查询实际上被解析器转换为:

SELECT round(CAST (4 AS numeric), 4);

因为包含小数点的数字常数初始会被分配类型numeric,下面的查询将不需要类型转换并因此可能会稍稍高效一些:

SELECT round(4.0, 4);


Example 11.7. 可变函数决定

CREATE FUNCTION public.variadic_example(VARIADIC numeric[]) RETURNS int
  LANGUAGE sql AS 'SELECT 1';
CREATE FUNCTION

这个函数接受(但不要求)VARIADIC关键词。它能同时容忍integer以numeric参数:

SELECT public.variadic_example(0),
       public.variadic_example(0.0),
       public.variadic_example(VARIADIC array[0.0]);
 variadic_example | variadic_example | variadic_example
------------------+------------------+------------------
                1 |                1 |                1
(1 row)

不过,如果可以,第一个和第二个调用将更喜欢更明确的函数:

CREATE FUNCTION public.variadic_example(numeric) RETURNS int
  LANGUAGE sql AS 'SELECT 2';
CREATE FUNCTION

CREATE FUNCTION public.variadic_example(int) RETURNS int
  LANGUAGE sql AS 'SELECT 3';
CREATE FUNCTION

SELECT public.variadic_example(0),
       public.variadic_example(0.0),
       public.variadic_example(VARIADIC array[0.0]);
 variadic_example | variadic_example | variadic_example
------------------+------------------+------------------
                3 |                2 |                1
(1 row)

如果给定默认的配置并且只有第一个函数存在,则第一个和第二个调用是不安全的。任何用户都可以通过创建第二个或者第三个函数来截断它们。通过精确匹配参数类型并且使用VARIADIC关键词,第三个调用是安全的。


Example 11.8. 子串函数类型决定

有几个substr函数,其中一个用于textinteger类型。如果使用一个未指定类型的字符常量调用,那么系统选择接受一个首选分类string(也就是text类型)的参数的候选函数。

SELECT substr('1234', 3);

 substr
--------
     34
(1 row)

如果字符串被声明为类型varchar(如果它来自于一个表就会这样),那么解析器将尝试转换它为text

SELECT substr(varchar '1234', 3);

 substr
--------
     34
(1 row)

解析器所作的转换:

SELECT substr(CAST (varchar '1234' AS text), 3);

Note

解析器从pg_cast目录中知道textvarchar是二进制可兼容的, 意思是其中一个可以被传递给接受另一种类型的函数而不需要做任何物理转换。因此,在这种情况下不会真正使用类型转换调用。


Example 11.9. 添加月份函数类型解析。

orafce扩展提供了函数add_months的两个版本, 一个在模式pg_catalog下,另一个在模式oracle下。

这两个函数之间存在一些细微的差异: pg_catalog.add_months接受dateint类型的参数,并返回date类型; oracle.add_months接受timestamp with time zoneint类型的参数,并返回timestamp类型。

CREATE FUNCTION pg_catalog.add_months(day date, value int)
RETURNS date ...

select pg_catalog.add_months('2021-12-23', 4);
 add_months 
------------
 2022-04-23
(1 row)

CREATE FUNCTION oracle.add_months(TIMESTAMP WITH TIME ZONE,INTEGER)
RETURNS TIMESTAMP ...

select oracle.add_months('2021-12-23', 4);
     add_months      
---------------------
 2022-04-23 00:00:00
(1 row)

当使用未知类型调用add_months函数时,根据search_path不同,会匹配到不同的函数。

当模式oracle不在search_path中时,默认只在模式pg_catalog中查找add_months函数。

当模式oraclesearch_path中时,将有两个版本的add_months函数可用。 然而,oracle.add_months参数类型timestamp with time zone是首选类型,因此将选择oracle.add_months

select add_months('2021-12-23', 4);
 add_months 
------------
 2022-04-23
(1 row)

set search_path="$user",public,oracle;

select add_months('2021-12-23', 4);
     add_months      
---------------------
 2022-04-23 00:00:00
(1 row)




[10] 对非方案限定的名称,不会出现这种危害,因为包含允许不可信用户创建对象的方案的搜索路径不是一种安全的方案使用模式

[11] 这一步的原因是在没有一个实际的造型函数的情况下支持函数风格的造型声明。如果有一个造型函数,它被按惯例以其输出类型命名,并且不需要有特殊情况。更多信息请见CREATE CAST