Skip to content

10.2. 运算符#

使用以下过程确定运算符表达式引用的特定运算符。请注意,此过程间接受到涉及运算符的优先级的影像,因为这将决定哪些子表达式被视为哪些运算符的输入。有关详细信息,请参阅第 4.1.6 节

运算符类型解析

  1. pg_operator 系统目录中选择要考虑的运算符。如果使用了非模式限定运算符名称(通常情况),则考虑的运算符是具有匹配名称和参数计数的运算符,并且在当前搜索路径中可见(请参阅 第 5.9.3 节)。如果给出了限定运算符名称,则只考虑指定模式中的运算符。

    1. 如果搜索路径找到具有相同参数类型的多个运算符,则只考虑在路径中最早出现的运算符。具有不同参数类型的运算符被视为同等,而不管搜索路径位置如何。

  2. 检查是否接受完全输入参数类型的运算符。如果存在(在考虑的运算符集中只能有一个完全匹配),则使用它。在通过限定名称 [9](不典型)调用时,缺乏完全匹配会产生安全隐患,在模式中找到的任何运算符都允许不受信任的用户创建对象。在这种情况下,将参数强制转换为完全匹配。

    1. 如果二元运算符调用的一个参数是 unknown 类型,则假设它是与其他参数相同的类型以进行此检查。涉及两个 unknown 输入的调用,或具有 unknown 输入的前缀运算符,将永远不会在此步骤中找到匹配项。

    2. 如果二元运算符调用的一个参数是 unknown 类型,而另一个是域类型,则接下来检查是否存在在两侧都接受域基本类型的运算符;如果存在,则使用它。

  3. 寻找最佳匹配。

    1. 丢弃输入类型不匹配且无法转换(使用隐式转换)以匹配的候选运算符。为此目的,假设 unknown 文字可转换为任何内容。如果只保留一个候选,则使用它;否则继续执行下一步。

    2. 如果任何输入参数是域类型,则将其视为在所有后续步骤中为域的基本类型。这确保域在解决模棱两可的运算符时表现得像其基本类型。

    3. 遍历所有候选,保留与输入类型最精确匹配的候选。如果没有候选具有精确匹配,则保留所有候选。如果仅剩一个候选,则使用它;否则继续执行下一步。

    4. 遍历所有候选,保留在需要类型转换的位置最多接受首选类型(输入数据类型的类型类别)的候选。如果没有候选接受首选类型,则保留所有候选。如果仅剩一个候选,则使用它;否则继续执行下一步。

    5. 如果任何输入参数为 unknown,请检查剩余候选在这些参数位置接受的类型类别。在每个位置,如果任何候选接受该类别,则选择 string 类别。(这种偏向字符串是合适的,因为未知类型文字看起来像字符串。)否则,如果所有剩余候选接受相同的类型类别,则选择该类别;否则失败,因为无法在没有更多线索的情况下推断出正确的选择。现在,丢弃不接受所选类型类别的候选。此外,如果任何候选在该类别中接受首选类型,则丢弃为该参数接受非首选类型的候选。如果没有任何候选通过这些测试,则保留所有候选。如果仅剩一个候选,则使用它;否则继续执行下一步。

    6. 如果既有 unknown 也有已知类型参数,并且所有已知类型参数具有相同的类型,则假设 unknown 参数也具有该类型,并检查哪些候选可以在 unknown-argument 位置接受该类型。如果恰好有一个候选通过此测试,则使用它。否则,失败。

下面是一些示例。

示例 10.1. 平方根运算符类型解析

标准目录中仅定义了一个平方根运算符(前缀|/),它采用类型为double precision的参数。扫描程序将初始类型integer分配给此查询表达式中的参数

SELECT |/ 40 AS "square root of 40";
 square root of 40
-------------------
 6.324555320336759
(1 row)

因此,解析器对操作数执行类型转换,并且查询等效于

SELECT |/ CAST(40 AS double precision) AS "square root of 40";

示例 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)

在此,系统在应用所选运算符之前已将未知类型文字隐式解析为类型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.21 节)都不被认为是首选,因此解析器无法在此基础上解决歧义。但是,步骤 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运算符。如果达到最佳匹配规则,它们会主动区别对待域类型上的运算符。如果它们没有区别对待,此类运算符将创建太多歧义运算符失败,因为转换规则始终将域视为可转换为基本类型或从基本类型转换,因此域运算符将被视为在所有情况下都可与基本类型上同名运算符一起使用。


[9]由于搜索路径包含允许不受信任的用户创建对象的模式,因此,非模式限定名称不会出现危险,因为这并不是安全的模式使用模式