【ORACLE】对ORACLE说"123木头人",它会正常run么?
连接到:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.13.0.0.0
SQL> select 123木头人 from dual;
木头人
----------
123
背景
我有次在调试某国产数据库,特意制造一些错误来看堆栈情况时,输入了 select 123t;
执行,发现竟然没有报错,我一开始还以为是这个国产库太垃圾,连这种错都没判断出来。但是当我去ORACLE里执行 select 123t from dual
时,发现ORACLE里竟然也没报错!特写本文来记录这种现象。
连接到:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.13.0.0.0
SQL> select 123t from dual;
T
----------
123
分析
从上面这个查询结果来看,初步判断ORACLE是把 123t
中从左往右的数字提取出来作为列值,后面的作为列的别名。
一般情况下,分词需要用空格、回车、换行、括号、逗号等等符号来进行切分,比如 select*
中间不加空格,也是可以认为是两个不同的token的。但是 123t
中间没有任何符号,ORACLE也对其进行了切分,我们模拟想象一下这个分词过程
首先,不带双引号的 123t
肯定不能作为表里的字段名,因为标识符不能以数字开头;
不是标识符,就也不是函数表达式的开头,那么它就是字面值常量;
然后这个字面值常量没带单引号,那么它就是数字类型的字面值常量;
数字只能识别到 123
,词法规则分词,t
被分开了 ,
所以最后的token流就是
0,'select'
1,' '
2,'123'
3,'t'
4,' '
5,'from'
6,' '
7,'dual'
8,';'
- Nonquoted identifiers must begin with an alphabetic character from your database character set. Quoted identifiers can begin with any character.未加引号的标识符必须以数据库字符集中的字母字符开头。加引号的标识符可以以任何字符开头。
- Nonquoted identifiers can only contain alphanumeric characters from your database character set and the underscore (_), dollar sign ($), and pound sign (#). Database links can also contain periods (.) and "at" signs (@). Oracle strongly discourages you from using $ and # in nonquoted identifiers.
未加引号的标识符只能包含数据库字符集中的字母数字字符以及下划线(_)、美元符号($)和井号(#)。数据库链接还可以包含句点(.)和"at"符号(@)。Oracle 强烈建议你不要在未加引号的标识符中使用$和#。
Quoted identifiers can contain any characters and punctuations marks as well as spaces. However, neither quoted nor nonquoted identifiers can contain double quotation marks or the null character (\0
).
加引号的标识符可以包含任何字符、标点符号以及空格。然而,无论是加引号的还是未加引号的标识符都不能包含双引号或空字符( \0 )。
在语法解析过程中,空格、回车、换行这样的token是会被忽略掉的,所以这里123t
中间加不加空格并没有区别。
后面我又使用了antlr4解析了一下这个SQL,发现解析的结果和ORACLE是一样的
select 123t from dual;
^Z
[@0,0:5='select',<'SELECT'>,1:0]
[@1,6:6=' ',<SPACES>,channel=1,1:6]
[@2,7:9='123',<UNSIGNED_INTEGER>,1:7]
[@3,10:10='t',<'T'>,1:10]
[@4,11:11=' ',<SPACES>,channel=1,1:11]
[@5,12:15='from',<'FROM'>,1:12]
[@6,16:16=' ',<SPACES>,channel=1,1:16]
[@7,17:20='dual',<REGULAR_ID>,1:17]
[@8,21:21=';',<';'>,1:21]
[@8,22:23='\r\n',<SPACES>,channel=1,1:22]
[@9,24:23='<EOF>',<EOF>,2:0]
select 123d from dual;
^Z
[@0,0:5='select',<'SELECT'>,1:0]
[@1,6:6=' ',<SPACES>,channel=1,1:6]
[@2,7:10='123d',<APPROXIMATE_NUM_LIT>,1:7]
[@3,11:11=' ',<SPACES>,channel=1,1:11]
[@4,12:15='from',<'FROM'>,1:12]
[@5,16:16=' ',<SPACES>,channel=1,1:16]
[@6,17:20='dual',<REGULAR_ID>,1:17]
[@7,21:21=';',<';'>,1:21]
[@8,22:23='\r\n',<SPACES>,channel=1,1:22]
[@9,24:23='<EOF>',<EOF>,2:0]
但数字字面量的格式不仅有纯数字,根据Oracle官方文档所说,还有以下几种情况
https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Literals.html#GUID-083FEFEA-B33F-436B-AEBF-9101A49EF189
Here are some validNUMBER
literals:这里是一些有效的 NUMBER 字面量:
25
+6.34
0.5
25e-03
-1
Here are some valid floating-point number literals:这里是一些有效的浮点数字面量:
25f
+6.34F
0.5d
-1D
这里的 25f 和0.5d,也是数字后面接了非数字的字符,难道表现会不一样?
SQL> select 123c,123d,123cd,123dc from dual;
C 123D CD C
---------- ---------- ---------- ----------
123 123 123 123
炸裂吧,都是字母,表现真不一样:
第一列 123c, 字段值为numbe类型的123,列别名c
第二列 123d ,字段值为双精度浮点型的123,无列别名
第三列 123cd,字段值为numbe类型的123,列别名cd
第四列 123dc,字段值为双精度浮点型的123,列别名c
科学计数法也是一样的情况,数值后面紧接d或f时,表示变为浮点类型,如果是接其他非数字和符号的字符,则作为别名
SQL> select 25e-03e,25e-03d from dual;
E 25E-03D
---------- ----------
0.025 0.025
这样就会有下面这种有趣的现象:
SQL> select 36d-36d from dual;
36D-36D
----------
0
SQL> select 36e-36e from dual;
E
----------
3.6E-35
两个SQL看似形式一样,但是完全是两个不同的处理逻辑:
第一个SQL发生了一次减法计算,减数和被减数相等,差为0;
第二个SQL则是一个字面值常量,用科学计数法表示了 "36乘以10的负36次方",即"3.6乘以10的负35次方",然后使用字母E
作为列别名
国产数据库表现
对于ORACLE这种处理方式是否合理,我目前持中性态度。
但既然ORACLE是这么玩的,那么其他宣称高度兼容ORACLE的国产数据库又是什么表现呢?
各个数据库测试情况
数据库 | 123dc | 是否和ORACLE一致 | 123cd | 是否和ORACLE一致 |
---|---|---|---|---|
YASHAN 23 | 报错[1:11]YAS-04105 invalid number character d | N | 报错[1:11]YAS-04105 invalid number character c | N |
GaussDB 506.0 | 值:123,字段名:dc | N | 值:123,字段名:cd | Y |
openGauss 5.0.0 | 值:123,字段名:dc | N | 值:123,字段名:cd | Y |
Gbase 8c | 值:123,字段名:dc | N | 值:123,字段名:cd | Y |
VastBase v2.2. build 16 | 值:123,字段名:dc | N | 值:123,字段名:cd | Y |
highgo 6 | 值:123,字段名:dc | N | 值:123,字段名:cd | Y |
ivorysql 3.4 | 值:123,字段名:c | Y | ERROR: trailing junk after numeric literal at or near "123c" | N |
kingbase 9 | 值:123,字段名:c | Y | 值:123,字段名:cd | Y |
DM 8 | 值:123,字段名:c | Y | 值:123,字段名:cd | Y |
OceanBase 4 | 值:123,字段名:c | Y | 值:123,字段名:cd | Y |
看上去只有3款产品做全了,另外的几款都没做全,甚至有一款宣传完全参考Oracle进行开发的产品在两个测试场景都没通过,这也出乎意料了。
这种特殊的分词规则在Oracle官方文档中是没有提及的,国产数据库把这个规则做进去,我猜要么是在客户处遇到了这种场景,要么就是研究了开源的词法规则(比如antlr4的PlSql),否则谁会想到在字面量这么基础简单的使用上,Oracle还埋了特殊规则。
总结
回到本文标题, select 123木头人 from dual
在ORACLE里真能run起来,返回列名为"木头人",值为"123",看似没有报错,但这种特殊分词规则隐藏了巨大的隐患,实际开发过程中不建议使用,使用列别名的场景还是加as
为好,比如 select 123 as 木头人 from dual
。