
在go中对sql字符串进行可读性格式化(如关键字大写、换行缩进)不能依赖简单字符串分割,而应基于语法解析——推荐使用成熟sql解析器(如vitess/sqlparser)并自定义输出逻辑,兼顾准确性与可维护性。
对SQL语句进行格式化看似是简单的文本处理任务,但实际涉及词法分析、语法结构识别和嵌套层级管理。例如将:
select col1, col2, col3 from foo where col1 > 1000 and col2 < 2000
转换为:
SELECT col1, col2, col3
FROM foo
WHERE col1 > 1000
AND col2 < 2000若仅靠strings.Split() + 关键字匹配(如"select"→"SELECT\n"),会面临诸多陷阱:
- 关键字大小写混用(SELECT vs select vs SeLeCt);
- 字符串字面量或注释中误匹配(如'select * from users');
- 嵌套结构无法识别缩进层级(如BEGIN ... SELECT ... END中的多层IF-BEGIN嵌套);
- 运算符(AND/OR)需根据上下文决定是否换行+缩进,而非机械前置。
✅ 正确做法:使用语法感知的SQL解析器。
vitess/go/vt/sqlparser 是目前Go生态中最成熟、生产验证过的SQL解析库,支持MySQL语法(兼容大部分标准SQL),可准确提取AST(抽象语法树),从而安全地重构格式:
立即学习“go语言免费学习笔记(深入)”;
import (
"fmt"
"github.com/vitessio/vitess/go/vt/sqlparser"
)
func formatSQL(sql string) (string, error) {
stmt, err := sqlparser.Parse(sql)
if err != nil {
return "", err
}
// 自定义格式化器:遍历AST,按节点类型插入换行/缩进/大写
var buf strings.Builder
indent := 0
formatNode(stmt, &buf, &indent)
return buf.String(), nil
}
func formatNode(node sqlparser.SQLNode, buf *strings.Builder, indent *int) {
switch n := node.(type) {
case *sqlparser.Select:
buf.WriteString("SELECT ")
// ... 处理SelectExprList、From、Where等子节点,递增indent
case *sqlparser.Where:
fmt.Fprintf(buf, "\n%*sWHERE ", *indent*4, "")
*indent++
sqlparser.String(n.Expr, buf)
*indent--
case *sqlparser.AndExpr:
fmt.Fprintf(buf, "\n%*sAND ", *indent*4, "")
// 递归格式化左右表达式
}
}⚠️ 注意事项:
- 不要自行正则替换:(?i)\b(select|from|where)\b 类模式在真实SQL中极易误伤(如列名select_id、注释-- select all);
- 缩进需语义驱动:AND是否换行取决于其是否位于WHERE子句顶层,而非单纯出现位置;
- 嵌套块需栈式缩进:BEGIN/END、IF/ELSE 等需维护缩进深度栈,而非静态Tab计数;
- 兼容性权衡:vitess默认适配MySQL,若需PostgreSQL/SQL Server语法,需评估pglogrepl或pq的解析能力,或切换至通用解析器如sqlparser-go。
总结:SQL格式化本质是编译器前端问题,而非字符串处理。在Go项目中,应优先集成健壮的SQL解析器,并在其AST基础上构建可扩展的格式化逻辑——这虽比正则方案初期成本略高,却能彻底规避语法歧义、保障长期可维护性,并为后续SQL审计、重写、权限校验等功能奠定基础。










