240 lines
7.5 KiB
Go
240 lines
7.5 KiB
Go
|
|
package db
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"regexp"
|
|||
|
|
"strings"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// IdentifierProcessor 标识符处理器
|
|||
|
|
// 用于处理表名、字段名的前缀添加和引号转换
|
|||
|
|
type IdentifierProcessor struct {
|
|||
|
|
dialect Dialect
|
|||
|
|
prefix string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewIdentifierProcessor 创建标识符处理器
|
|||
|
|
func NewIdentifierProcessor(dialect Dialect, prefix string) *IdentifierProcessor {
|
|||
|
|
return &IdentifierProcessor{
|
|||
|
|
dialect: dialect,
|
|||
|
|
prefix: prefix,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ProcessTableName 处理表名(添加前缀+引号)
|
|||
|
|
// 输入: "order" 或 "`order`" 或 "\"order\""
|
|||
|
|
// 输出: "`app_order`" (MySQL) 或 "\"app_order\"" (PostgreSQL/SQLite)
|
|||
|
|
func (p *IdentifierProcessor) ProcessTableName(name string) string {
|
|||
|
|
// 去除已有的引号
|
|||
|
|
name = p.stripQuotes(name)
|
|||
|
|
|
|||
|
|
// 检查是否包含空格(别名情况,如 "order AS o")
|
|||
|
|
if strings.Contains(name, " ") {
|
|||
|
|
// 处理别名情况
|
|||
|
|
parts := strings.SplitN(name, " ", 2)
|
|||
|
|
tableName := p.stripQuotes(parts[0])
|
|||
|
|
alias := parts[1]
|
|||
|
|
return p.dialect.QuoteIdentifier(p.prefix+tableName) + " " + alias
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加前缀和引号
|
|||
|
|
return p.dialect.QuoteIdentifier(p.prefix + name)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ProcessTableNameNoPrefix 处理表名(只添加引号,不添加前缀)
|
|||
|
|
// 用于已经包含前缀的情况
|
|||
|
|
func (p *IdentifierProcessor) ProcessTableNameNoPrefix(name string) string {
|
|||
|
|
name = p.stripQuotes(name)
|
|||
|
|
if strings.Contains(name, " ") {
|
|||
|
|
parts := strings.SplitN(name, " ", 2)
|
|||
|
|
tableName := p.stripQuotes(parts[0])
|
|||
|
|
alias := parts[1]
|
|||
|
|
return p.dialect.QuoteIdentifier(tableName) + " " + alias
|
|||
|
|
}
|
|||
|
|
return p.dialect.QuoteIdentifier(name)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ProcessColumn 处理 table.column 格式
|
|||
|
|
// 输入: "name" 或 "order.name" 或 "`order`.name" 或 "`order`.`name`"
|
|||
|
|
// 输出: "`name`" 或 "`app_order`.`name`" (MySQL)
|
|||
|
|
func (p *IdentifierProcessor) ProcessColumn(name string) string {
|
|||
|
|
// 检查是否包含点号
|
|||
|
|
if !strings.Contains(name, ".") {
|
|||
|
|
// 单独的列名,只加引号
|
|||
|
|
return p.dialect.QuoteIdentifier(p.stripQuotes(name))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理 table.column 格式
|
|||
|
|
parts := p.splitTableColumn(name)
|
|||
|
|
if len(parts) == 2 {
|
|||
|
|
tableName := p.stripQuotes(parts[0])
|
|||
|
|
columnName := p.stripQuotes(parts[1])
|
|||
|
|
// 表名添加前缀
|
|||
|
|
return p.dialect.QuoteIdentifier(p.prefix+tableName) + "." + p.dialect.QuoteIdentifier(columnName)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 无法解析,返回原样但转换引号
|
|||
|
|
return p.convertQuotes(name)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ProcessColumnNoPrefix 处理 table.column 格式(不添加前缀)
|
|||
|
|
func (p *IdentifierProcessor) ProcessColumnNoPrefix(name string) string {
|
|||
|
|
if !strings.Contains(name, ".") {
|
|||
|
|
return p.dialect.QuoteIdentifier(p.stripQuotes(name))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
parts := p.splitTableColumn(name)
|
|||
|
|
if len(parts) == 2 {
|
|||
|
|
tableName := p.stripQuotes(parts[0])
|
|||
|
|
columnName := p.stripQuotes(parts[1])
|
|||
|
|
return p.dialect.QuoteIdentifier(tableName) + "." + p.dialect.QuoteIdentifier(columnName)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return p.convertQuotes(name)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ProcessConditionString 智能解析条件字符串(如 ON 条件)
|
|||
|
|
// 输入: "user.id = order.user_id AND order.status = 1"
|
|||
|
|
// 输出: "`app_user`.`id` = `app_order`.`user_id` AND `app_order`.`status` = 1" (MySQL)
|
|||
|
|
func (p *IdentifierProcessor) ProcessConditionString(condition string) string {
|
|||
|
|
if condition == "" {
|
|||
|
|
return condition
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
result := condition
|
|||
|
|
|
|||
|
|
// 首先处理已有完整引号的情况 `table`.`column` 或 "table"."column"
|
|||
|
|
// 这些需要先处理,因为它们的格式最明确
|
|||
|
|
fullyQuotedPattern := regexp.MustCompile("[`\"]([a-zA-Z_][a-zA-Z0-9_]*)[`\"]\\.[`\"]([a-zA-Z_][a-zA-Z0-9_]*)[`\"]")
|
|||
|
|
result = fullyQuotedPattern.ReplaceAllStringFunc(result, func(match string) string {
|
|||
|
|
parts := fullyQuotedPattern.FindStringSubmatch(match)
|
|||
|
|
if len(parts) == 3 {
|
|||
|
|
tableName := parts[1]
|
|||
|
|
colName := parts[2]
|
|||
|
|
return p.dialect.QuoteIdentifier(p.prefix+tableName) + "." + p.dialect.QuoteIdentifier(colName)
|
|||
|
|
}
|
|||
|
|
return match
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 然后处理部分引号的情况 `table`.column 或 "table".column
|
|||
|
|
// 注意:需要避免匹配已处理的内容(已经是双引号包裹的)
|
|||
|
|
quotedTablePattern := regexp.MustCompile("[`\"]([a-zA-Z_][a-zA-Z0-9_]*)[`\"]\\.([a-zA-Z_][a-zA-Z0-9_]*)(?:[^`\"]|$)")
|
|||
|
|
result = quotedTablePattern.ReplaceAllStringFunc(result, func(match string) string {
|
|||
|
|
parts := quotedTablePattern.FindStringSubmatch(match)
|
|||
|
|
if len(parts) >= 3 {
|
|||
|
|
tableName := parts[1]
|
|||
|
|
colName := parts[2]
|
|||
|
|
// 保留末尾字符(如果有)
|
|||
|
|
suffix := ""
|
|||
|
|
if len(match) > len(parts[0])-1 {
|
|||
|
|
lastChar := match[len(match)-1]
|
|||
|
|
if lastChar != '`' && lastChar != '"' && !isIdentChar(lastChar) {
|
|||
|
|
suffix = string(lastChar)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return p.dialect.QuoteIdentifier(p.prefix+tableName) + "." + p.dialect.QuoteIdentifier(colName) + suffix
|
|||
|
|
}
|
|||
|
|
return match
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 最后处理无引号的情况 table.column
|
|||
|
|
// 使用更精确的正则,确保不匹配已处理的内容
|
|||
|
|
unquotedPattern := regexp.MustCompile(`([^` + "`" + `"\w]|^)([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)([^` + "`" + `"\w(]|$)`)
|
|||
|
|
result = unquotedPattern.ReplaceAllStringFunc(result, func(match string) string {
|
|||
|
|
parts := unquotedPattern.FindStringSubmatch(match)
|
|||
|
|
if len(parts) >= 5 {
|
|||
|
|
prefix := parts[1] // 前面的边界字符
|
|||
|
|
tableName := parts[2]
|
|||
|
|
colName := parts[3]
|
|||
|
|
suffix := parts[4] // 后面的边界字符
|
|||
|
|
return prefix + p.dialect.QuoteIdentifier(p.prefix+tableName) + "." + p.dialect.QuoteIdentifier(colName) + suffix
|
|||
|
|
}
|
|||
|
|
return match
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// isIdentChar 判断是否是标识符字符
|
|||
|
|
func isIdentChar(c byte) bool {
|
|||
|
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ProcessFieldList 处理字段列表字符串
|
|||
|
|
// 输入: "order.id, user.name AS uname, COUNT(*)"
|
|||
|
|
// 输出: "`app_order`.`id`, `app_user`.`name` AS uname, COUNT(*)" (MySQL)
|
|||
|
|
func (p *IdentifierProcessor) ProcessFieldList(fields string) string {
|
|||
|
|
if fields == "" || fields == "*" {
|
|||
|
|
return fields
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用与 ProcessConditionString 相同的逻辑
|
|||
|
|
return p.ProcessConditionString(fields)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// stripQuotes 去除标识符两端的引号(反引号或双引号)
|
|||
|
|
func (p *IdentifierProcessor) stripQuotes(name string) string {
|
|||
|
|
name = strings.TrimSpace(name)
|
|||
|
|
// 去除反引号
|
|||
|
|
if strings.HasPrefix(name, "`") && strings.HasSuffix(name, "`") {
|
|||
|
|
return name[1 : len(name)-1]
|
|||
|
|
}
|
|||
|
|
// 去除双引号
|
|||
|
|
if strings.HasPrefix(name, "\"") && strings.HasSuffix(name, "\"") {
|
|||
|
|
return name[1 : len(name)-1]
|
|||
|
|
}
|
|||
|
|
return name
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// splitTableColumn 分割 table.column 格式
|
|||
|
|
// 支持: table.column, `table`.column, `table`.`column`, "table".column 等
|
|||
|
|
func (p *IdentifierProcessor) splitTableColumn(name string) []string {
|
|||
|
|
// 先尝试按点号分割
|
|||
|
|
dotIndex := -1
|
|||
|
|
|
|||
|
|
// 查找不在引号内的点号
|
|||
|
|
inQuote := false
|
|||
|
|
quoteChar := byte(0)
|
|||
|
|
for i := 0; i < len(name); i++ {
|
|||
|
|
c := name[i]
|
|||
|
|
if c == '`' || c == '"' {
|
|||
|
|
if !inQuote {
|
|||
|
|
inQuote = true
|
|||
|
|
quoteChar = c
|
|||
|
|
} else if c == quoteChar {
|
|||
|
|
inQuote = false
|
|||
|
|
}
|
|||
|
|
} else if c == '.' && !inQuote {
|
|||
|
|
dotIndex = i
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if dotIndex == -1 {
|
|||
|
|
return []string{name}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return []string{name[:dotIndex], name[dotIndex+1:]}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// convertQuotes 将已有的引号转换为当前方言的引号格式
|
|||
|
|
func (p *IdentifierProcessor) convertQuotes(name string) string {
|
|||
|
|
quoteChar := p.dialect.QuoteChar()
|
|||
|
|
// 替换反引号
|
|||
|
|
name = strings.ReplaceAll(name, "`", quoteChar)
|
|||
|
|
// 如果目标是反引号,需要替换双引号
|
|||
|
|
if quoteChar == "`" {
|
|||
|
|
name = strings.ReplaceAll(name, "\"", quoteChar)
|
|||
|
|
}
|
|||
|
|
return name
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetDialect 获取方言
|
|||
|
|
func (p *IdentifierProcessor) GetDialect() Dialect {
|
|||
|
|
return p.dialect
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetPrefix 获取前缀
|
|||
|
|
func (p *IdentifierProcessor) GetPrefix() string {
|
|||
|
|
return p.prefix
|
|||
|
|
}
|