2024年9月26日: PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:开发版
不支持的版本:11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2 / 7.1

10.3. 函数 #

函数调用引用的特定函数是使用以下过程确定的。

函数类型解析

  1. pg_proc系统目录中选择要考虑的函数。如果使用了非模式限定的函数名,则考虑的函数是那些在当前搜索路径中可见的、具有匹配名称和参数数量的函数(参见第 5.10.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参数位置接受该类型。如果只有一个候选者通过此测试,则使用它。否则,失败。

请注意,运算符和函数类型解析的“最佳匹配”规则是相同的。下面是一些示例。

示例 10.6. 四舍五入函数参数类型解析

只有一个round函数接受两个参数;它接受类型为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);

示例 10.7. 变长函数解析

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

此函数接受但不一定需要 VARIADIC 关键字。它可以容忍整数和数字参数

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关键字,第三个调用是安全的。


示例 10.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);

注意

解析器从pg_cast目录中了解到textvarchar是二进制兼容的,这意味着一个可以传递给接受另一个的函数而无需进行任何物理转换。因此,在这种情况下,实际上没有插入任何类型转换调用。

并且,如果函数使用类型为integer的参数调用,则解析器将尝试将其转换为text

SELECT substr(1234, 3);
ERROR:  function substr(integer, integer) does not exist
HINT:  No function matches the given name and argument types. You might need
to add explicit type casts.

这不起作用,因为integer没有到text的隐式转换。但是,显式转换将起作用

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

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



[10] 对于非模式限定名称,此隐患不会出现,因为包含允许不受信任的用户创建对象的模式的搜索路径不是安全的模式使用模式

[11] 此步骤的原因是为了在没有实际转换函数的情况下支持函数风格的转换规范。如果存在转换函数,则通常以其输出类型命名,因此无需特殊情况。有关其他说明,请参阅CREATE CAST

提交更正

如果您在文档中发现任何不正确的内容、与您对特定功能的体验不符的内容或需要进一步澄清的内容,请使用此表单 报告文档问题。