10.2. 操作符

下面讲解的过程解释了操作符表达式如何确定引用哪个操作符。 请注意这个过程受被调用操作符的优先级影响,因为这将决定哪个子表达式被用来作为操作符的输入。 参阅第 4.1.6 节获取更多信息。

操作符类型解析

  1. 从系统表pg_operator中选出要考虑的操作符。 如果使用了一个不带模式修饰的操作符名(常见的状况), 那么认为该操作符是那些在当前搜索路径中名字和参数个数都匹配的操作符 (参阅第 5.7.3 节)。如果给出一个带修饰的操作符名, 那么只考虑指定模式中的操作符。

    1. 如果搜索路径中找到了多个相同参数类型的操作符,那么只考虑最早出现在路径中的那一个。 但是不同参数类型的操作符将被平等看待,而不管它们在路径中的位置如何。

  2. 查找精确接受输入参数类型的操作符。如果找到一个(在一组被考虑的操作符中, 可能只存在一个精确匹配的),则用之。

    1. 如果一个双目操作符调用中的一个参数是unknown类型, 则在本次检查中假设其与另一个参数类型相同。包括两个unknown 输入的调用或一个一元带有unknown输入的操作符,将绝不会在此处找到匹配。

    2. 如果二进制操作符调用的一个参数是unknown类型的,其他参数是域类型, 下一步检查看看是否有一个操作符正好两侧都接受域的基本类型;如果有,则使用它。

  3. 寻找最优匹配。

    1. 抛弃那些输入类型不匹配并且也不能隐式转换成匹配的候选操作符。 unknown文本在这种情况下可以转换成任何东西。 如果只剩下一个候选项,则用之,否则继续下一步。

    2. 如果任意输入参数是域类型的,那么在随后的步骤中将其看做是域的基本类型。 这保证了域像它们的基本类型那样动作,解决了歧义的操作符。

    3. 遍历所有候选操作符,保留那些输入类型匹配最准确的。 如果没有一个操作符能被保留,则保留所有候选。如果只剩下一个候选项,则用之,否则继续下一步。

    4. 遍历所有候选操作符,保留那些需要类型转换时接受(属于输入数据类型的类型范畴的)首选类型位置最多的操作符。 如果没有接受首选类型的操作符,则保留所有候选。如果只剩下一个候选项,则用之,否则继续下一步。

    5. 如果有任何输入参数是unknown类型,检查剩余的候选操作符对应参数位置的类型范畴。 在每一个能够接受字符串类型范畴的位置使用 string类型(这种对字符串的偏爱是合适的, 因为 unknown 文本确实像字符串)。另外,如果所有剩下的候选操作符都接受相同的类型范畴, 则选择该类型范畴,否则抛出一个错误(因为在没有更多线索的条件下无法作出正确的选择)。 现在抛弃不接受选定的类型范畴的候选操作符,然后, 如果任意候选操作符在某个给定的参数位置接受一个首选类型, 则抛弃那些在该参数位置接受非首选类型的候选操作符。 如果没有一个操作符能被保留,则保留所有候选。如果只剩下一个候选项,则用之,否则继续下一步。

    6. 如果同时有unknown和已知类型的参数,并且所有已知类型的参数都是相同的类型, 那么假设unknown参数也是那种类型,并检查哪个候选操作符在unknown 参数位置接受那个类型。如果只有一个操作符符合,那么使用它。否则,产生一个错误。

下面是一些例子。

例 10-1. 阶乘操作符类型解析

在系统表中里只有一个阶乘操作符(后缀!),它以bigint作为参数。 扫描器给下面查询表达式的参数赋予integer的初始类型:

SELECT 40 ! AS "40 factorial";

                   40 factorial
--------------------------------------------------
 815915283247897734345611269596115894272000000000
(1 row)

分析器对参数做类型转换,查询等效于:

SELECT CAST(40 AS bigint) ! AS "40 factorial";

例 10-2. 字符串连接操作符类型分析

一种字符串风格的语法既可以用于字符串也可以用于复杂的扩展类型。 未声明类型的字符串将被所有可能的候选操作符匹配。

有一个未声明的参数的例子:

SELECT text 'abc' || 'def' AS "text and unknown";

 text and unknown
------------------
 abcdef
