类型转换
概述
SQL 是一种强类型语言。也就是说,每个数据项都有一个相关的数据类型,数据类型决定其行为和允许的用法。 AntDB 有一个可扩展的类型系统,该系统比其它 SQL 实现更具通用和灵活。因而,AntDB 中大多数类型转换行为是由通用规则来管理的,而不是 ad hoc 启发式规则。这种做法允许使用混合类型表达式,即便是其中包含用户定义的类型。
AntDB 扫描器/解析器只将词法元素分解成五个基本种类:整数、非整数数字、字符串、标识符、关键字。大多数非数字类型常量首先被分类为字符串。SQL 语言定义允许将类型名指定为字符串, 这个机制被 AntDB 用于保证解析器沿着正确的方向运行。例如,查询:
SELECT text 'Origin' AS "label", point '(0,0)' AS "value";
label | value
--------+-------
Origin | (0,0)
(1 row)
有两个文字常量,类型分别为 text
和 point
。如果一个串文字没有指定类型,初始将被分配一个占位符类型 unknown
,该类型将在下文描述的后续阶段被解析。
在 SQL 解析器里,有四种基本的 SQL 结构要求独立的类型转换规则:
-
函数调用
AntDB 类型系统的大部分建立在一套丰富的函数上。 函数可以有一个或多个参数。由于 AntDB 允许函数重载, 所以函数名自身并不唯一地标识将要被调用的函数,解析器必须根据提供的参数类型选择正确的函数。
-
操作符
AntDB 允许带有前缀和后缀一元(单目)操作符的表达式,也允许二元(两个参数)操作符。像函数一样,操作符也可以被重载,因此操作符的选择也有同样的问题。
-
值存储
SQL
INSERT
和UPDATE
语句将表达式的结果放 入表中。语句中的表达式类型必须和目标列的类型一致(或者可以被转换为一致)。 -
UNION
、CASE
和相关结构因为来自一个联合的
SELECT
语句中的所有查询结果必须在一个列集中显示,所以每个SELECT
子句的结果类型必须能相互匹配并被转换成一个统一的集合。类似地,一个CASE
结构的结果表达式必须被转换成一种公共的类型,这样CASE
表达式作为整体才有一种已知的输出类型。其他一些结构,例如ARRAY[]
和GREATEST
和LEAST
函数,同样需要确定几个子表达式的公共类型。
系统目录存储有关哪些数据类型之间存在哪种转换(或造型)以及如何执行这些转换的相关信息。额外的造型可以由用户通过 CREATE CAST 命令增加(这个通常和定义一种新的数据类型一起完成。 内建的类型转换集已经经过了仔细的雕琢,最好不要去更改它们)。
解析器提供了一种额外的启发式规则,它允许在具有隐式造型的类型组中恰当造型行为的改进决定。 数据类型被分为几个基本的类型分类,包括 boolean
、numeric
、string
、bitstring
、datetime
、timespan
、geometric
、network
和用户自定义。在每个分类中,可以有一个或多个首选类型, 当存在类型选择时,这个是更好的选择。利用精心选择的首选类型和可用的隐式造型, 可以确保有歧义的表达式(那些有多个候选解析方案的表达式)可以用一种有用的方式来处理。
所有类型转换规则都是建立在下面几个基本原则上的:
- 隐式转换决不能有意外的或不可预见的输出。
- 如果一个查询不需要隐式类型转换,解析器或执行器不应该有额外的开销。也就是说,如果一个查询是结构良好的并且类型已经匹配,则查询不应该在解析器里耗费额外的时间执行,也不会在查询中引入不必要的隐式类型转换调用。
- 另外,如果一个查询通常要求为某个函数进行隐式类型转换,而用户定义了一个有正确参数类型的新函数, 解析器应该使用新函数并不再做隐式转换来使用旧函数。
操作符
被一个操作符表达式引用的特定操作符由下列过程决定。注意这个过程会被所涉及的操作符的优先级间接地影响,因为这将决定哪些子表达式被用作哪个操作符的输入。
操作符类型决定
-
从系统目录
pg_operator
中选出要考虑的操作符。如果使用了一个不带模式限定的操作符 名(常见的情况),那么操作符被认为是那些在当前搜索路径中可见并有匹配的名字和参数个数的操作符。如果给出一个被限定的操作符名,那么只考虑指定模式中的操作符。- 如果搜索路径找到了多个有相同参数类型的操作符,那么只考虑最早出现在路径中的那一个。但是不同参数类型的操作符将被平等看待,而不管它们在路径中的位置如何。
-
查找一个正好接受输入参数类型的操作符。如果找到一个(在一组被考虑的操作符中,可能只存在一个正好匹配的),则使用之。在通过限定名称(非典型)调用在一个允许不可信用户创建对象的方案中找到的任意操作符时,精确匹配的缺失会导致安全性危害。在这样的情况下,应该造型参数以便强制一次精确匹配。
-
如果一个二元操作符调用中的一个参数是
unknown
类型,则在本次检查中假设它与另一个参数类型相同。 对于涉及两个unknown
输入的调用或者带有一个unknown
输入的一元操作符,在这一步将永远找不到一个匹配。 -
如果一个二元操作符调用的其中一个参数是
unknown
类型 而另一个是一种域类型,下一次检查会看看是否有一个操作符正好在两边都 接受该域的基类型,如果有就使用它。
-
-
寻找最优匹配。
- 抛弃那些输入类型不匹配并且也不能被转换成匹配的候选操作符。
unknown
文字被假定为可以为这个目的被转换为任何东西。如果只剩下一个候选操作符,则使用之,否则继续下一 步。 - 如果任何输入参数是一种域类型,对所有后续步骤都把它当做是该 域的基类型。这确保在做有歧义的操作符解析时,域的举止像它们 的基类型。
- 遍历所有候选操作符,保留那些在输入类型上的匹配最准确的。如果没有一个操作符能准确匹配,则保留所有候选。如果只剩下一个候选操作符,则使用之,否则继续下一步。
- 遍历所有候选操作符,保留那些在最多个需要类型转换的位置上接受首选类型(属于输入数据类型的类型分类)的操作符。如果没有接受首选类型的操作符,则保留所有候选。如果只剩下一个候选操作符,则使用之, 否则继续下一步。
- 如果有任何输入参数是
unknown
类型,检查被剩余候选操作符在那些参数位置上接受的类型分类。 在每一个位置,如果任何候选接受该分类,则选择string
分类(这种对字符串的偏爱是合适的, 因为未知类型的文本确实像字符串)。否则,如果所有剩下的候选操作符都接受相同的类型 分类,则选择该分类;否则抛出一个错误(因为在没有更多线索的条件下无法作出正确 的推断)。现在抛弃不接受选定的类型分类的候选操作符。然后,如果任意候选操作符接受那个分类中的首选类型, 则抛弃那些在该参数位置接受非首选类型的候选操作符。如果没有候选操作符能通过这些测试则保留全部候选者。如果只剩下一个候选者,则使用之;否则继续下一步。 - 如果既有
unknown
参数也有已知类型的参数,并且所有已知类型参数具有相同的类型,则假定该unknown
参数也是那种类型的,并且检查哪些候选操作符可以在该unknown
参数的位置上接受那个类型。如果正好有一个候选者通过了这个测试,则使用之;否则失败。
- 抛弃那些输入类型不匹配并且也不能被转换成匹配的候选操作符。
下面是一些例子。
举例:平方根运算符类型解析
在标准目录中只有一个被定义的平方根操作符(前缀|/
),它接受一个类型为 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";
举例:字符串连接操作符类型决定
一个类字符串的语法被用来处理字符串类型和处理复杂的扩展类型。未指定类型的字符串与可能的候选操作符匹配。
一个未指定参数的例子:
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
)会被用作解决未知类型文字的指定类型。
举例:绝对值与否定操作符类型决定
AntDB 操作符目录中有几个对于前缀操作符@
的条目, 这些都现实了针对各种数字数据类型的绝对值操作。其中之一用于 float8
类型,它是在数字分类中的首选类型。 因此,AntDB 将在遇到一个 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)
举例:数组包含操作符类型决定
这里是另一个决定带有一个已知和一个未知输入的操作符的例子:
SELECT array[1,2] <@ '{1,2,3}' as "is subset";
is subset
-----------
t
(1 row)
举例:域类型上的自定义操作符
用户有时会尝试声明只适用于一种域类型的操作符。这是可能的, 但是远非它看起来那么有用,因为操作符解析规则被设计为选择 适用于域的基类型的操作符。考虑这个例子:
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
操作符,当然这里没有; 然后它将会考虑该域的基类型 text
,并且看看是否有一 个 text
=
text
操作符,这里也没有;因 此它会把unknown
-类型文字解析为 text
并使用 text
=
text
操作符。让自定义操作符能被使用的唯一方法是显式地转换改文字:
SELECT * FROM mytable WHERE val = text 'foo';
这样根据准确匹配规则会立即找到 mytext
=
text
操作符。如果 到达最佳匹配规则,它们会积极地排斥域类型上的操作符。如果它 们没有,这样一个操作符将创建太多歧义操作符失败,因为转换规 则总是认为一个域可以和它的基类型相互转换,并且因此该域操作 符在所有与该基类型上的一个类似命名的操作符相同的情况中都被 认为可用。
函数
被一个函数调用引用的特定函数使用下面的过程来决定。
函数类型决定
-
从
pg_proc
系统目录中选择要被考虑的函数。 如果使用一个非模式限定的函数名称,那么函数被认为是那些在当前搜索路径中可见并有匹配的名字和参数个数的函数。如果给出一个被限定的函数名,那么只考虑指定模式中的函数。-
如果搜索路径发现多个参数类型相同的函数,那么只考虑最早在搜索路径中出现的那个。不同参数类型的函数被平等对待,不受在搜索路径中位置的影响。
-
如果使用一个
VARIADIC
数组参数声明一个函数,并且调用不使用关键字VARIADIC
, 那么该函数就好像其数组参数被它的元素类型的一次或多次出现所替换,根据需要去匹配调用。 这样的扩展之后,函数可能会有和非可变函数相同的参数类型。在这种情况下,在搜索路径中出现比较早的函数将被使用,或者如果两个函数在相同的模式中时首选非可变的那一个。在通过限定名称调用在一个允许不可信用户创建对象的方案中找到的可变函数时,会导致安全性危害。 恶意用户可以拿到控制权并且执行任意 SQL 函数(就好像在执行它们一样)。将涉及
VARIADIC
关键词的调用替换掉就可以绕过这种危害。涉及到VARIADIC "any"
参数的调用通常没有等效的包含VARIADIC
关键词的形式。为了安全地发出那些调用,函数的方案必须只允许可信用户创建对象。 -
考虑使用有默认参数值的函数来匹配任何省略了零个或者多个可默认参数位置的调用。如果有超出一个的这种函数匹配一个调用,那么使用最早出现在搜索路径中的那个。如果同一个模式中在同一个非默认位置上有两个或者更多这样的函数(如果它们有 不同的默认参数设置,这是可能的),系统将不能确定去选择哪一个,并且如果不能找到该调用更好的匹配,将会导致一个“有歧义的函数调用” 错误。
在通过限定名称 [9]调用在一个允许不可信用户创建对象的方案中找到的任意函数时,会导致可用性危害。恶意用户可以用一个已有函数的名称创建一个函数,复制该函数的参数并且追加新的具有默认值的参数。这会妨碍对原始函数的新调用。为了防止这种危害,应将函数放在仅允许可信用户创建对象的方案中。
-
-
检查一个函数正好接受输入参数类型。如果存在一个(在所考虑的一组函数中只能有一个准确匹配),则使用之。在通过限定名称[9]调用在一个允许不可信用户创建对象的方案中找到的函数时,精确匹配的缺失会导致安全性危害。在这样的情况下,应该造型参数以便强制一次精确匹配(在该步骤中,涉及
unknown
的情况将永远找不到一个匹配)。 -
如果没有发现准确匹配,那么查看函数调用是否作为一个特定的类型转换请求出现。 如果函数调用仅有一个参数并且函数名和一些数据类型的(内部)名称相同,那么该情况将会发生。 并且,该函数参数必须是一个未知类型的文字,或者是一个可以被二进制强制转换到命名数据类型的类型, 或者是一个可以通过应用其 I/O 函数被转换为命名数据类型的类型(也就是,转换是转到标准字符串类型或者从标准字符串类型转来)。当满足这些条件的时候,函数调用被当做
CAST
声明的一种形式来对待。 [10] -
查找最佳匹配。
- 如果候选函数的输入类型不匹配并且不能通过转换(使用一个隐式转换)达到匹配,则丢弃它。为了这个目的,
unknown
文字被假定可被转换成任何东西。如果仅有一个候选项,则使用之;否则继续下一步。 - 如果任何输入参数是一种域类型,在所有后续步骤中都把它当做 该域的基类型。这确保在做有歧义的操作符解析时,域的举止像它们的基类型。
- 遍历所有候选函数并保留那些最匹配输入类型的。如果没有准确匹配,则保留所有候选项。 如果仅有一个候选项,则使用之;否则继续下一步。
- 遍历所有候选函数并保留那些在最多要求类型转换的位置上接受首选类型(属于输入数据类型的类型分类)的候选项。如果没有接受首选类型的候选项,则保留所有候选项。如果仅有一个候选项,则使用之;否则继续下一步。
- 如果任何输入参数是
unknown
,那么检查那些被剩余候选项在那些参数位置上接受的类型分类。在每一个位置上,如果任何候选项接受该分类则选择string
分类 (这个偏向于字符串是恰当的,因为一个未知类型文字看起来像字符)。 否则,如果所有剩余的候选项接受相同的类型分类,那么选择那个分类;否则将失败,因为缺乏更多线索来推断出正确的选择。现在,丢弃不接受被选中类型分类的候选项。此外,如果任何候选项接受那个分类中的一个首选类型,则丢弃对该参数接受非首选类型的候选项。如果没有候选项能通过这些测试,则保留所有候选项。如果只剩下一个候选项,则使用之;否则继续下一步。 - 如果既有
unknown
参数也有已知类型的参数,并且所有已知类型参数具有相同的类型,则假定该unknown
参数也是那种类型的,并且检查哪些候选函数可以在该unknown
参数的位置上接受那个类型。如果正好有一个候选者通过了这个测试,则使用之;否则失败。
- 如果候选函数的输入类型不匹配并且不能通过转换(使用一个隐式转换)达到匹配,则丢弃它。为了这个目的,
注意,对于操作符和函数类型决定来说“最优匹配”规则是完全相同的。下面是一些例子。
举例:圆整函数参数类型决定
只有一个带有两个参数的圆整
函数; 它采用第一个参数类型为 numeric
和第二个参数类型为 integer
。这样下面的查询自动将第一个类型为 integer
参数转换为 numeric
:
SELECT round(4, 4);
round
--------
4.0000
(1 row)
该查询实际上被解析器转换为:
SELECT round(CAST (4 AS numeric), 4);
因为包含小数点的数字常数初始会被分配类型 numeric
,下面的查询将不需要类型转换并因此可能会稍稍高效一些:
SELECT round(4.0, 4);
举例:可变函数决定
CREATE FUNCTION public.variadic_example(VARIADIC numeric[]) RETURNS int
LANGUAGE sql AS 'SELECT 1';
CREATE FUNCTION
这个函数接受(但不要求)VARIADIC 关键词。它能同时容忍 integer 以 numeric 参数:
SELECT public.variadic_example(0),
public.variadic_example(0.0),
public.variadic_example(VARIADIC array[0.0]);
variadic_example | variadic_example | variadic_example
------------------+------------------+------------------
1 | 1 | 1
(1 row)
不过,如果可以,第一个和第二个调用将更喜欢更明确的函数:
CREATE FUNCTION public.variadic_example(numeric) RETURNS int
LANGUAGE sql AS 'SELECT 2';
CREATE FUNCTION
CREATE FUNCTION public.variadic_example(int) RETURNS int
LANGUAGE sql AS 'SELECT 3';
CREATE FUNCTION
SELECT public.variadic_example(0),
public.variadic_example(0.0),
public.variadic_example(VARIADIC array[0.0]);
variadic_example | variadic_example | variadic_example
------------------+------------------+------------------
3 | 2 | 1
(1 row)
如果给定默认的配置并且只有第一个函数存在,则第一个和第二个调用是不安全的。任何用户都可以通过创建第二个或者第三个函数来截断它们。通过精确匹配参数类型并且使用 VARIADIC
关键词,第三个调用是安全的。
举例:子串函数类型决定
有几个 substr
函数,其中一个用于 text
和 integer
类型。如果使用一个未指定类型的字符常量调用,那么系统选择接受一个首选分类 string
(也就是 text
类型)的参数的候选函数。
SELECT substr('1234', 3);
substr
--------
34
(1 row)
如果字符串被声明为类型 varchar
(如果它来自于一个表就会这样),那么解析器将尝试转换它为 text
:
SELECT substr(varchar '1234', 3);
substr
--------
34
(1 row)
解析器所作的转换:
SELECT substr(CAST (varchar '1234' AS text), 3);
注意
解析器从 pg_cast
目录中知道 text
和 varchar
是二进制可兼容的, 意思是其中一个可以被传递给接受另一种类型的函数而不需要做任何物理转换。因此,在这种情况下不会真正使用类型转换调用。
并且,如果该函数使用一个 integer
类型的参数调用,那么解析器将尝试将它转换为 text
:
SELECT substr(1234, 3);
ERROR: function substr(integer, integer) does not exist
HINT: No function matches the given name and argument types. You might need
to add explicit type casts.
由于 integer
类型没有到 text
的一个隐式造型,这将不会工作。但是一次显式造型则可以工作:
SELECT substr(CAST (1234 AS text), 3);
substr
--------
34
(1 row)
值存储
将被插入到一个表的值会按照下列步骤被转换到目标列的数据类型。
值存储类型转换
- 检查一个与目标的准确匹配。
- 否则,尝试转换表达式为目标类型。如果在两种类型之间的一个 赋值造型已经被注册在
pg_cast
目录中, 这是可能的。或者,如果该表达式是一个未知类型的文字, 则该文字串的内容将被提供给目标类型的输入转换例程。 - 检查是否有一个用于目标类型的尺寸调整造型。尺寸调整造型是一个从该类型到其自身的造型。 如果在
pg_cast
目录中找到一个,那么把表达式存储到目标列中之前把它应用到表达式。这样一个造型的实现函数总是采用一个额外的integer
类型的参数,它接收目标列的atttypmod
值(通常是它被声明的长度,尽管对于不同数据类型atttypmod
有不同的解释),并且它可能采用第三个boolean
参数来说明造型是显式的还是隐式的。该造型函数负责应用任何长度相关的语义,例如尺寸检查或截断。
举例:character
存储类型转换
对于一个声明为 character(20)
的目标列,下面的语句展示了被存储的值如何被正确地调整尺寸:
CREATE TABLE vv (v character(20));
INSERT INTO vv SELECT 'abc' || 'def';
SELECT v, octet_length(v) FROM vv;
v | octet_length
----------------------+--------------
abcdef | 20
(1 row)
实际发生的事情是两个未知文字被默认决定为 text
,允许||
操作符被决定为 text
连接。 然后操作符的 text
结果被转换成 bpchar
(“空白填充字符”,character
数据类型的内部名称)来匹配目标列类型(由于从 text
到 bpchar
的转换是二进制强制的,这个转换不会插入任何实际的函数调用)。最后,尺寸调整函数 bpchar(bpchar, integer, boolean)
被从系统目录中找到并应用到操作符的结果和存储的列长度上。这个类型相关的函数执行必要的长度检查并增加填充的空间。
UNION
、CASE
和相关结构
SQL UNION
结构必须使可能不相似的类型匹配成为一个单一的结果集。该决定算法被独立地应用到一个联合查询的每个输出列。INTERSECT
和 EXCEPT
采用和 UNION
相同的方法来决定不相似的类型。其他一些结构,包括 CASE
、ARRAY
、VALUES
以及 GREATEST
和 LEAST
函数,使用相同的算法来使它们的组成表达式匹配并选择一种结果数据类型。
UNION
、CASE
和相关结构的类型决定
- 如果所有的输入为相同类型,并且不是
unknown
,那么就决定是该类型。 - 如果任何输入是一种域类型,在所有后续步骤中都把它当做 该域的基类型。 [11]
- 如果所有的输入为
unknown
类型,则决定为text
(字符串分类的首选类型)类型。否则,为了剩余规则,unknown
输入会被忽略。 - 如果非未知输入不全是相同的类型分类,则失败。
- 选择第一个非未知输入类型作为候选类型,然后从左到右考虑其他非未知输入类型。 [12] 如果候选类型可以隐式转换为其他类型,但反之不行,则选择其他类型作为新的候选类型。 然后继续考虑剩余的输入。 如果在此过程的任何阶段选择了首选类型,请停止考虑其他输入。
- 将所有输入转换为最终候选类型。如果没有从给定输入类型到候选类型的隐式转换,则失败。
下面是一些例子。
举例:联合中未指定类型的类型决定
SELECT text 'a' AS "text" UNION SELECT 'b';
text
------
a
b
(2 rows)
这里,未知类型文字'b'
将被决定为类型 text
。
举例:简单联合中的类型决定
SELECT 1.2 AS "numeric" UNION SELECT 1;
numeric
---------
1
1.2
(2 rows)
文字 1.2
是 numeric
类型,且 integer
值 1
可以被隐式地造型为 numeric
,因此使用 numeric
类型。
举例:可换位联合中的类型决定
SELECT 1 AS "real" UNION SELECT CAST('2.2' AS REAL);
real
------
1
2.2
(2 rows)
这里,由于类型 real
被能被隐式地造型为 integer
,而 integer
可以被隐式地造型为 real
,联合结果类型被决定为 real
。
举例:嵌套合并中的类型决定
SELECT NULL UNION SELECT NULL UNION SELECT 1;
ERROR: UNION types text and integer cannot be matched
这个失败发生的原因是 AntDB 把多个 UNION
当作是成对操作的嵌套,也就是说上面的输入等同于:
(SELECT NULL UNION SELECT NULL) UNION SELECT 1;
根据上面给定的规则,内层的 UNION
被确定为类型 text
。然后外层的 UNION
的输入是类型 text
和 integer
,这就导致了上面看到的错误。通过确保最左边的 UNION
至少有一个输入类型为想要的结果类型,就可以修正这个问题。
INTERSECT
和 EXCEPT
操作也被当作成对操作。不过,这一节中描述的其他结构会在一个决定步骤中考虑所有的输入。
SELECT
的输出列
前面的小节中给出的规则将会导致对 SQL 查询中的所有表达式分配非 unknown
数据类型,不过作为 SELECT
命令的简单输出列出现的未指定类型的文本除外。例如,在
SELECT 'Hello World';
中没有标识该字符串应该取何种类型。在这种情况下,AntDB 将会退而求其次将其类型决定为 text
。
当 SELECT
处于 UNION
(或者INTERSECT
,或者EXCEPT
)结构的一边或者出现在 INSERT ... SELECT
中时,这条规则就不适用了,因为在前面小节中给出的规则会优先。在第一种情况下未指定类型文本的类型将从 UNION
的另一边取得,而在第二种情况下未指定类型文本的类型将从目标列取得。
出于这样的目的,RETURNING
列表采用和 SELECT
输出列表同样的方式对待。