10.3. 函数#
使用以下过程确定函数调用所引用的特定函数。
函数类型解析
从
pg_proc
系统目录中选择要考虑的函数。如果使用了非模式限定函数名称,则考虑的函数是那些与匹配的名称和参数计数匹配的函数,并且在当前搜索路径中可见(请参见 第 5.9.3 节)。如果给出了限定函数名称,则仅考虑指定模式中的函数。如果搜索路径找到多个具有相同参数类型的函数,则仅考虑在路径中最早出现的函数。不同参数类型的函数被视为同等地位,而不管搜索路径位置如何。
如果函数使用
VARIADIC
数组参数声明,并且调用未使用VARIADIC
关键字,则函数将被视为数组参数被替换为一个或多个其元素类型出现的情况,以满足调用需要。经过这样的扩展后,函数可能具有与某些非可变函数相同的有效参数类型。在这种情况下,使用在搜索路径中最早出现的函数,或者如果这两个函数在同一模式中,则优先使用非可变函数。当通过限定名称 [10] 调用在允许不受信任的用户创建对象的模式中找到的可变函数时,这会产生安全隐患。恶意用户可以控制并执行任意 SQL 函数,就像您执行它们一样。用带有
VARIADIC
关键字的调用来代替,这可以绕过此隐患。填充VARIADIC "any"
参数的调用通常没有包含VARIADIC
关键字的等效公式。为了安全地发出这些调用,函数的模式必须只允许受信任的用户创建对象。对于具有参数默认值的函数,如果调用省略了零个或多个可设置默认值的参数位置,则认为它们匹配。如果多个这样的函数与调用匹配,则使用在搜索路径中最早出现的函数。如果在同一模式中存在两个或更多这样的函数,并且在非默认位置具有相同的参数类型(如果它们具有不同的可设置默认值参数集,则这是可能的),则系统将无法确定优先使用哪一个,因此如果找不到与调用更好的匹配项,则会产生 “不明确的函数调用” 错误。
当通过限定名称[10] 调用在允许不受信任的用户创建对象的模式中找到的任何函数时,这会产生可用性隐患。恶意用户可以创建具有现有函数名称的函数,复制该函数的参数并追加具有默认值的新参数。这会阻止对原始函数的新调用。为了防止此隐患,请将函数放在只允许受信任的用户创建对象的模式中。
检查一个接受完全输入参数类型的函数。如果存在(在考虑的函数集中只能有一个完全匹配),使用它。在通过限定名称[10]调用时,缺乏完全匹配会产生安全隐患,在模式中找到的函数允许不受信任的用户创建对象。在这种情况下,强制转换参数以实现完全匹配。(涉及
unknown
的情况永远不会在此步骤中找到匹配项。)如果没有找到完全匹配,请查看函数调用是否看起来是特殊类型转换请求。如果函数调用只有一个参数,并且函数名称与某些数据类型的(内部)名称相同,则会发生这种情况。此外,函数参数必须是未知类型文字,或者可以二进制强制转换为命名数据类型,或者可以通过应用该类型 I/O 函数(即转换是到或来自标准字符串类型之一)转换为命名数据类型。当满足这些条件时,函数调用将被视为
CAST
规范的一种形式。 [11]寻找最佳匹配。
丢弃输入类型不匹配且无法转换(使用隐式转换)以匹配的候选函数。为此目的,假定
unknown
文字可以转换为任何内容。如果只保留一个候选者,则使用它;否则继续下一步。如果任何输入参数是域类型,则将其视为在所有后续步骤中是域的基本类型。这确保域在消除歧义函数的目的中像其基本类型一样起作用。
遍历所有候选者,并保留在输入类型上具有最精确匹配的候选者。如果没有精确匹配,则保留所有候选者。如果只保留一个候选者,则使用它;否则继续下一步。
遍历所有候选者,并保留在需要类型转换的大多数位置接受首选类型(输入数据类型的类型类别)的候选者。如果没有候选者接受首选类型,则保留所有候选者。如果只保留一个候选者,则使用它;否则继续下一步。
如果任何输入参数是
unknown
,请检查剩余候选者在这些参数位置接受的类型类别。在每个位置,如果任何候选者接受该类别,则选择string
类别。(这种偏向字符串是合适的,因为未知类型文字看起来像字符串。)否则,如果所有剩余候选者都接受相同的类型类别,则选择该类别;否则失败,因为无法在没有更多线索的情况下推断出正确选择。现在丢弃不接受所选类型类别的候选者。此外,如果任何候选者在该类别中接受首选类型,则丢弃接受该参数非首选类型的候选者。如果没有候选者通过这些测试,则保留所有候选者。如果只保留一个候选者,则使用它;否则继续下一步。如果同时存在
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
函数,其中一个采用类型text
和integer
。如果使用未指定类型的字符串常量进行调用,系统将选择接受首选类别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
目录了解到text
和varchar
是二进制兼容的,这意味着可以将一个传递给接受另一个的函数,而无需进行任何物理转换。因此,在这种情况下实际上不会插入任何类型转换调用。
并且,如果函数使用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。