/ 三方言语法手册 / 复核附录
DbSql · 文档

实测验证附录

每条支持 / 不支持 / 与厂商数据库的差异,都由 verify.sh 在真实二进制上跑过。

本文记录 README.md、tsql.md、plsql.md、mysql.md 里每条「支持 / 不支持 / 有差异」声明的实测依据。

1. 复核环境

项目 值
日期 2026-11-13
系统 WSL2,Linux 6.18.58.1-microsoft-standard-WSL2
二进制 /path/to/dbsql/target/release/dbsql,mtime 2026-11-10 18:44:48 +0800
插件 plugins/libdbsql_oracle_plugin.so、plugins/libdbsql_mysql_plugin.so,mtime 2026-11-13 09:40:03 +0800
数据根 /tmp/dbsql-dialect-verify(脚本每次 rm -rf 重建,不碰任何客户库)
库 tdb(T-SQL)、pdb(PL/SQL)、mdb(MySQL)

2. 怎么跑

cd /path/to/dbsql
bash docs/dialects/verify.sh > /tmp/verify-out.txt 2>&1
echo $?          # 0
wc -l /tmp/verify-out.txt   # 776

3. 方言怎么选

3.1 命令行

CLI=./target/release/dbsql

$CLI --root /tmp/r create database tdb

$CLI --root /tmp/r --dialect standard   sql tdb "SELECT 1 AS x"   # T-SQL(默认)
$CLI --root /tmp/r --dialect procedural sql pdb "SELECT 1 AS x"   # PL/SQL
$CLI --root /tmp/r --dialect mysql      sql mdb "SELECT 1 AS x"   # MySQL

3.2 插件缺席 / 用错方言的原文

$ dbsql --dialect procedural sql pdb "SELECT 1"
错误:PL/SQL 方言需要插件,请用 --plugin <路径> 指定 dbsql_oracle_plugin.so

$ dbsql --dialect mysql sql mdb "SELECT 1"
错误:MySQL 方言需要插件,请用 --plugin <路径> 指定 dbsql_mysql_plugin.so

$ dbsql --dialect standard --plugin ./plugins/libdbsql_mysql_plugin.so sql x "SELECT 1"
错误:--plugin 只在 procedural/mysql 方言下生效,当前方言为 standard;请加 --dialect procedural 或 --dialect mysql(收到的插件路径:./plugins/libdbsql_mysql_plugin.so)

3.3 HTTP

/api/sql、/api/select、create-routine 支持请求体里的 "dialect" 字段,取值 standard / procedural / mysql。 注意:未知取值会静默回退到 T-SQL(standard),不报错——拼错方言名时会得到「语法莫名其妙报错」而不是「方言名无效」。

4. 关键声明与实测输出

4.1 T-SQL 方言

