10.2. 操作符

被一个操作符表达式引用的特定操作符由下列过程决定。注意这个过程会被所涉及的操作符的优先级间接地影响,因为这将决定哪些子表达式被用作哪个操作符的输入。详见Section 4.1.6

操作符类型决定

  1. 从系统目录pg_operator中选出要考虑的操作符。如果使用了一个不带模式限定的操作符 名(常见的情况),那么操作符被认为是那些在当前搜索路径中可见并有匹配的名字和参数个数的操作符(参见Section 5.8.3)。如果给出一个被限定的操作符名,那么只考虑指定模式中的操作符。

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

  2. 查找一个正好接受输入参数类型的操作符。如果找到一个(在一组被考虑的操作符中,可能只存在一个正好匹配的),则使用之。在通过限定名称(非典型)调用在一个允许不可信用户创建对象的方案中找到的任意操作符时,精确匹配的缺失会导致安全性危害 [8] 。在这样的情况下,应该造型参数以便强制一次精确匹配。

    1. 如果一个二元操作符调用中的一个参数是unknown类型,则在本次检查中假设它与另一个参数类型相同。 对于涉及两个unknown输入的调用或者带有一个unknown输入的一元操作符,在这一步将永远找不到一个匹配。

    2. 如果一个二元操作符调用的其中一个参数是unknown类型 而另一个是一种域类型,下一次检查会看看是否有一个操作符正好在两边都 接受该域的基类型,如果有就使用它。

  3. 寻找最优匹配。

    1. 抛弃那些输入类型不匹配并且也不能被转换成匹配的候选操作符。 unknown文字被假定为可以为这个目的被转换为 任何东西。如果只剩下一个候选操作符,则使用之,否则继续下一 步。

    2. 如果任何输入参数是一种域类型,对所有后续步骤都把它当做是该 域的基类型。这确保在做有歧义的操作符解析时,域的举止像它们 的基类型。

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

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

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

    6. 如果既有unknown参数也有已知类型的参数,并且所有已知类型参数具有相同的类型,则假定该unknown参数也是那种类型的,并且检查哪些候选操作符可以在该unknown参数的位置上接受那个类型。如果正好有一个候选者通过了这个测试,则使用之;否则失败。

下面是一些例子。

Example 10.1. 阶乘操作符类型决定

在标准目录中只有一个被定义的阶乘操作符(后缀!),它接受一个类型为bigint的参数。在下面这个查询表达式中,扫描器会为该参数分配一个初始类型integer

SELECT 40 ! AS "40 factorial";

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

因此,解析器在操作数上做了一个类型转换,该查询等价于:

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


Example 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)会被用作解决未知类型文字的指定类型。


Example 10.3. 绝对值与否定操作符类型决定

PostgreSQL操作符目录中有几个对于前缀操作符@的条目, 这些都现实了针对各种数字数据类型的绝对值操作。其中之一用于float8类型,它是在数字分类中的首选类型。 因此,PostgreSQL将在遇到一个unknown输入时使用它:

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

在这里,系统在应用所选操作符之前已经隐式地解决了将未知类型文字作为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)


Example 10.4. 数组包含操作符类型决定

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

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

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

PostgreSQL操作符目录有一些条目用于中缀操作符<@,但是仅有的两个可以在左手边接受一个整数数组的是数组包含(anyarray <@ anyarray)和范围包含(anyelement <@ anyrange)。因为这些多态伪类型(见Section 8.21)中没有一个被认为是首选的,解析器不能以此为基础来解决歧义。不过,Step 3.f告诉它假定位置类型的文字和其他输入的类型相同,即整数数组。现在这两个操作符中只有一个可以匹配,因此数组包含被选择(如果选择范围包含,我们将得到一个错误,因为该字符串没有成为一个范围文字的正确格式)。


Example 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操作符( Step 2.a),当然这里没有; 然后它将会考虑该域的基类型text,并且看看是否有一 个text = text操作符( Step 2.b),这里也没有;因 此它会把unknown-类型文字解析为text 并使用text = text操作符。 让自定义操作符能被使用的唯一方法是显式地转换改文字:

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

这样根据准确匹配规则会立即找到 mytext = text操作符。如果 到达最佳匹配规则,它们会积极地排斥域类型上的操作符。如果它 们没有,这样一个操作符将创建太多歧义操作符失败,因为转换规 则总是认为一个域可以和它的基类型相互转换,并且因此该域操作 符在所有与该基类型上的一个类似命名的操作符相同的情况中都被 认为可用。




[8] 对非方案限定的名称,不会出现这种危害,因为包含允许不可信用户创建对象的方案的搜索路径不是一种安全的方案使用模式