SQL Formatter 方言兼容:关键字、函数与语法保真
SQL formatter 的任务是调整空格、换行和缩进,而不是把一种 SQL 方言翻译成另一种。PostgreSQL 的 ::jsonb、MySQL 的反引号标识符和 SQLite 的函数写法都可能影响词法或语义。本文只讨论方言声明与格式保真,不把 formatter 当成 SQL 迁移器或完整 parser。
一、问题概述:格式正确不等于方言正确
同一段文本在不同数据库中可能使用不同关键字、函数和标识符引号。若 formatter 没有目标方言,自动大写或重排 token 可能把保留字、类型转换或字符串内容改坏;保持原语义比追求统一外观更重要。
二、最小复现:三个方言的写法不同
下面只列出需要声明方言的片段,不代表可以跨数据库直接执行。formatter 应保留这些 token 的边界,并对不认识的扩展选择保守失败或原样保留。
const samples = {
postgresql: "SELECT payload::jsonb ->> 'name' FROM \"User\";",
mysql: "SELECT JSON_EXTRACT(" + String.fromCharCode(96) + "payload" + String.fromCharCode(96) + ", '$.name') FROM " + String.fromCharCode(96) + "User" + String.fromCharCode(96) + ";",
sqlite: "SELECT json_extract(payload, '$.name') FROM User;",
};
console.log(samples.postgresql);
console.log(samples.mysql);
console.log(samples.sqlite);三、根因:关键字和函数属于方言词汇表
关键字是否保留、函数参数顺序、类型转换符号、LIMIT/OFFSET 形式和引号规则都可能随方言变化。把所有大写单词当关键字,或把未知函数当成可重排的普通调用,都会越过 formatter 的安全边界。
四、推荐方案:方言配置只决定格式规则,不改写语义
调用 formatter 前明确目标 dialect,并为关键字、函数、标识符引号和参数占位符使用对应规则。遇到目标方言不支持或 tokenizer 无法识别的语法,应保留原片段并提示,不能偷偷转换为另一种数据库的写法。
五、完整代码:对方言特征做保守提示
以下代码不是 SQL formatter,而是一个输入前检查器,用于提醒调用方选择方言。它不解析字符串内部,也不声称能证明 SQL 可执行;真正格式化仍应由声明 dialect 的 formatter 完成。
type Dialect = "postgresql" | "mysql" | "sqlite";
function dialectNotes(sql: string, dialect: Dialect): string[] {
const notes: string[] = [];
if (sql.includes("::") && dialect !== "postgresql") notes.push("type-cast syntax is PostgreSQL-specific");
if (sql.includes(String.fromCharCode(96)) && dialect !== "mysql") notes.push("backtick identifiers need dialect confirmation");
if (sql.includes("JSON_EXTRACT") && dialect === "postgresql") notes.push("check the PostgreSQL JSON operator/function form");
return notes;
}
console.log(dialectNotes("SELECT payload::jsonb FROM data", "mysql"));
// ["type-cast syntax is PostgreSQL-specific"]六、常见错误方案
把 PostgreSQL、MySQL 和 SQLite 的关键字列表合并成一套会误判保留字;把单引号、双引号和反引号统一替换会改变标识符或字符串;用 formatter 输出能解析的文本就宣称完成方言迁移,也忽略了函数和类型语义。
七、边界条件:注释、占位符与未知扩展
注释中的关键字、字符串中的逗号和参数占位符不能参与关键字重排。驱动占位符可能是 $1、? 或命名参数;formatter 应保留它们。厂商扩展、存储过程和方言特有 hint 如果无法识别,应让用户选择保留、警告或拒绝。
八、如何验证格式保真
针对每个目标方言准备包含关键字、函数、类型转换、标识符引号、字符串、注释和占位符的样本。格式化前后用目标 parser 或数据库客户端检查 AST/可执行性,并断言字符串、参数和注释内容未改变;不要只比较输出是否更整齐。
九、FAQ
问:formatter 能把 MySQL SQL 转成 PostgreSQL 吗?答:通常不能,格式化与方言迁移是不同任务。
问:未知函数应该删除或改名吗?答:不应猜测,保留原文并提示目标方言。
问:为什么同一 SQL 在不同 formatter 上缩进不同?答:关键字、函数和方言 profile 的词法规则可能不同,应以目标 dialect 配置为准。
十、总结
SQL formatter 的方言兼容核心是保留语义:声明目标 dialect,使用对应关键字/函数/引号规则,对未知扩展保守处理,并用目标 parser 验证格式化前后等价。统一外观不能替代 SQL 方言迁移。
来源与延伸阅读
技术审校所依据的规范与权威参考资料。
- PostgreSQL Documentation — SQL Syntax
PostgreSQL Global Development Group
- RFC 8259 — The JavaScript Object Notation (JSON) Data Interchange Format
RFC Editor
相关文章
SQL Formatter 接入 CI/CD:检查、自动修改与失败策略
围绕 SQL 格式化的 CI/CD 接入说明 check、write 两种模式、差异输出和失败策略,避免把格式检查写成泛化工程模板。
实现原理SQL Formatter Lexer 与高亮引擎:Token、缩进状态和能力边界
从 lexer/token 角度说明 SQL 高亮和缩进状态机如何工作,并明确语法高亮引擎不是完整 SQL parser。
踩坑避坑SQL 转 JSON:NULL、布尔值与 0/1 的语义差异
分开说明 PostgreSQL boolean、MySQL TINYINT(1)、SQL NULL、字符串 null 与数值 0/1 在 JSON 转换中的差异,并给出显式映射和验证示例。
继续阅读
可打开关联的浏览器工具,使用自己的样本验证文中的处理流程。
打开关联工具