声明 SQL 片段 实测输出
列默认 NOT NULL CREATE TABLE t (id INT PRIMARY KEY, v VARCHAR(5)) 后 INSERT INTO t (id) VALUES (1) 错误:第 1 行的列 'v' 不允许为空(未提供值且无默认值)
默认不可空列写显式 NULL 也拒 INSERT INTO t (id, v) VALUES (2, NULL) 错误:第 1 行的列 'v' 不允许为空
声明了 NULL 才能存空 n INT NULL 后 INSERT INTO tt (id, v, n) VALUES (40, 'a', NULL) INSERT 1 行
VARCHAR(n) 不校验长度 VARCHAR(5) 存 'abcdefgh' INSERT 1 行
比较区分大小写 已存 'abcdefgh',WHERE v = 'ABCDEFGH' n = 0
CHAR(n) 是单字符 CHAR(3) 插 'abcd' 错误:列 'ch' 为字符列,字面量必须是单个字符,如 'a'
DECIMAL(p,s) 生效 DECIMAL(5,2) 插 1234.567 错误:列 'amt' 的值超出精度约束:小数位 3 超过声明小数位 2
双引号是字符串 SELECT "id" AS x FROM tt 结果 id
TOP / OFFSET…FETCH SELECT TOP 1 …;OFFSET 0 ROWS FETCH NEXT 2 ROWS ONLY 1 行;2 行
INTERSECT / EXCEPT SELECT id FROM tt INTERSECT … WHERE id = 1;EXCEPT … 1 行;其余行
CROSS JOIN 不支持 FROM tt a CROSS JOIN tt b 错误:SELECT 语句末尾存在多余内容(位于 31)
窗口函数不支持 PARTITION BY ROW_NUMBER() OVER (PARTITION BY v ORDER BY id) 错误:ROW_NUMBER 的 PARTITION BY 暂不支持(位于 30)
TOP n PERCENT 不支持 SELECT TOP 50 PERCENT id FROM tt 错误:TOP n PERCENT 不支持,请改写为 LIMIT(位于 14)
dbo. 前缀不支持 FROM dbo.tt 错误:表名 'dbo' 带库/架构前缀,dbsql 不支持跨库引用(请改写为同库表名)(位于 29)
ISNULL 不存在 SELECT ISNULL(v, 'x') FROM tt 错误:未定义的函数 'ISNULL'
TRUNCATE 不支持 TRUNCATE TABLE tt 错误:不支持的 SQL 语句类型:TRUNCATE
唯一索引不支持 CREATE UNIQUE INDEX ux ON tt (v) 错误:CREATE 后必须是 DATABASE / TABLE / INDEX / VIEW / PROCEDURE / FUNCTION / TRIGGER / SCHEDULE
外键不支持 CREATE TABLE tfk (id INT PRIMARY KEY, tid INT REFERENCES tt(id)) 错误:外键约束 'tt'(引用 'tt')暂不支持:外键的跨表校验还没有实现…
触发器可拦截 CREATE TRIGGER trg_b BEFORE INSERT ON tt AS RAISE '不允许插入' 插入报 错误:触发器 'trg_b':不允许插入;DROP TRIGGER trg_b 可删
触发器体可多语句 AFTER INSERT … INSERT INTO tt_audit …; INSERT INTO tt_audit … 触发一次后审计表 n = 2
游标 / 动态 EXEC CREATE PROCEDURE pc () AS BEGIN DECLARE cur CURSOR …;EXEC('INSERT …') 均创建成功,CALL pc() / CALL pdyn() 返回 CALL
SELECT … INTO SELECT id, v INTO tt_copy FROM tt WHERE id = 1 返回 0 行列集,但 tt_copy 有 1 行
顶层多条语句不支持 SELECT 1 AS x; SELECT 2 AS y 错误:SELECT 语句末尾存在多余内容(位于 13)

4.2 PL/SQL 方言

