10.2. Operators

使用以下过程确定由运算符表达式引用的特定运算符。请注意,此过程受所涉及运算符的优先级间接影响,因为这将确定将哪些子表达式作为哪个运算符的 Importing。有关更多信息,请参见Section 4.1.6

操作员类型分辨率

  • pg_operator系统目录中选择要考虑的运算符。如果使用了非模式限定的运算符名称(通常情况),则考虑的运算符是具有在当前搜索路径中可见的名称和参数计数匹配的运算符(请参见Section 5.8.3)。如果给出了合格的运算符名称,则仅考虑指定架构中的运算符。

  • 如果搜索路径找到具有相同参数类型的多个运算符,则仅考虑路径中最早出现的一个。无论搜索路径位置如何,都将在同等基础上考虑具有不同参数类型的运算符。

  • 检查运算符是否完全接受 Importing 参数类型。如果存在(在所考虑的一组运算符中只有一个完全匹配),请使用它。当通过限定名称[8](非典型值)调用允许允许不受信任的用户创建对象的架构中的任何运算符时,缺少完全匹配项都会造成安全隐患。在这种情况下,强制转换参数以强制完全匹配。

  • 如果二进制运算符调用的一个参数为unknown类型,则假定它与此检查的另一参数为同一类型。涉及两个unknownImporting 或具有unknownImporting 的一元运算符的调用在此步骤将永远找不到匹配项。

    • 如果二进制运算符调用的一个参数是unknown类型,而另一个参数是域类型,则下一步检查是否有一个运算符在两侧均接受该域的基本类型。如果是这样,请使用它。
  • 寻找最佳搭配。

  • 丢弃 Importing 类型不匹配且无法转换(使用隐式转换)匹配的候选运算符。为此,假定unknownLiterals 可转换为任何东西。如果只剩下一名候选人,请使用它;否则 continue 下一步。

    • 如果任何 Importing 参数属于域类型,则在所有后续步骤中都将其视为域的基本类型。这样可以确保域的行为像其基本类型一样,以解决歧义运算符。

    • 遍历所有候选者,并保留与 Importing 类型最匹配的候选者。如果没有完全匹配的候选人,则保留所有候选人。如果只剩下一名候选人,请使用它;否则 continue 下一步。

    • 遍历所有候选项,并将那些接受首选类型(Importing 数据类型的类别)的候选项保留在需要进行类型转换的大多数位置。如果没有候选人接受首选类型,则保留所有候选人。如果只剩下一名候选人,请使用它;否则 continue 下一步。

    • 如果任何 Importing 自变量是unknown,请检查其余自变量在这些自变量位置接受的类型类别。如果有候选人接受该类别,请在每个位置选择string类别。 (这种偏向字符串的做法是适当的,因为未知类型的 Literals 看起来像字符串.)否则,如果所有其他候选都接受相同的类型类别,则选择该类别;否则,如果所有其他候选都接受相同的类型类别,则选择该类别。否则失败,因为没有更多线索就无法推断出正确的选择。现在,丢弃不接受所选类型类别的候选对象。此外,如果任何候选者接受该类别中的首选类型,则丢弃该参数接受非首选类型的候选者。如果没有任何候选人在这些测试中幸存下来,请保留所有候选人。如果只剩下一名候选人,请使用它;否则 continue 下一步。

    • 如果同时具有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)

在这种情况下,由于没有在查询中指定任何类型,因此没有使用哪种类型的初始提示。因此,解析器将查找所有候选运算符,并发现存在同时接受字符串类别和位字符串类别 Importing 的候选者。由于字符串类别在可用时是首选的,因此将选择该类别,然后将字符串的首选类型text用作特定类型,以将未知类型的 Literals 解析为。

例 10.3. 绝对值和负运算符类型解析

PostgreSQL 运算符目录中有几个前缀运算符@的条目,所有条目都对各种数字数据类型实现绝对值运算。这些条目之一用于类型float8,这是数字类别中的首选类型。因此,当遇到unknownImporting 时,PostgreSQL 将使用该条目:

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

在这里,系统在应用所选的运算符之前已将未知类型的 Literals 隐式解析为类型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. 数组包含运算符类型解析

这是使用一个已知 Importing 和一个未知 Importing 来解析运算符的另一个示例:

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

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

PostgreSQL 运算符目录中有几个用于 infix 运算符<@的条目,但是可能在左侧接受整数数组的仅有两个条目是数组包含(anyarray <@ anyarray)和范围包含(anyelement <@ anyrange)。由于这些多态伪类型(请参见Section 8.20)都不被认为是首选,因此解析器无法在此基础上解决歧义。但是,Step 3.f告诉它假定未知类型 Literals 与其他 Importing(即整数数组)具有相同的类型。现在,只有两个运算符之一可以匹配,因此选择了数组包含。 (如果选择了范围内包含,我们将得到一个错误,因为字符串的格式不正确,不能作为范围 Literals.)

实施例 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类型的 Literals 解析为text并使用text = text运算符。获取要使用的自定义运算符的唯一方法是显式转换 Literals:

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

以便根据完全匹配规则立即找到mytext = text运算符。如果达到最佳匹配规则,则它们会积极区分域类型上的运算符。否则,这样的运算符会造成太多的模棱两可的运算符故障,因为强制转换规则始终将域视为可从其基本类型或从其基本类型强制转换的域,因此在与域类型相同的所有情况下,域运算符都将被视为可用基本类型上名称相似的运算符。


[8]使用非模式限定的名称不会引起危险,因为包含允许不信任用户创建对象的架构的搜索路径不是安全模式使用模式