用于引用算符表达式的特定算符使用以下过程来确定。请注意,该过程会间接受到相关算符优先级的影响,因为这将决定哪些子表达式被视为哪些算符的输入。有关更多信息,请参见第 4.1.6 节。
算符类型解析
从 pg_operator
系统目录中选择要考虑的算符。如果使用了非模式限定的算符名称(通常情况下),则考虑的是那些在当前搜索路径中可见、名称和参数计数匹配的算符(参见第 5.10.3 节)。如果给出了限定算符名称,则只考虑指定模式中的算符。
如果搜索路径找到具有相同参数类型的多个算符,则只考虑在路径中最早出现的算符。具有不同参数类型的算符被视为平等,而不管搜索路径位置如何。
检查接受输入参数类型的算符。如果存在(在所考虑的算符集中只有一个完全匹配),则使用它。在调用中缺少完全匹配会产生安全隐患,通过限定名[9](不常见),任何在允许不受信任的用户创建对象的模式中找到的算符。在这种情况下,将 cast 参数强制为完全匹配。
如果二元算符调用的一个参数为 unknown
类型,那么假设它是与该检查中另一个参数相同的类型。涉及两个 unknown
输入的调用,或者一个带有 unknown
输入的前缀运算符,将永远不会在此步骤中找到匹配项。
如果二元算符调用的一个参数为 unknown
类型,另一个参数为域类型,则接下来检查是否有算符在两边都接受域的基本类型;如果存在,则使用它。
查找最佳匹配项。
丢弃输入类型不匹配并且无法转换(使用隐式转换)以匹配的候选算符。对于此目的,假定 unknown
文字可转换为任何内容。如果只保留一个候选者,则使用它;否则继续下一步骤。
如果任何输入参数是域类型,则在所有后续步骤中将其视为域基本类型。这确保域在解析二义性算符时如同其基本类型一样。
遍历所有候选项,并保留那些与输入类型最匹配的候选项。如果没有任何候选项完全匹配,则保留所有候选项。如果只有一个候选项保留下来,则使用它;否则继续到下一步。
遍历所有候选项,并保留那些在需要类型转换的最多的位置接受首选类型(输入数据类型的类型类别)的候选项。如果没有任何候选项接受首选类型,则保留所有候选项。如果只有一个候选项保留下来,则使用它;否则继续到下一步。
如果任何输入参数都是 unknown
,则检查剩余候选项在那些参数位置接受的类型类别。在每个位置,如果任何候选项接受此类别,则选择 string
类别。(由于未知类型的文字看起来像字符串,因此此偏向字符串是合适的。)否则,如果所有剩余的候选项接受相同的类型类别,则选择该类别;否则,由于无法推断出正确的选择而失败,除非获得更多线索。现在,丢弃不接受所选类型类别的候选项。此外,如果任何候选项接受该类别中的一种首选类型,则丢弃接受该参数的非首选类型的候选项。如果没有候选项通过这些测试,则保留所有候选项。如果只有一个候选项保留下来,则使用它;否则继续到下一步。
如果既有 unknown
类型参数,也有已知类型参数,并且所有已知类型参数都是同一种类型,则假定 unknown
参数也是该类型,并检查哪些候选项可以在 unknown
参数位置接受该类型。如果正好有一个候选项通过此测试,则使用它。否则会失败。
这里有一些示例。
示例 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
,这是数字范畴中的首选类型。因此,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)
示例 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
运算符。如果达到最佳匹配规则,它们会主动区别对待域类型上的运算符。如果不这样做,此类运算符会创建过多的运算符模糊错误,因为转换规则总是将域视为可转换到或从其基本类型转换,因此在与基本类型上同名运算符的所有情况下都会认为域运算符可用。