声明 SQL 片段 实测输出
无 FROM 可用 SELECT 1 AS x x = 1
DUAL 可用 SELECT SYSDATE AS d FROM DUAL 返回当前时间
|| 拼接 SELECT 'a' || 'b' AS s FROM DUAL ab
q'[…]' 不做 Oracle 解开 SELECT q'[it''s]' AS s FROM DUAL 原样返回 it''s
空串不是 NULL 一行存 ''(id=5),一行存 NULL(id=2) WHERE nm IS NULL → id=2;WHERE nm = '' → id=5
NULL 排序在前 ORDER BY nm ASC id=2(NULL)排第一
ROWNUM 不可用 SELECT ROWNUM, id FROM pt 错误:SELECT 列 'ROWNUM' 不存在
INSERT 值只能是字面量 VALUES (6, UPPER('a')) 错误:期望字面量,实际遇到 'UPPER'
UPDATE 值只能是字面量 UPDATE pt SET nm = nm WHERE id = 1 错误:期望字面量,实际遇到 'nm'
INSERT … SELECT 不支持 INSERT INTO pt (id, nm) SELECT 9, 'q' FROM DUAL 错误:期望关键字 'VALUES',实际遇到 'SELECT'
匿名块可用 BEGIN NULL; END; 执行成功
EXCEPTION 不支持 BEGIN NULL; EXCEPTION WHEN OTHERS THEN NULL; END; 错误:无法识别的 PL/SQL 语句:EXCEPTION WHEN OTHERS THEN NULL
MINUS / UNION / INTERSECT / CTE SELECT … MINUS SELECT …;WITH x AS (…) SELECT … 均返回行集
EXCEPT 不识别 SELECT id FROM pt EXCEPT SELECT id FROM pt WHERE id = 1 错误:只支持单条语句;语句结束后仍有多余内容:'SELECT'(位于 25)
递归 CTE 不支持 WITH r (n) AS (SELECT 1 FROM DUAL UNION ALL SELECT n+1 FROM r …) 错误:数据表 'r' 不存在(库 'pdb' 中没有该表)
窗口函数不支持 ROW_NUMBER() OVER (ORDER BY id) 错误:只支持单条语句;语句结束后仍有多余内容:'LParen'(位于 29)
FETCH FIRST 可用 ORDER BY id FETCH FIRST 1 ROWS ONLY 1 行
OFFSET … FETCH NEXT 可用 ORDER BY id OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY 1 行
单独 FETCH NEXT 不可用 ORDER BY id FETCH NEXT 1 ROWS ONLY 错误:期望关键字 'FIRST',实际遇到 'NEXT'
过程体字符串可写 CREATE PROCEDURE pz () AS BEGIN INSERT INTO pt (id, nm) VALUES (10, 'p'); END 创建成功,CALL pz() 后 id=10 存在
CREATE OR REPLACE VIEW / ALTER CREATE OR REPLACE VIEW pv AS …;ALTER TABLE pt ADD flag INT / DROP COLUMN flag 均 OK
索引 / 触发器 / 序列不支持 CREATE INDEX / CREATE TRIGGER / CREATE SEQUENCE 错误:CREATE 后不支持的对象类型(位于 7)
TRUNCATE / MERGE 不支持 TRUNCATE TABLE pt;MERGE INTO … 错误:不支持或无法识别的 Oracle 语句(位于 0):'TRUNCATE' / 'MERGE'
NUMBER(p,s) / VARCHAR2(n) 不校验 NUMBER(5,2) 存 1234.567;VARCHAR2(3) 存 6 字符 均 INSERT 1 行
复合主键必须显式 NOT NULL a INT, b INT, PRIMARY KEY (a, b) 错误:列 'a' 声明为 NULL,不能做复合主键的一部分;请改成 NOT NULL
DATE '…' 字面量不支持 VALUES (1, DATE '2026-01-02') 错误:缺少 VALUES 行右括号(位于 38);改写字符串 '2026-01-02' 可入库
单独 COMMIT 无效 COMMIT 错误:没有活动事务

4.3 MySQL 方言

