chore(example): 清理调试日志并优化测试代码

- 移除大量 debugLog 调试日志调用
- 修改数据库计数检查代码,使用下划线忽略返回值
- 更新事务测试中的变量声明,统一使用下划线忽略不需要的返回值
- 添加注释说明 admin 表数据检查的目的
- 移除条件查询语法测试中的冗余调试日志
- 优化链式查询测试中的调试信息
- 清理 JOIN 查询测试部分的日志输出
- 移除聚合函数测试中的调试日志
- 删除分页查询测试中的多余日志
- 清理批量插入测试中的调试信息
- 优化 upsert 测试部分的日志输出
- 移除事务测试中的冗余调试日志
- 删除原生 SQL 测试中的大量调试日志
- 移除 IN/NOT IN 数组测试中的 region 注释块
This commit is contained in:
2026-01-22 07:25:41 +08:00
parent 60a12a09bd
commit ebe590ca9b
2 changed files with 253 additions and 141 deletions
@@ -0,0 +1,249 @@
---
name: 多数据库方言与前缀支持
overview: 为 HoTimeDB ORM 实现完整的多数据库(MySQL/PostgreSQL/SQLite)方言支持和自动表前缀功能,采用智能解析+辅助方法兜底的混合策略,保持完全向后兼容。
todos:
- id: dialect-interface
content: 扩展 Dialect 接口,添加 QuoteIdentifier 和 QuoteChar 方法
status: pending
- id: identifier-processor
content: 新建 identifier.go,实现 IdentifierProcessor 及智能解析逻辑
status: pending
- id: db-integration
content: 在 db.go 中集成处理器,添加 T() 和 C() 辅助方法
status: pending
- id: crud-update
content: 修改 crud.go 中 Select/Insert/Update/Delete/buildJoin 等方法
status: pending
- id: where-update
content: 修改 where.go 中 varCond 等条件处理方法
status: pending
- id: builder-check
content: 检查 builder.go 是否需要额外修改
status: pending
- id: testing
content: 编写测试用例验证多数据库和前缀功能
status: pending
- id: todo-1769037903242-d7aip6nh1
content: ""
status: pending
---
# HoTimeDB 多数据库方言与自动前缀支持计划(更新版)
## 目标
1. **多数据库方言支持**MySQL、PostgreSQL、SQLite 标识符引号自动转换
2. **自动表前缀**:主表、JOIN 表、ON/WHERE 条件中的表名自动添加前缀
3. **完全向后兼容**:用户现有写法无需修改
4. **辅助方法兜底**:边缘情况可用 `T()` / `C()` 精确控制
## 混合策略设计
### 各部分处理方式
| 位置 | 处理方式 | 准确度 | 说明 |
|------|---------|--------|------|
| 主表名 | 自动 | 100% | `Select("order")` 自动处理 |
| JOIN 表名 | 自动 | 100% | `[><]order` 中提取表名处理 |
| ON 条件字符串 | 智能解析 | ~95% | 正则匹配 `table.column` 模式 |
| WHERE 条件 Map | 自动 | 100% | Map 的 key 是结构化的 |
| SELECT 字段 | 智能解析 | ~95% | 同 ON 条件 |
### 辅助方法(兜底)
```go
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](db/dialect.go)
添加新方法到 `Dialect` 接口:
```go
// QuoteIdentifier 处理单个标识符(去除已有引号,添加正确引号)
QuoteIdentifier(name string) string
// QuoteChar 返回引号字符
QuoteChar() string
```
三种方言实现:
- MySQL: 反引号 `` ` ``
- PostgreSQL/SQLite: 双引号 `"`
### 第2步:添加标识符处理器([db/identifier.go](db/identifier.go) 新文件)
```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
```
**智能解析正则**
```go
// 匹配 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](db/db.go)
```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](db/crud.go)
**Select 方法改动**
```go
// 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):
```go
// 原代码 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](db/where.go)
**varCond 方法改动**(多处):
```go
// 原代码(多处出现)
if !strings.Contains(k, ".") {
k = "`" + k + "`"
}
// 改为
k = that.GetProcessor().ProcessColumn(k)
```
需要修改的函数:
- `varCond` (L205-338)
- `handleDefaultCondition` (L340-368)
- `handlePlainField` (L370-400)
### 第6步:修改链式构建器([db/builder.go](db/builder.go)
**LeftJoin 等方法需要传递处理器**
由于 builder 持有 HoTimeDB 引用,可以直接使用:
```go
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](db/dialect.go) | 修改 | 扩展 Dialect 接口 |
| [db/identifier.go](db/identifier.go) | 新增 | IdentifierProcessor 实现 |
| [db/db.go](db/db.go) | 修改 | 集成处理器,添加 T()/C() 方法 |
| [db/crud.go](db/crud.go) | 修改 | 修改所有 CRUD 方法 |
| [db/where.go](db/where.go) | 修改 | 修改条件处理逻辑 |
| [db/builder.go](db/builder.go) | 检查 | 可能无需修改(buildJoin 统一处理)|
## 测试用例
1. **多数据库切换**MySQL → PostgreSQL → SQLite
2. **前缀场景**:有前缀 vs 无前缀
3. **复杂 JOIN**:多表 JOIN + 复杂 ON 条件
4. **混合写法**`order.name` + `` `user`.id `` 混用
5. **辅助方法**`T()``C()` 正确性