3681564ca4
- 在 ColumnDataType 中新增对 PostgreSQL 和 Oracle 数据类型的支持 - 更新 Db2JSON 方法,优化对达梦数据库表和列信息的查询逻辑 - 增强字段备注解析,支持从备注中提取提示信息并清理标签 - 更新文档,详细说明字段备注格式及前端提示的展示效果
261 lines
8.1 KiB
Go
261 lines
8.1 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,
|
||
}
|
||
}
|
||
|
||
// 系统数据库列表,这些数据库不添加前缀
|
||
var systemDatabases = map[string]bool{
|
||
"INFORMATION_SCHEMA": true,
|
||
"information_schema": true,
|
||
"mysql": true,
|
||
"performance_schema": true,
|
||
"sys": true,
|
||
"pg_catalog": true,
|
||
"pg_toast": true,
|
||
}
|
||
|
||
// ProcessTableName 处理表名(添加前缀+引号)
|
||
// 输入: "order" 或 "`order`" 或 "\"order\"" 或 "INFORMATION_SCHEMA.TABLES"
|
||
// 输出: "`app_order`" (MySQL) 或 "\"app_order\"" (PostgreSQL/SQLite)
|
||
// 对于 database.table 格式,会分别处理,系统数据库不添加前缀
|
||
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.ProcessTableName(tableName) + " " + alias
|
||
}
|
||
|
||
// 检查是否包含点号(database.table 格式)
|
||
if strings.Contains(name, ".") {
|
||
parts := p.splitTableColumn(name)
|
||
if len(parts) == 2 {
|
||
dbName := p.stripQuotes(parts[0])
|
||
tableName := p.stripQuotes(parts[1])
|
||
// 系统数据库不添加前缀
|
||
if systemDatabases[dbName] {
|
||
return p.dialect.QuoteIdentifier(dbName) + "." + p.dialect.QuoteIdentifier(tableName)
|
||
}
|
||
// 非系统数据库,只给表名添加前缀
|
||
return p.dialect.QuoteIdentifier(dbName) + "." + p.dialect.QuoteIdentifier(p.prefix+tableName)
|
||
}
|
||
}
|
||
|
||
// 添加前缀和引号
|
||
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) / "\"app_order\".\"name\"" (DM/PG)
|
||
func (p *IdentifierProcessor) ProcessColumn(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(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)
|
||
// 输出: "\"app_user\".\"id\" = \"app_order\".\"user_id\" AND \"app_order\".\"status\" = 1" (DM/PG)
|
||
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
|
||
}
|
||
|
||
result := p.ProcessConditionString(fields)
|
||
result = p.convertQuotes(result)
|
||
return result
|
||
}
|
||
|
||
// 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
|
||
}
|