下面讲解的过程解释了如何在一次函数调用中确定所使用的究竟是哪个函数。
函数类型解析
-
从系统表
pg_proc
中选择要考虑的函数。如果使用了一个不带模式修饰的函数名字, 那么认为该函数是那些在当前搜索路径中名字和参数个数都匹配的函数(参阅第 5.7.3 节)。 如果给出一个带修饰的函数名,那么只考虑指定模式中的函数。-
如果搜索路径中找到了多个相同参数类型的函数,那么只考虑最早出现在路径中的那一个。 但是不同参数类型的函数将被平等看待,而不管它们在路径中的位置如何。
-
如果使用一个VARIADIC数组参数声明一个函数,并且调用时不使用关键字VARIADIC, 那么该函被认为数组参数被一个或更多它的元素类型的实体代替,并且需要去匹配调用。 经过这样的扩展,这个函数可能有和非可变函数相同的有效参数类型。在这种情况下, 使用在搜索路径中出现比较早的函数,或者如果两个函数在相同的模式中,那么首选非可变的一个。
-
考虑使用有默认参数值的函数来匹配任何省略了零或者多个默认表参数位置的调用。 如果多个这样的函数匹配一个调用,那么使用最早出现在搜索路径中的那个。 如果在非默认位置有两个或者更多带有相同模式相同参数类型这样的函数 (他们的默认参数设置可能有不同),系统将不能确定去选择哪个, 并且如果不能找到更好的函数匹配调用,那么将会产生一个"ambiguous function call"错误。
-
-
查找精确接受输入参数类型的函数。如果找到一个(在一组被考虑的函数中, 可能只存在一个精确匹配的),则用之。包含unknown类型的函数调用绝不会在此处找到匹配。
-
如果没有找到精确的匹配,则看看函数调用是否需要一个特殊的类型转换。 如果函数调用只有一个参数并且函数名与某些数据类型的内部名称相同,那么就会出现这种情况。 另外,该函数的参数必须是一个未知类型的文本,或者与某个已命名数据类型二进制兼容, 或者是一个可以通过请求那种类型的I/O函数转换为已命名数据类型。(也就是, 要么可以转换成标准字符串类型,要么可以从标准字符串类型转换而来。)如果符合这些条件, 那么该函数调用被认为是一种CAST声明。 [1]
-
寻找最优匹配。
-
抛弃那些输入类型不匹配并且也不能隐式转换成匹配的候选函数。unknown 文本在这种情况下可以转换成任何东西。如果只剩下一个候选项,则用之,否则继续下一步。
-
如果任意输入参数是域类型,那么在所有随后的步骤中都将其看做是域的基本类型。 这保证了域像它们的基本类型那样动作,解决了歧义函数。 If any input argument is of a domain type, treat it as being of the domain's base type for all subsequent steps. This ensures that domains act like their base types for purposes of ambiguous-function resolution.
-
遍历所有候选函数,保留那些输入类型匹配最准确的。 如果没有一个函数能准确匹配,则保留所有候选。 如果只剩下一个候选项,则用之,否则继续下一步。
-
遍历所有候选函数,保留那些需要类型转换时接受(属于输入数据类型的类型范畴的) 首选类型位置最多的函数。如果没有接受首选类型的函数,则保留所有候选。 如果只剩下一个候选项,则用之,否则继续下一步。
-
如果有任何输入参数是unknown类型,检查剩余的候选函数对应参数位置的类型范畴。 在每一个能够接受字符串类型范畴的位置使用string类型(这种对字符串的偏爱是合适的, 因为 unknown 文本确实像字符串)。另外,如果所有剩下的候选函数都接受相同的类型范畴, 则选择该类型范畴,否则抛出一个错误(因为在没有更多线索的条件下无法作出正确的选择)。 现在抛弃不接受选定的类型范畴的候选函数,然后,如果任意候选函数在那个范畴接受一个首选类型, 则抛弃那些在该参数位置接受非首选类型的候选函数。如果没有一个候选符合这些测试则保留所有候选。 如果只有一个候选函数符合,则使用它;否则,继续下一步。
-
如果同时有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. 子字符串函数类型解析
有好几个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)
要插入表中的数值也根据下面的步骤转换成目标列的数据类型。
值存储数据类型解析
-
查找与目标字段准确的匹配。
-
试着将表达式直接转换成目标类型。如果已知这两种类型之间存在一个已注册的转换函数, 那么直接调用该转换函数即可。如果表达式是一个未知类型文本, 该文本字符串的内容将交给目标类型的输入转换过程。
-
检查一下看看目标类型是否有长度转换。长度转换是一个从某类型到自身的转换。 如果在pg_cast表里面找到一个,那么在存储到目标字段之前先在表达式上应用。 这样的转换函数总是接受一个额外的类型为integer的参数, 它接收目标字段的atttypmod值(实际上是其声明长度,atttypmod 的解释随不同的数据类型而不同),并且它可能接受一个boolean类型的第三个参数, 表示转换是显式的还是隐式的。转换函数负责施加那些长度相关的语义,比如长度检查或者截断。
例 10-8. character 存储类型转换
对一个目标列定义为character(20)的语句,下面的语句显示存储值的长度正确:
CREATE TABLE vv (v character(20)); INSERT INTO vv SELECT 'abc' || 'def'; SELECT v, octet_length(v) FROM vv; v | octet_length ----------------------+-------------- abcdef | 20 (1 row)
这里真正发生的事情是两个 unknown 文本缺省解析成text, 这样就允许||操作符解析成text连接。 然后操作符的text结果转换成bpchar("空白填充的字符型", character类型内部名称)以匹配目标字段类型。不过,从text 到bpchar的转换是二进制兼容的,这样的转换是隐含的并且实际上不做任何函数调用。 最后,在系统表里找到长度转换函数bpchar(bpchar, integer, boolean) 并且应用于该操作符的结果和存储的字段长。这个类型相关的函数执行所需的长度检查和额外的空白填充。