声明 SQL 片段 实测输出
反引号 / LIMIT n,m SELECT `id` FROM `mt` LIMIT 0, 1 1 行
LIMIT n OFFSET m LIMIT 1 OFFSET 0 1 行
无 FROM / CTE / 递归 CTE SELECT 1 AS x;WITH x AS (…);WITH RECURSIVE r … 均成功
INTERSECT / EXCEPT SELECT id FROM mt INTERSECT … id = 1;EXCEPT … 1 行;其余行
窗口函数不支持 ROW_NUMBER() OVER (ORDER BY id) 错误:语句结束后存在多余内容:'LParen'(位于 29)
INSERT 值只能是字面量 VALUES (2, UPPER('b'), 2) 错误:期望字面量,实际遇到 'UPPER'
INSERT … SELECT / SET / 多表 UPDATE 三种写法 错误:期望关键字 'VALUES',实际遇到 'SELECT' / 'SET';错误:期望关键字 'SET',实际遇到 'Comma'
AUTO_INCREMENT 必须 NOT NULL id INT AUTO_INCREMENT PRIMARY KEY 错误:列 'id' 声明为 NULL,不能做主键;请改成 NOT NULL
只按主键判重 唯一键冲突时 INSERT IGNORE / REPLACE / ON DUPLICATE KEY UPDATE 三条都报 错误:唯一约束 'uk' 冲突:列 (k) 的值 ('9') 与已存在的行重复
主键冲突正常 主键冲突时 INSERT IGNORE / ON DUPLICATE KEY UPDATE / REPLACE 跳过 / 更新 / 替换(id=1 的 k 依次变为 11、12)
SHOW COLUMNS 形状 SHOW COLUMNS FROM mu TEXT→VARCHAR(255)、BOOL→TINYINT(1)、自增 Extra=auto_increment
SHOW CREATE TABLE 丢约束 带 UNIQUE KEY uk (k) 的 mu 回显只有 PRIMARY KEY;UNIQUE KEY 缺失
SHOW 缺表不报错 SHOW CREATE TABLE nope 返回空骨架 CREATE TABLE \nope` (\n\n) ENGINE=InnoDB…`
用户变量静默 SELECT @v 原样返回字符串 @v
过程:数字/参数正常 CREATE PROCEDURE pnum (IN n INT) BEGIN INSERT INTO mu (k) VALUES (n); END + CALL pnum(7) INSERT 1 行
过程:字符串字面量有缺陷 BEGIN INSERT INTO mt (v) VALUES ('x'); END + CALL pstr() 错误:列 'v' 的值写错了:表中不存在列 'x'。可用列:
过程:SELECT 'x' AS s 有缺陷 CALL psel() 错误:无法计算表达式:x AS s
控制流可用,循环有上限 WHILE i < n DO … END WHILE;CALL ploop(10001) 3 次循环写入 3 行;10001 次报 错误:WHILE 循环超过 10000 次,可能是死循环
默认值 / 生成列限制 DEFAULT CURRENT_TIMESTAMP;GENERATED ALWAYS AS (v + 1) 错误:列 'ts':默认值 'CURRENT_TIMESTAMP' 要按写入时刻求值,尚未支持…;错误:暂不支持生成列…
ALTER 一次一个子句 ADD COLUMN e INT, ADD COLUMN f INT 错误:ALTER TABLE 一次只支持一个子句(逗号分隔的多子句暂不支持)
索引 / 触发器 / OR REPLACE VIEW CREATE INDEX;CREATE TRIGGER;CREATE OR REPLACE VIEW 错误:CREATE 后不支持的对象类型(位于 7);DROP INDEX 报 DROP 后不支持的对象类型(位于 5)
TRUNCATE 语义不同 TRUNCATE TABLE mu(6 行) 返回 DELETE 6 行,随后自增从 1 重新开始
DROP TABLE IF EXISTS 缺表异形 DROP TABLE IF EXISTS nope 不报错,返回一行 COUNT(*) = 0

5. 跨方言隔离

存储过程/函数按方言隔离,同名互不可见:

# T-SQL 下建 only_tsql
### [建 T-SQL 过程(隔离用)] (standard) CREATE PROCEDURE only_tsql () AS BEGIN INSERT … END
    CREATE ROUTINE
### [PL/SQL 调 T-SQL 过程] (procedural) CALL only_tsql()
    错误:存储过程 'only_tsql' 不存在
### [MySQL 调 T-SQL 过程] (mysql) CALL only_tsql()
    错误:存储过程 'only_tsql' 不存在

表、约束、数据是共享的(三个库都在同一个 root 下),方言只决定解析与例程的命名空间。

6. 已知缺陷复现

缺陷 复现 实测
MySQL 过程体字符串字面量当列名 CALL pstr() 列 'v' 的值写错了:表中不存在列 'x'。可用列:
MySQL 过程体 SELECT 'x' AS s CALL psel() 无法计算表达式:x AS s
MySQL 只按主键判重 INSERT IGNORE 命中唯一键 唯一约束 'uk' 冲突
SHOW CREATE TABLE 丢 UNIQUE KEY SHOW CREATE TABLE mu 只回显 PRIMARY KEY
SHOW CREATE TABLE 缺表回空骨架 SHOW CREATE TABLE nope 一行空骨架,不报错
SELECT @v 静默返回字符串 SELECT @v 值就是 @v
PL/SQL ROWNUM 收下但执行不了 SELECT ROWNUM, id FROM pt SELECT 列 'ROWNUM' 不存在
T-SQL CHAR(n) 单字符 / VARCHAR(n) 不校验 见 4.1 见 4.1
MySQL DROP TABLE IF EXISTS 缺表返回 COUNT(*) 行 DROP TABLE IF EXISTS nope COUNT(*)=0

7. 清理

复核产生的文件都在临时目录,可直接删:

rm -rf /tmp/dbsql-dialect-verify /tmp/verify-out*.txt /tmp/plugchk

verify.sh 本身不写仓库文件,也不连接任何线上服务。