(1 row)

本例中分析器寻找两个参数都是text的操作符。确实这样的操作符, 因此另一个参数就被认为是text类型。

下面是连接两个未声明类型的值:

SELECT 'abc' || 'def' AS "unspecified";

 unspecified
-------------
 abcdef
(1 row)

因为查询中没有声明任何类型,所以本例中对类型没有任何初始提示。因此, 分析器查找所有候选操作符,发现既存在接受字符串类型范畴的操作符也存在接受位串类型范畴的操作符。 因为字符串类型范畴是首选,所以选择字符串类型范畴的首选类型text 作为解析未知类型文本的声明类型。

例 10-3. 绝对值和取反操作符类型分析

PostgreSQL操作符表里面有几条记录对应于前缀操作符@, 它们都用于为各种数值类型实现绝对值操作。其中之一用于float8类型, 它是数值类型范畴中的首选类型。因此,在面对unknown输入的时候, PostgreSQL会使用该类型:

SELECT @ '-4.5' AS "abs";
 abs
-----
 4.5
(1 row)

此处,系统在应用选定的操作符之前隐式的转换unknown类型的文字为float8类型。 我们可以验证它是float8而不是其它类型:

SELECT @ '-4.5e500' AS "abs";

ERROR:  "-4.5e500" is out of range for type double precision

另一方面,前缀操作符~(按位取反)只为整数数据类型定义, 而不为float8定义。因此,如果我们用~做类似的实验将得到:

SELECT ~ '20' AS "negation";

ERROR:  operator is not unique: ~ "unknown"
HINT:  Could not choose a best candidate operator. You might need to add
explicit type casts.

这是因为系统无法决定几个可能的~操作符中究竟应该使用哪一个。 我们可以用明确地类型转换来帮它:

SELECT ~ CAST('20' AS int8) AS "negation";

 negation
----------
      -21
(1 row)

例 10-4. 数组包含操作符类型分析

这里是解决一个操作符带有一个已知和一个未知类型输入的例子:

SELECT array[1,2] <@ '{1,2,3}' as "is subset";

 is subset
-----------
 t
(1 row)

PostgreSQL操作符表有几条记录对应于中缀操作符<@, 但是只有两个可以在左侧接受一个整数数组的操作符是数组包含(anyarray <@ anyarray) 和范围包含(anyelement <@ anyrange)的。 因为没有多态的伪类型(参阅第 8.20 节)是首选的,所以解析器不能解决这个基础上的歧义。 然而,步骤 3.f告诉我们,假设未知类型的文字是和另外一个输入相同的类型,也就是,整数数组。 现在只有两个操作符中的一个可以匹配,所以选择数组包含。(如果我们选择了范围包含, 我们将得到一个错误,因为字符串没有正确的格式成为范围的文字。)

例 10-5. 在域类型上自定义操作符

用户有时尝试声明只作用于域类型的操作符。这是可能的,但是没看上去那么有用, 因为操作符解析规则的设计是选择应用于域的基本类型的操作符。考虑一个例子:

CREATE DOMAIN mytext AS text CHECK(...);
CREATE FUNCTION mytext_eq_text (mytext, text) RETURNS boolean AS ...;
CREATE OPERATOR = (procedure=mytext_eq_text, leftarg=mytext, rightarg=text);
CREATE TABLE mytable (val mytext);

SELECT * FROM mytable WHERE val = 'foo';

这个查询将不会使用自定义操作符。解析器首先查看是否有 mytext = mytext操作符(步骤 2.a), 这里没有;然后它将考虑域的基本类型text,并查看是否有 text = text操作符(步骤 2.b), 这里有;所以它将unknown类型文字看做text并使用 text = text操作符。 使用自定义操作符的唯一方式是明确转换文字:

SELECT * FROM mytable WHERE val = text 'foo';

所以,根据正好匹配的规则,立刻就找到了mytext = text 操作符。如果达到了最佳匹配的规则,那么它们积极的排斥域类型上的操作符。 如果没有的达到,那么这样一个操作符将会创建太多歧义操作符失败, 因为转换规则总是认为域是可以和它的基本类型相互转换的, 所以在所有相同的情况下,作为在基本类型上同样命名的操作符, 域操作符将被认为是可用的。