函数调用所引用的具体函数按照下述过程确定。
函数类型解析
从系统目录pg_proc中选取要考虑的函数。如果使用的是未带模式限定的函数名,则考虑当前搜索路径中可见且名称和参数个数匹配的函数(见Section 5.10.3)。如果给出了限定名,则只考虑指定模式中的函数。
如果搜索路径中找到多个参数类型完全相同的函数,只考虑路径中最早出现的那一个。参数类型不同的函数则不受搜索路径位置影响,处于同等地位。
如果函数声明了一个VARIADIC数组参数,而调用时没有使用VARIADIC关键字,则把该函数视为其数组参数按需要被其元素类型的一个或多个出现所替换,以匹配该调用。扩展之后,该函数的有效参数类型可能与某个非VARIADIC函数完全相同。在这种情况下,使用搜索路径中较早出现的函数;如果两个函数位于同一模式,则优先选择非VARIADIC函数。
通过限定名调用某个允许不可信用户创建对象的模式中找到的可变参数函数时,会带来安全隐患 [10]。恶意用户可以借此劫持调用,并以你的身份执行任意 SQL 函数。改为使用带有VARIADIC关键字的调用可以绕过这一隐患。对于填充VARIADIC "any"参数的调用,往往没有包含VARIADIC关键字的等价写法。要安全地发出这类调用,该函数所在模式必须只允许可信用户创建对象。
带有参数默认值的函数,被认为可以匹配任何省略了零个或多个可默认参数位置的调用。如果不止一个这样的函数能匹配某个调用,则使用搜索路径中最早出现的那一个。如果同一模式中存在两个或更多这样的函数,并且它们在未使用默认值的参数位置上的参数类型相同(如果它们具有不同的可默认参数集合,这种情况是可能的),系统就无法判断该偏好哪一个;如果找不到更好的匹配,就会报“有歧义的函数调用”错误。
通过限定名[10]调用某个允许不可信用户创建对象的模式中的任意函数时,会带来可用性隐患。恶意用户可以用现有函数的名字创建一个新函数,复制该函数的参数,并附加带有默认值的新参数。这样就会阻止对原始函数的新调用。为避免这种隐患,应把函数放在只允许可信用户创建对象的模式中。
检查是否存在一个恰好接受输入参数类型的函数。如果存在(在被考虑的函数集合中至多只有一个精确匹配),就使用它。若通过限定名[10]调用某个允许不可信用户创建对象的模式中的函数,而又缺少精确匹配,就会带来安全隐患。在这种情况下,应通过对参数进行类型转换来强制获得精确匹配。(涉及unknown的情况在这一步永远找不到匹配。)
如果没有找到精确匹配,就检查该函数调用是否看起来像一种特殊的类型转换请求。这种情况发生在:函数调用只有一个参数,且函数名与某个数据类型的(内部)名称相同。此外,函数参数必须是unknown类型文字,或者是一种能够二进制可强制到该命名数据类型的类型,或者是一种可以通过应用其 I/O 函数转换为该命名数据类型的类型(也就是说,该转换要么转到某种标准字符串类型,要么从某种标准字符串类型转来)。满足这些条件时,该函数调用会被当作一种CAST形式。 [11]
寻找最佳匹配。
丢弃那些输入类型不匹配,且也无法通过隐式类型转换实现匹配的候选函数。为此,假定unknown文字可以转换为任何类型。如果只剩一个候选函数,就使用它;否则继续下一步。
如果任何输入参数是域类型,则在后续所有步骤中都把它当作该域的基类型。这样可以确保在解决函数歧义时,域的行为与其基类型一致。
遍历所有候选函数,保留那些在输入类型上精确匹配数最多的候选项。如果没有任何候选项存在精确匹配,则保留全部候选项。如果只剩一个候选函数,就使用它;否则继续下一步。
遍历所有候选函数,保留那些在需要类型转换的最多位置上接受首选类型(属于输入数据类型的类型分类)的候选项。如果没有候选项接受首选类型,则保留全部候选项。如果只剩一个候选函数,就使用它;否则继续下一步。
如果有任何输入参数是unknown,就检查剩余候选函数在这些参数位置上接受哪些类型分类。在每个位置上,如果有任何候选项接受string分类,就选择该分类。(这种偏向字符串的做法是合适的,因为unknown类型的文字看起来像字符串。)否则,如果所有剩余候选项都接受同一种类型分类,就选择该分类;否则失败,因为在缺少更多线索的情况下无法推断出正确选择。然后丢弃不接受所选类型分类的候选项。此外,如果有任何候选项接受该分类中的首选类型,就丢弃在该参数上接受非首选类型的候选项。如果没有候选项通过这些测试,则保留全部候选项。如果只剩一个候选函数,就使用它;否则继续下一步。
如果同时存在unknown参数和已知类型参数,且所有已知类型参数都具有相同类型,则假定unknown参数也属于该类型,并检查哪些候选项能在unknown参数位置接受该类型。如果恰好只有一个候选项通过测试,就使用它;否则失败。
注意,操作符类型解析和函数类型解析的“最佳匹配”规则是完全相同的。下面是一些示例。
Example 10.6. round 函数参数类型解析
只有一个接受两个参数的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);
Example 10.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 10.8. substr 函数类型解析
存在多个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。
如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。