使用下列过程确定函数调用引用的特定函数。
函数类型解析
从 pg_proc
系统目录中选择需要考虑的功能。如果使用了非模式限定函数名称,则考虑匹配的名称和参数计数与当前搜索路径中可见的函数(请参阅 第 5.10.3 节)。如果给出了限定函数名称,则仅考虑指定架构中的函数。
如果搜索路径找到多个具有相同参数类型的函数,则仅考虑路径中最早出现的那个。不同参数类型的函数无论搜索路径位置如何,都被视为平等对待。
如果函数是用 VARIADIC
数组参数声明的,并且调用没有使用 VARIADIC
关键字,则该函数被视为将数组参数替换为其元素类型的出现一次或多次,以匹配调用。此类扩展后,函数可能具有与某些非可变函数相同的有效参数类型。在这种情况下,将使用在搜索路径中最早出现的函数,或者如果两个函数在相同架构中,则优先使用非可变函数。
通过限定名称 [10] 调用一个在允许不受信任的用户创建对象的架构中找到的可变函数时,会造成安全隐患。恶意用户可以进行控制,并任意执行 SQL 函数,就像您执行它们一样。用包含 VARIADIC
关键字的调用替换,这会绕开此类隐患。填充 VARIADIC "any"
参数的调用通常没有包含 VARIADIC
关键字的等效公式。为了安全地发出这些调用,函数的架构只能允许受信任的用户创建对象。
参数具有默认值的函数被视为与省略零个或多个可默认参数位置的任何调用匹配。如果多个此类函数与调用匹配,则使用在搜索路径中最早出现的那个。如果在同一架构中存在两个或多个此类函数,并且在非默认参数位置具有相同的参数类型(如果有不同的可默认参数集,这是可能的),则系统将无法确定选择哪个,因此如果未找到与该调用更好地匹配项,则会导致 “不明确的函数调用” 错误。
通过限定名称[10]调用在允许不受信任的用户创建对象的模式中找到的任何函数时,会产生可用性隐患。恶意用户可以使用现有函数的名称创建函数,复制该函数的参数并附加带有默认值的新参数。这将阻止对原始函数的新调用。为了避免此隐患,将函数放在仅允许可信用户创建对象的模式中。
检查函数是否完全接受输入参数类型。如果存在(在所考虑的函数集中只能有一个完全匹配),请使用它。完全匹配不存在会产生安全隐患,即通过限定名称[10]在允许不受信任的用户创建对象的模式中调用函数。在这种情况下,转换参数以强制完全匹配。(涉及 unknown
的情况在这一步将永远找不到匹配项。)
如果找不到完全匹配,请查看函数调用是否看上去是特殊的类型转换请求。如果函数调用只有一个参数,并且函数名称与某些数据类型(内部)名称相同,则会出现这种情况。此外,函数参数必须是 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)
[11] 此步骤的原因是为了支持在没有实际强制转换函数的情况下使用函数风格的强制转换规范。如果有强制转换函数,按照惯例将以其输出类型命名,因此不需要特殊情况。有关其他注释,请参见 CREATE CAST。