- 移除大量 debugLog 调试日志调用 - 修改数据库计数检查代码,使用下划线忽略返回值 - 更新事务测试中的变量声明,统一使用下划线忽略不需要的返回值 - 添加注释说明 admin 表数据检查的目的 - 移除条件查询语法测试中的冗余调试日志 - 优化链式查询测试中的调试信息 - 清理 JOIN 查询测试部分的日志输出 - 移除聚合函数测试中的调试日志 - 删除分页查询测试中的多余日志 - 清理批量插入测试中的调试信息 - 优化 upsert 测试部分的日志输出 - 移除事务测试中的冗余调试日志 - 删除原生 SQL 测试中的大量调试日志 - 移除 IN/NOT IN 数组测试中的 region 注释块
7.2 KiB
| name | overview | todos | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 多数据库方言与前缀支持 | 为 HoTimeDB ORM 实现完整的多数据库(MySQL/PostgreSQL/SQLite)方言支持和自动表前缀功能,采用智能解析+辅助方法兜底的混合策略,保持完全向后兼容。 |
|
HoTimeDB 多数据库方言与自动前缀支持计划(更新版)
目标
- 多数据库方言支持:MySQL、PostgreSQL、SQLite 标识符引号自动转换
- 自动表前缀:主表、JOIN 表、ON/WHERE 条件中的表名自动添加前缀
- 完全向后兼容:用户现有写法无需修改
- 辅助方法兜底:边缘情况可用
T()/C()精确控制
混合策略设计
各部分处理方式
| 位置 | 处理方式 | 准确度 | 说明 |
|------|---------|--------|------|
| 主表名 | 自动 | 100% | Select("order") 自动处理 |
| JOIN 表名 | 自动 | 100% | [><]order 中提取表名处理 |
| ON 条件字符串 | 智能解析 | ~95% | 正则匹配 table.column 模式 |
| WHERE 条件 Map | 自动 | 100% | Map 的 key 是结构化的 |
| SELECT 字段 | 智能解析 | ~95% | 同 ON 条件 |
辅助方法(兜底)
db.T("order") // 返回 "`app_order`" (MySQL) 或 "\"app_order\"" (PG)
db.C("order", "name") // 返回 "`app_order`.`name`"
db.C("order.name") // 同上,支持点号格式
实现步骤
第1步:扩展 Dialect 接口(db/dialect.go)
添加新方法到 Dialect 接口:
// QuoteIdentifier 处理单个标识符(去除已有引号,添加正确引号)
QuoteIdentifier(name string) string
// QuoteChar 返回引号字符
QuoteChar() string
三种方言实现:
- MySQL: 反引号
` - PostgreSQL/SQLite: 双引号
"
第2步:添加标识符处理器(db/identifier.go 新文件)
type IdentifierProcessor struct {
dialect Dialect
prefix string
}
// ProcessTableName 处理表名(添加前缀+引号)
// "order" → "`app_order`"
func (p *IdentifierProcessor) ProcessTableName(name string) string
// ProcessColumn 处理 table.column 格式
// "order.name" → "`app_order`.`name`"
// "`order`.name" → "`app_order`.`name`"
func (p *IdentifierProcessor) ProcessColumn(name string) string
// ProcessConditionString 智能解析条件字符串
// "user.id = order.user_id" → "`app_user`.`id` = `app_order`.`user_id`"
func (p *IdentifierProcessor) ProcessConditionString(condition string) string
// ProcessFieldList 处理字段列表字符串
// "order.id, user.name AS uname" → "`app_order`.`id`, `app_user`.`name` AS uname"
func (p *IdentifierProcessor) ProcessFieldList(fields string) string
智能解析正则:
// 匹配 table.column 模式,排除已有引号、函数调用等
// 模式: \b([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)\b
// 排除: `table`.column, "table".column, FUNC(), 123.456
第3步:在 HoTimeDB 中集成(db/db.go)
// processor 缓存
var processorOnce sync.Once
var processor *IdentifierProcessor
// GetProcessor 获取标识符处理器
func (that *HoTimeDB) GetProcessor() *IdentifierProcessor
// T 辅助方法:获取带前缀和引号的表名
func (that *HoTimeDB) T(table string) string
// C 辅助方法:获取带前缀和引号的 table.column
func (that *HoTimeDB) C(args ...string) string
第4步:修改 CRUD 方法(db/crud.go)
Select 方法改动:
// L112-116 原代码
if !strings.Contains(table, ".") && !strings.Contains(table, " AS ") {
query += " FROM `" + that.Prefix + table + "` "
} else {
query += " FROM " + that.Prefix + table + " "
}
// 改为
query += " FROM " + that.GetProcessor().ProcessTableName(table) + " "
// 字段列表处理(L90-107)
// 如果是字符串,调用 ProcessFieldList 处理
buildJoin 方法改动(L156-222):
// 原代码 L186-190
table := Substr(k, 3, len(k)-3)
if !strings.Contains(table, " ") {
table = "`" + table + "`"
}
query += " LEFT JOIN " + table + " ON " + v.(string) + " "
// 改为
table := Substr(k, 3, len(k)-3)
table = that.GetProcessor().ProcessTableName(table)
onCondition := that.GetProcessor().ProcessConditionString(v.(string))
query += " LEFT JOIN " + table + " ON " + onCondition + " "
Insert/BatchInsert/Update/Delete 同样修改表名和字段名处理。
第5步:修改 WHERE 条件处理(db/where.go)
varCond 方法改动(多处):
// 原代码(多处出现)
if !strings.Contains(k, ".") {
k = "`" + k + "`"
}
// 改为
k = that.GetProcessor().ProcessColumn(k)
需要修改的函数:
varCond(L205-338)handleDefaultCondition(L340-368)handlePlainField(L370-400)
第6步:修改链式构建器(db/builder.go)
LeftJoin 等方法需要传递处理器:
由于 builder 持有 HoTimeDB 引用,可以直接使用:
func (that *HotimeDBBuilder) LeftJoin(table, joinStr string) *HotimeDBBuilder {
// 不在这里处理,让 buildJoin 统一处理
that.Join(Map{"[>]" + table: joinStr})
return that
}
JOIN 的实际处理在 crud.go 的 buildJoin 中完成。
智能解析的边界处理
会自动处理的情况
user.id = order.user_id→ 正确处理user.id=order.user_id→ 正确处理(无空格)`user`.id = order.user_id→ 正确处理(混合格式)user.id = order.user_id AND order.status = 1→ 正确处理
需要辅助方法的边缘情况
- 子查询中的表名
- 复杂 CASE WHEN 表达式
- 动态拼接的 SQL 片段
文件清单
| 文件 | 操作 | 说明 |
|------|------|------|
| db/dialect.go | 修改 | 扩展 Dialect 接口 |
| db/identifier.go | 新增 | IdentifierProcessor 实现 |
| db/db.go | 修改 | 集成处理器,添加 T()/C() 方法 |
| db/crud.go | 修改 | 修改所有 CRUD 方法 |
| db/where.go | 修改 | 修改条件处理逻辑 |
| db/builder.go | 检查 | 可能无需修改(buildJoin 统一处理)|
测试用例
- 多数据库切换:MySQL → PostgreSQL → SQLite
- 前缀场景:有前缀 vs 无前缀
- 复杂 JOIN:多表 JOIN + 复杂 ON 条件
- 混合写法:
order.name+`user`.id混用 - 辅助方法:
T()和C()正确性