函数调用所引用的具体函数是使用以下过程确定的。
函数类型解析
从 pg_proc
系统目录中选择要考虑的函数。如果使用了非模式限定的函数名称,则考虑的函数是那些在当前搜索路径中可见的具有匹配名称和参数计数的函数(请参见第 5.10.3 节)。如果给出了限定的函数名称,则仅考虑指定模式中的函数。
如果搜索路径找到多个具有相同参数类型的函数,则仅考虑路径中最早出现的函数。具有不同参数类型的函数被视为地位相同,而与搜索路径中的位置无关。
如果一个函数使用 VARIADIC
数组参数声明,并且该调用没有使用 VARIADIC
关键字,那么该函数被视为好像该数组参数被其元素类型的一个或多个实例所替换,以匹配该调用。在这种扩展之后,该函数可能具有与某个非可变参数函数相同的有效参数类型。在这种情况下,使用搜索路径中较早出现的函数,或者如果这两个函数在同一模式中,则优先使用非可变参数的函数。
当通过限定名称[10]调用允许不受信任的用户创建对象模式中的可变参数函数时,会产生安全隐患。恶意用户可以控制并执行任意 SQL 函数,就好像您执行了它们一样。替换一个带有 VARIADIC
关键字的调用,这可以绕过这种隐患。填充 VARIADIC "any"
参数的调用通常没有包含 VARIADIC
关键字的等效形式。为了安全地发出这些调用,该函数的模式必须仅允许受信任的用户创建对象。
具有参数默认值的函数被认为与省略零个或多个可默认参数位置的任何调用匹配。如果多个这样的函数与一个调用匹配,则使用搜索路径中最早出现的函数。如果同一模式中有两个或多个这样的函数,它们在非默认位置具有相同的参数类型(如果它们具有不同的可默认参数集,则这是可能的),则系统将无法确定哪个优先,因此如果找不到更好的匹配,将导致 “函数调用不明确”错误。
当通过限定名称[10]调用允许不受信任的用户创建对象模式中的任何函数时,会产生可用性隐患。恶意用户可以创建一个具有现有函数名称的函数,复制该函数的参数并附加具有默认值的新参数。这会阻止对原始函数的新调用。为了防止这种隐患,请将函数放在仅允许受信任的用户创建对象的模式中。
检查是否存在完全接受输入参数类型的函数。如果存在一个(在考虑的函数集中只能有一个完全匹配),则使用它。(在此时,涉及 unknown
的情况永远不会找到匹配。)缺少完全匹配会在通过限定名称[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. Substring 函数类型解析
有几个 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)
[11] 这一步的原因是为了在没有实际的转换函数的情况下支持函数式类型转换规范。如果存在转换函数,按照惯例,它的名称会与其输出类型一致,因此不需要特殊处理。请参阅 CREATE CAST 获取更多说明。
如果您在文档中发现任何不正确、与您使用特定功能的经验不符或需要进一步澄清的内容,请使用此表单报告文档问题。