操作符表达式所引用的具体操作符按照下述过程确定。注意,该过程还会间接受到相关操作符优先级的影响,因为优先级决定了哪些子表达式会被视为哪些操作符的输入。详见Section 4.1.6。
操作符类型解析
从系统目录pg_operator中选取要考虑的操作符。如果使用的是未带模式限定的操作符名(通常如此),则考虑当前搜索路径中可见且名称和参数个数匹配的操作符(见Section 5.10.3)。如果给出了限定名,则只考虑指定模式中的操作符。
如果搜索路径中找到多个参数类型完全相同的操作符,只考虑路径中最早出现的那一个。参数类型不同的操作符则不受搜索路径位置影响,处于同等地位。
检查是否存在一个恰好接受输入参数类型的操作符。如果存在(在被考虑的操作符集合中至多只有一个精确匹配),就使用它。若通过限定名调用(这种情况并不常见)某个允许不可信用户创建对象的模式中的操作符,而又缺少精确匹配,就会带来安全隐患 [9] 。在这种情况下,应通过对参数进行类型转换来强制获得精确匹配。
如果二元操作符调用的一个参数是unknown类型,则在本次检查中假定它与另一个参数类型相同。涉及两个unknown输入的调用,或带有unknown输入的前缀操作符,在这一步都不可能找到匹配。
如果二元操作符调用的一个参数是unknown类型,而另一个是域类型,则接着检查是否存在两边都恰好接受该域基类型的操作符;如果有,就使用它。
寻找最佳匹配。
丢弃那些输入类型不匹配,且也无法通过隐式类型转换实现匹配的候选操作符。为此,假定unknown文字可以转换为任何类型。如果只剩一个候选操作符,就使用它;否则继续下一步。
如果任何输入参数是域类型,则在后续所有步骤中都把它当作该域的基类型。这样可以确保在解决操作符歧义时,域的行为与其基类型一致。
遍历所有候选操作符,保留那些在输入类型上精确匹配数最多的候选项。如果没有任何候选项存在精确匹配,则保留全部候选项。如果只剩一个候选操作符,就使用它;否则继续下一步。
遍历所有候选操作符,保留那些在需要类型转换的最多位置上接受首选类型(属于输入数据类型的类型分类)的候选项。如果没有候选项接受首选类型,则保留全部候选项。如果只剩一个候选操作符,就使用它;否则继续下一步。
如果有任何输入参数是unknown,就检查剩余候选操作符在这些参数位置上接受哪些类型分类。在每个位置上,如果有任何候选项接受string分类,就选择该分类。(这种偏向字符串的做法是合适的,因为unknown类型的文字看起来像字符串。)否则,如果所有剩余候选项都接受同一种类型分类,就选择该分类;否则失败,因为在缺少更多线索的情况下无法推断出正确选择。然后丢弃不接受所选类型分类的候选项。此外,如果有任何候选项接受该分类中的首选类型,就丢弃在该参数上接受非首选类型的候选项。如果没有候选项通过这些测试,则保留全部候选项。如果只剩一个候选操作符,就使用它;否则继续下一步。
如果同时存在unknown参数和已知类型参数,且所有已知类型参数都具有相同类型,则假定unknown参数也属于该类型,并检查哪些候选项能在unknown参数位置接受该类型。如果恰好只有一个候选项通过测试,就使用它;否则失败。
下面是一些示例。
Example 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";
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作为解析这些unknown类型文字的具体类型。
Example 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)
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告诉它可以假定unknown类型文字与另一输入具有相同类型,也就是整数数组。这样两个操作符中只有一个能够匹配,因此会选择数组包含。(如果选中范围包含,就会报错,因为该字符串的格式并不是合法的范围文字。)
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操作符。如果解析过程进入最佳匹配规则,它们会主动排斥作用于域类型的操作符。如果不是这样,这类操作符会导致过多的操作符歧义失败,因为类型转换规则总是把域视为可以转换到其基类型或从其基类型转换而来,因此域操作符会在所有与基类型上同名操作符相同的场景中都被视为可用。
如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。