5.3. 操作符 #
操作符调用的操作数类型按下面的过程解析。注意这个过程间接受所涉操作符优先级的影响。更多信息见第 1.4 节。
操作数类型解析
在
pg_operator系统目录中检查精确匹配。如果二元操作符的一个参数是
unknown类型,那么在这一步检查中假定它与另一个参数的类型相同。其他涉及unknown的情况在这一步永远找不到匹配。
寻找最佳匹配。
列出所有同名且输入类型匹配或可以强制转换成匹配的操作符。(为此目的,假定
unknown字面量可以转换成任何类型。)如果只有一个,就使用它;否则继续下一步。遍历所有候选,保留输入类型精确匹配最多的。如果没有任何精确匹配,则保留全部候选。如果只剩一个候选,就使用它;否则继续下一步。
遍历所有候选,保留输入类型精确匹配或二进制兼容匹配最多的。如果没有任何精确或二进制兼容匹配,则保留全部候选。如果只剩一个候选,就使用它;否则继续下一步。
遍历所有候选,保留在需要类型强制转换的最多个位置上接受首选类型的。如果没有任何候选接受首选类型,则保留全部候选。如果只剩一个候选,就使用它;否则继续下一步。
如果有输入参数是 “unknown”,检查其余候选在这些参数位置上接受的类型类别。在每个位置上,如果有候选接受 "string" 类别就选择该类别(这种偏向字符串的做法是恰当的,因为未知类型的字面量看起来确实像字符串)。否则,如果所有剩余候选接受相同的类型类别,就选择该类别;否则失败,因为没有更多线索就无法推断出正确的选择。还要注意是否有候选接受所选类别中的首选数据类型。现在丢弃不接受所选类型类别的操作符候选;此外,如果某个候选在给定的参数位置上接受首选类型,就丢弃在该参数上接受非首选类型的候选。
如果只剩一个候选操作符,就使用它。如果没有候选操作符或者不止一个候选操作符剩余,则失败。
Examples
例 5.1. 指数操作符类型解析
目录中只定义了一个取幂运算符,它接受
double precision 类型的参数。扫描器给这个查询表达式的两个参数都赋予了初始类型 integer:
tgl=> SELECT 2 ^ 3 AS "Exp"; Exp ----- 8 (1 row)
于是解析器对两个操作数都做类型转换,查询等价于
tgl=> SELECT CAST(2 AS double precision) ^ CAST(3 AS double precision) AS "Exp"; Exp ----- 8 (1 row)
或
tgl=> SELECT 2.0 ^ 3.0 AS "Exp"; Exp ----- 8 (1 row)
注意
最后这种形式的开销最小,因为没有调用任何函数来做隐式类型转换。这对小查询不是问题,但可能影响涉及大表的查询的性能。
例 5.2. 字符串连接操作符类型解析
类字符串的语法既用于处理字符串类型,也用于处理复杂的扩展类型。未指定类型的字符串会与可能的操作符候选匹配。
一个参数未指定类型的例子:
tgl=> SELECT text 'abc' || 'def' AS "Text and Unknown"; Text and Unknown ------------------ abcdef (1 row)
在这种情况下,解析器会查看是否存在一个两边参数都接受text的操作符。既然存在,它就会假定第二个参数应解释为text类型。
未指定类型上的串接:
tgl=> SELECT 'abc' || 'def' AS "Unspecified"; Unspecified ------------- abcdef (1 row)
这种情况下没有关于使用哪个类型的初始提示,因为查询中没有指定类型。于是解析器查找所有候选操作符,发现有候选既接受 string 类别也接受
bit-string 类别的输入。由于 string 类别可用时会被优先选择,就选择了该类别,然后把字符串的“首选类型” text 用作解析未知字面量的具体类型。
例 5.3. 绝对值与阶乘操作符的类型解析
PostgreSQL 运算符目录中前缀运算符
@ 有若干条目,它们为各种数值数据类型实现绝对值操作。其中一个条目面向 float8 类型,它是数值类别中的首选类型。因此,PostgreSQL
遇到非数值输入时会使用那个条目:
tgl=> select @ text '-4.5' as "abs"; abs ----- 4.5 (1 row)
这里系统在应用选中的运算符之前做了一次隐式的 text 到 float8 转换。我们可以验证用的是 float8 而不是别的类型:
tgl=> select @ text '-4.5e500' as "abs"; ERROR: Input '-4.5e500' is out of range for float8
另一方面,后缀运算符 !(阶乘)只为整数数据类型定义,不为 float8 定义。因此,如果我们用 ! 尝试类似的情形,会得到:
tgl=> select text '44' ! as "factorial";
ERROR: Unable to identify a postfix operator '!' for type 'text'
You may need to add parentheses or an explicit cast
这是因为系统无法决定几个可能的
! 运算符中应该首选哪个。我们可以用显式转换来帮它解决:
tgl=> select cast(text '44' as int8) ! as "factorial";
factorial
---------------------
2673996885588443136
(1 row)