mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-07 08:03:04 +08:00
- 安全加固:TruncateTables 增加审计日志(Warnf 级别)和参数校验(上限 200 张) - 容错增强:批量清空部分失败时返回已执行 SQL 列表并提示已清空表不可恢复 - 错误优化:DBQueryMulti 逐条执行失败时附带语句序号和已成功条数 - 性能优化:splitSQLStatements 从 string 拼接改为 strings.Builder,消除 O(n²) 分配 - 转义修复:splitSQLStatements 支持 SQL 标准转义单引号 '' 防止误拆分 - 前端修复:handleBatchClear 统一取消判断字符串为 '已取消' 并移除冗余变量声明 - refs #244
176 lines
3.5 KiB
Go
176 lines
3.5 KiB
Go
package app
|
||
|
||
import "strings"
|
||
|
||
// splitSQLStatements 按分号拆分 SQL 文本为独立语句。
|
||
// 正确处理单引号/双引号/反引号字符串、行注释(-- / #)、块注释(/* */)和
|
||
// PostgreSQL/Kingbase 的 $$...$$ dollar-quoting,避免在这些上下文中错误拆分。
|
||
// 同时支持 SQL 标准的转义单引号(两个连续单引号 '' 表示字面量引号)。
|
||
func splitSQLStatements(sql string) []string {
|
||
text := strings.ReplaceAll(sql, "\r\n", "\n")
|
||
var statements []string
|
||
|
||
var cur strings.Builder
|
||
inSingle := false
|
||
inDouble := false
|
||
inBacktick := false
|
||
escaped := false
|
||
inLineComment := false
|
||
inBlockComment := false
|
||
var dollarTag string // postgres/kingbase: $$...$$ or $tag$...$tag$
|
||
|
||
push := func() {
|
||
s := strings.TrimSpace(cur.String())
|
||
if s != "" {
|
||
statements = append(statements, s)
|
||
}
|
||
cur.Reset()
|
||
}
|
||
|
||
for i := 0; i < len(text); i++ {
|
||
ch := text[i]
|
||
next := byte(0)
|
||
if i+1 < len(text) {
|
||
next = text[i+1]
|
||
}
|
||
|
||
// 行注释
|
||
if inLineComment {
|
||
if ch == '\n' {
|
||
inLineComment = false
|
||
}
|
||
cur.WriteByte(ch)
|
||
continue
|
||
}
|
||
|
||
// 块注释
|
||
if inBlockComment {
|
||
cur.WriteByte(ch)
|
||
if ch == '*' && next == '/' {
|
||
cur.WriteByte('/')
|
||
i++
|
||
inBlockComment = false
|
||
}
|
||
continue
|
||
}
|
||
|
||
// Dollar-quoting
|
||
if dollarTag != "" {
|
||
if strings.HasPrefix(text[i:], dollarTag) {
|
||
cur.WriteString(dollarTag)
|
||
i += len(dollarTag) - 1
|
||
dollarTag = ""
|
||
} else {
|
||
cur.WriteByte(ch)
|
||
}
|
||
continue
|
||
}
|
||
|
||
// 转义字符(反斜杠转义,MySQL 风格)
|
||
if escaped {
|
||
escaped = false
|
||
cur.WriteByte(ch)
|
||
continue
|
||
}
|
||
if (inSingle || inDouble) && ch == '\\' {
|
||
escaped = true
|
||
cur.WriteByte(ch)
|
||
continue
|
||
}
|
||
|
||
// 字符串开闭
|
||
if !inDouble && !inBacktick && ch == '\'' {
|
||
if inSingle && next == '\'' {
|
||
// SQL 标准转义:两个连续单引号 '' 表示字面量引号,保持在引号内
|
||
cur.WriteByte(ch)
|
||
cur.WriteByte(next)
|
||
i++
|
||
continue
|
||
}
|
||
inSingle = !inSingle
|
||
cur.WriteByte(ch)
|
||
continue
|
||
}
|
||
if !inSingle && !inBacktick && ch == '"' {
|
||
inDouble = !inDouble
|
||
cur.WriteByte(ch)
|
||
continue
|
||
}
|
||
if !inSingle && !inDouble && ch == '`' {
|
||
inBacktick = !inBacktick
|
||
cur.WriteByte(ch)
|
||
continue
|
||
}
|
||
|
||
// 在引号/反引号内部不做任何判断
|
||
if inSingle || inDouble || inBacktick {
|
||
cur.WriteByte(ch)
|
||
continue
|
||
}
|
||
|
||
// 行注释开始
|
||
if ch == '-' && next == '-' {
|
||
inLineComment = true
|
||
cur.WriteByte(ch)
|
||
continue
|
||
}
|
||
if ch == '#' {
|
||
inLineComment = true
|
||
cur.WriteByte(ch)
|
||
continue
|
||
}
|
||
|
||
// 块注释开始
|
||
if ch == '/' && next == '*' {
|
||
inBlockComment = true
|
||
cur.WriteString("/*")
|
||
i++
|
||
continue
|
||
}
|
||
|
||
// Dollar-quoting 开始
|
||
if ch == '$' {
|
||
if tag := parseSQLDollarTag(text[i:]); tag != "" {
|
||
dollarTag = tag
|
||
cur.WriteString(tag)
|
||
i += len(tag) - 1
|
||
continue
|
||
}
|
||
}
|
||
|
||
// 分号分隔(支持全角分号";")
|
||
if ch == ';' {
|
||
push()
|
||
continue
|
||
}
|
||
// 全角分号 UTF-8 序列: 0xEF 0xBC 0x9B
|
||
if ch == 0xEF && i+2 < len(text) && text[i+1] == 0xBC && text[i+2] == 0x9B {
|
||
push()
|
||
i += 2
|
||
continue
|
||
}
|
||
|
||
cur.WriteByte(ch)
|
||
}
|
||
|
||
push()
|
||
return statements
|
||
}
|
||
|
||
// parseSQLDollarTag 解析 PostgreSQL/Kingbase 的 dollar-quoting 标签。
|
||
func parseSQLDollarTag(s string) string {
|
||
if len(s) < 2 || s[0] != '$' {
|
||
return ""
|
||
}
|
||
for i := 1; i < len(s); i++ {
|
||
c := s[i]
|
||
if c == '$' {
|
||
return s[:i+1]
|
||
}
|
||
if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') {
|
||
return ""
|
||
}
|
||
}
|
||
return ""
|
||
}
|