--- name: 多数据库方言与前缀支持 overview: 为 HoTimeDB ORM 实现完整的多数据库(MySQL/PostgreSQL/SQLite)方言支持和自动表前缀功能,采用智能解析+辅助方法兜底的混合策略,保持完全向后兼容。 todos: - id: dialect-interface content: 扩展 Dialect 接口,添加 QuoteIdentifier 和 QuoteChar 方法 status: completed - id: identifier-processor content: 新建 identifier.go,实现 IdentifierProcessor 及智能解析逻辑 status: completed - id: db-integration content: 在 db.go 中集成处理器,添加 T() 和 C() 辅助方法 status: completed - id: crud-update content: 修改 crud.go 中 Select/Insert/Update/Delete/buildJoin 等方法 status: completed - id: where-update content: 修改 where.go 中 varCond 等条件处理方法 status: completed - id: builder-check content: 检查 builder.go 是否需要额外修改 status: completed - id: testing content: 编写测试用例验证多数据库和前缀功能 status: completed - 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()` 正确性