mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-07 23:19:35 +08:00
🔧 fix(db/kingbase_impl): 修复标识符无条件加双引号导致SQL语法报错
- quoteKingbaseIdent 改为条件引用,仅对大写字母、保留字、特殊字符的标识符添加双引号 - 新增 kingbaseIdentNeedsQuote 判断标识符是否需要引用 - 新增 isKingbaseReservedWord 检测常见SQL保留字 - 补充 TestQuoteKingbaseIdent、TestKingbaseIdentNeedsQuote 单测覆盖各场景 - refs #176
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -805,12 +806,56 @@ func normalizeKingbaseIdentifier(raw string) string {
|
||||
return value
|
||||
}
|
||||
|
||||
// kingbaseIdentNeedsQuote 判断标识符是否需要双引号包裹。
|
||||
// 与前端 sql.ts 中 needsQuote 逻辑保持一致。
|
||||
func kingbaseIdentNeedsQuote(ident string) bool {
|
||||
if ident == "" {
|
||||
return false
|
||||
}
|
||||
// 不是合法裸标识符格式(必须以字母或下划线开头,仅含字母、数字、下划线)
|
||||
if matched, _ := regexp.MatchString(`^[a-zA-Z_][a-zA-Z0-9_]*$`, ident); !matched {
|
||||
return true
|
||||
}
|
||||
// 包含大写字母时需要引号保护(KingbaseES/PostgreSQL 默认将未加引号的标识符折叠为小写)
|
||||
for _, r := range ident {
|
||||
if r >= 'A' && r <= 'Z' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// 是 SQL 保留字
|
||||
return isKingbaseReservedWord(ident)
|
||||
}
|
||||
|
||||
// isKingbaseReservedWord 检查是否为常见 SQL 保留字(简化版,与前端保持一致)。
|
||||
func isKingbaseReservedWord(ident string) bool {
|
||||
switch strings.ToLower(ident) {
|
||||
case "select", "from", "where", "table", "index", "user", "order", "group", "by",
|
||||
"limit", "offset", "and", "or", "not", "null", "true", "false", "key",
|
||||
"primary", "foreign", "references", "default", "constraint",
|
||||
"create", "drop", "alter", "insert", "update", "delete", "set", "values", "into",
|
||||
"join", "left", "right", "inner", "outer", "on", "as", "is", "in", "like",
|
||||
"between", "case", "when", "then", "else", "end", "having", "distinct",
|
||||
"all", "any", "exists", "union", "except", "intersect",
|
||||
"column", "check", "unique", "with", "grant", "revoke", "trigger",
|
||||
"begin", "commit", "rollback", "schema", "database", "view", "function",
|
||||
"procedure", "sequence", "type", "domain", "role", "session", "current",
|
||||
"authorization", "cross", "full", "natural", "some", "cast", "fetch",
|
||||
"for", "to", "do", "if", "return", "returns", "declare", "cursor":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func quoteKingbaseIdent(name string) string {
|
||||
n := normalizeKingbaseIdentifier(name)
|
||||
n = strings.ReplaceAll(n, `"`, `""`)
|
||||
if n == "" {
|
||||
return "\"\""
|
||||
}
|
||||
// 仅在需要时才加双引号,避免 KingbaseES 兼容性问题
|
||||
if !kingbaseIdentNeedsQuote(n) {
|
||||
return n
|
||||
}
|
||||
n = strings.ReplaceAll(n, `"`, `""`)
|
||||
return `"` + n + `"`
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,25 @@ func TestQuoteKingbaseIdent(t *testing.T) {
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{name: "plain", in: "ldf_server", want: `"ldf_server"`},
|
||||
{name: "double quoted", in: `""ldf_server""`, want: `"ldf_server"`},
|
||||
{name: "escaped quoted", in: `\"ldf_server\"`, want: `"ldf_server"`},
|
||||
// 纯小写+下划线:不加引号
|
||||
{name: "plain lowercase", in: "ldf_server", want: "ldf_server"},
|
||||
{name: "plain lowercase 2", in: "bcs_barcode", want: "bcs_barcode"},
|
||||
{name: "double quoted input", in: `""ldf_server""`, want: "ldf_server"},
|
||||
{name: "escaped quoted input", in: `\"ldf_server\"`, want: "ldf_server"},
|
||||
// 含大写字母:加引号
|
||||
{name: "uppercase", in: "LDF_Server", want: `"LDF_Server"`},
|
||||
{name: "mixed case", in: "myTable", want: `"myTable"`},
|
||||
// SQL 保留字:加引号
|
||||
{name: "reserved word order", in: "order", want: `"order"`},
|
||||
{name: "reserved word user", in: "user", want: `"user"`},
|
||||
{name: "reserved word table", in: "table", want: `"table"`},
|
||||
{name: "reserved word select", in: "select", want: `"select"`},
|
||||
// 含特殊字符:加引号
|
||||
{name: "with hyphen", in: "my-table", want: `"my-table"`},
|
||||
{name: "with space", in: "my table", want: `"my table"`},
|
||||
{name: "with embedded quote", in: `ab"cd`, want: `"ab""cd"`},
|
||||
// 空值
|
||||
{name: "empty", in: "", want: `""`},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -49,6 +64,31 @@ func TestQuoteKingbaseIdent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestKingbaseIdentNeedsQuote(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
want bool
|
||||
}{
|
||||
{name: "plain lowercase", in: "ldf_server", want: false},
|
||||
{name: "starts with underscore", in: "_col", want: false},
|
||||
{name: "with digits", in: "col123", want: false},
|
||||
{name: "uppercase", in: "MyTable", want: true},
|
||||
{name: "reserved word", in: "order", want: true},
|
||||
{name: "with hyphen", in: "my-col", want: true},
|
||||
{name: "starts with digit", in: "123col", want: true},
|
||||
{name: "empty", in: "", want: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := kingbaseIdentNeedsQuote(tt.in); got != tt.want {
|
||||
t.Fatalf("kingbaseIdentNeedsQuote(%q) = %v, want %v", tt.in, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitKingbaseQualifiedTable(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user