hotime/.cursor/plans/多数据库方言与前缀支持_d7ceee79.plan.md
hoteas cf64276ab1 refactor(db): 重命名批量插入方法并更新文档
- 将 BatchInsert 方法重命名为 Inserts,以更好地反映其功能
- 更新示例代码和文档,确保使用新方法名
- 删除过时的文档文件,整合 HoTimeDB 使用说明和 API 参考
- 优化 README.md,增强框架特性和安装说明的清晰度
2026-01-22 20:32:29 +08:00

7.2 KiB
Raw Blame History

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

辅助方法(兜底)

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/Inserts/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.gobuildJoin 中完成。

智能解析的边界处理

会自动处理的情况

  • 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 统一处理)|

测试用例

  1. 多数据库切换MySQL → PostgreSQL → SQLite
  2. 前缀场景:有前缀 vs 无前缀
  3. 复杂 JOIN:多表 JOIN + 复杂 ON 条件
  4. 混合写法order.name + `user`.id 混用
  5. 辅助方法T()C() 正确性