🐛 fix(batch-truncate/query): 修复批量清空表安全隐患并优化多语句执行错误反馈

- 安全加固:TruncateTables 增加审计日志(Warnf 级别)和参数校验(上限 200 张)
- 容错增强:批量清空部分失败时返回已执行 SQL 列表并提示已清空表不可恢复
- 错误优化:DBQueryMulti 逐条执行失败时附带语句序号和已成功条数
- 性能优化:splitSQLStatements 从 string 拼接改为 strings.Builder,消除 O(n²) 分配
- 转义修复:splitSQLStatements 支持 SQL 标准转义单引号 '' 防止误拆分
- 前端修复:handleBatchClear 统一取消判断字符串为 '已取消' 并移除冗余变量声明
- refs #244
This commit is contained in:
Syngnat
2026-03-18 14:32:11 +08:00
parent fbd785400f
commit 64021ffd2a
6 changed files with 101 additions and 35 deletions

View File

@@ -5,11 +5,12 @@ import "strings"
// splitSQLStatements 按分号拆分 SQL 文本为独立语句。
// 正确处理单引号/双引号/反引号字符串、行注释(-- / #)、块注释(/* */)和
// PostgreSQL/Kingbase 的 $$...$$ dollar-quoting避免在这些上下文中错误拆分。
// 同时支持 SQL 标准的转义单引号(两个连续单引号 '' 表示字面量引号)。
func splitSQLStatements(sql string) []string {
text := strings.ReplaceAll(sql, "\r\n", "\n")
var statements []string
cur := ""
var cur strings.Builder
inSingle := false
inDouble := false
inBacktick := false
@@ -19,11 +20,11 @@ func splitSQLStatements(sql string) []string {
var dollarTag string // postgres/kingbase: $$...$$ or $tag$...$tag$
push := func() {
s := strings.TrimSpace(cur)
s := strings.TrimSpace(cur.String())
if s != "" {
statements = append(statements, s)
}
cur = ""
cur.Reset()
}
for i := 0; i < len(text); i++ {
@@ -38,15 +39,15 @@ func splitSQLStatements(sql string) []string {
if ch == '\n' {
inLineComment = false
}
cur += string(ch)
cur.WriteByte(ch)
continue
}
// 块注释
if inBlockComment {
cur += string(ch)
cur.WriteByte(ch)
if ch == '*' && next == '/' {
cur += "/"
cur.WriteByte('/')
i++
inBlockComment = false
}
@@ -56,66 +57,73 @@ func splitSQLStatements(sql string) []string {
// Dollar-quoting
if dollarTag != "" {
if strings.HasPrefix(text[i:], dollarTag) {
cur += dollarTag
cur.WriteString(dollarTag)
i += len(dollarTag) - 1
dollarTag = ""
} else {
cur += string(ch)
cur.WriteByte(ch)
}
continue
}
// 转义字符
// 转义字符反斜杠转义MySQL 风格)
if escaped {
escaped = false
cur += string(ch)
cur.WriteByte(ch)
continue
}
if (inSingle || inDouble) && ch == '\\' {
escaped = true
cur += string(ch)
cur.WriteByte(ch)
continue
}
// 字符串开闭
if !inDouble && !inBacktick && ch == '\'' {
if inSingle && next == '\'' {
// SQL 标准转义:两个连续单引号 '' 表示字面量引号,保持在引号内
cur.WriteByte(ch)
cur.WriteByte(next)
i++
continue
}
inSingle = !inSingle
cur += string(ch)
cur.WriteByte(ch)
continue
}
if !inSingle && !inBacktick && ch == '"' {
inDouble = !inDouble
cur += string(ch)
cur.WriteByte(ch)
continue
}
if !inSingle && !inDouble && ch == '`' {
inBacktick = !inBacktick
cur += string(ch)
cur.WriteByte(ch)
continue
}
// 在引号/反引号内部不做任何判断
if inSingle || inDouble || inBacktick {
cur += string(ch)
cur.WriteByte(ch)
continue
}
// 行注释开始
if ch == '-' && next == '-' {
inLineComment = true
cur += string(ch)
cur.WriteByte(ch)
continue
}
if ch == '#' {
inLineComment = true
cur += string(ch)
cur.WriteByte(ch)
continue
}
// 块注释开始
if ch == '/' && next == '*' {
inBlockComment = true
cur += "/*"
cur.WriteString("/*")
i++
continue
}
@@ -124,7 +132,7 @@ func splitSQLStatements(sql string) []string {
if ch == '$' {
if tag := parseSQLDollarTag(text[i:]); tag != "" {
dollarTag = tag
cur += tag
cur.WriteString(tag)
i += len(tag) - 1
continue
}
@@ -142,7 +150,7 @@ func splitSQLStatements(sql string) []string {
continue
}
cur += string(ch)
cur.WriteByte(ch)
}
push()