--- name: 多数据库方言与前缀支持 overview: 为 HoTimeDB ORM 实现完整的多数据库(MySQL/PostgreSQL/SQLite)方言支持和自动表前缀功能,同时保持完全向后兼容。 todos: - id: dialect-interface content: 扩展 Dialect 接口,添加 QuoteIdentifier 和 QuoteChar 方法 status: pending - id: identifier-processor content: 实现 IdentifierProcessor 结构体及其方法 status: pending - id: db-integration content: 在 HoTimeDB 中集成处理器,添加辅助方法 status: pending - id: crud-update content: 修改 crud.go 中的所有 CRUD 方法使用新处理器 status: pending - id: where-update content: 修改 where.go 中的条件处理逻辑 status: pending - id: builder-update content: 修改 builder.go 中的链式 JOIN 方法 status: pending - id: testing content: 测试多数据库和前缀功能 status: pending --- # HoTimeDB 多数据库方言与自动前缀支持计划 ## 目标 1. **多数据库方言支持**:让所有 ORM 方法正确支持 MySQL、PostgreSQL、SQLite 的标识符引号格式 2. **自动表前缀**:在主表、JOIN 表、WHERE/ON 条件中自动识别并添加表前缀 3. **完全向后兼容**:用户现有写法(`order.name`、`` `order`.name ``)无需修改 ## 核心设计 ### 标识符处理流程 ```mermaid flowchart TD Input["用户输入: order.name 或 `order`.name"] Parse["解析标识符"] AddPrefix["添加表前缀"] QuoteByDialect["根据数据库类型添加引号"] Output["输出: `app_order`.`name` (MySQL) 或 \"app_order\".\"name\" (PG)"] Input --> Parse Parse --> AddPrefix AddPrefix --> QuoteByDialect QuoteByDialect --> Output ``` ## 实现步骤 ### 第1步:扩展 Dialect 接口([db/dialect.go](db/dialect.go)) 在现有 `Dialect` 接口中添加新方法: ```go // QuoteIdentifier 处理标识符(支持 table.column 格式) // 输入: "order" 或 "order.name" 或 "`order`.name" // 输出: 带正确引号的标识符 QuoteIdentifier(name string) string // QuoteChar 获取引号字符(用于字符串中的替换检测) QuoteChar() string ``` 为三种数据库实现这些方法,核心逻辑: - 去除已有的引号(支持反引号和双引号) - 按点号分割,对每部分单独加引号 - MySQL 使用反引号,PostgreSQL/SQLite 使用双引号 ### 第2步:添加标识符处理器([db/dialect.go](db/dialect.go)) 新增 `IdentifierProcessor` 结构体: ```go type IdentifierProcessor struct { dialect Dialect prefix string } // ProcessTableName 处理表名(添加前缀+引号) func (p *IdentifierProcessor) ProcessTableName(name string) string // ProcessColumn 处理字段名(table.column 格式,自动给表名加前缀) func (p *IdentifierProcessor) ProcessColumn(name string) string // ProcessCondition 处理 ON/WHERE 条件字符串中的 table.column func (p *IdentifierProcessor) ProcessCondition(condition string) string ``` 关键实现细节: - 使用正则表达式识别条件字符串中的 `table.column` 模式 - 识别已有的引号包裹(`` `table`.column `` 或 `"table".column`) - 避免误处理字符串字面量中的内容 ### 第3步:在 HoTimeDB 中集成处理器([db/db.go](db/db.go)) ```go // GetProcessor 获取标识符处理器(懒加载) func (that *HoTimeDB) GetProcessor() *IdentifierProcessor // processTable 内部方法:处理表名 func (that *HoTimeDB) processTable(table string) string // processColumn 内部方法:处理字段名 func (that *HoTimeDB) processColumn(column string) string ``` ### 第4步:修改 CRUD 方法([db/crud.go](db/crud.go)) 需要修改的方法及位置: | 方法 | 修改内容 | |------|---------| | `Select` (L77-153) | 表名、字段名处理 | | `buildJoin` (L156-222) | JOIN 表名、ON 条件处理 | | `Insert` (L248-302) | 表名、字段名处理 | | `BatchInsert` (L316-388) | 表名、字段名处理 | | `Update` (L598-638) | 表名、字段名处理 | | `Delete` (L640-661) | 表名处理 | 核心改动模式(以 Select 为例): ```go // 之前 query += " FROM `" + that.Prefix + table + "` " // 之后 query += " FROM " + that.processTable(table) + " " ``` ### 第5步:修改 WHERE 条件处理([db/where.go](db/where.go)) 修改 `varCond` 方法(L205-338)中的字段名处理: ```go // 之前 if !strings.Contains(k, ".") { k = "`" + k + "`" } // 之后 k = that.processColumn(k) ``` 同样修改 `handlePlainField`、`handleDefaultCondition` 等方法。 ### 第6步:修改链式构建器([db/builder.go](db/builder.go)) `LeftJoin`、`RightJoin` 等方法中的表名和条件字符串也需要处理: ```go func (that *HotimeDBBuilder) LeftJoin(table, joinStr string) *HotimeDBBuilder { // 处理表名和 ON 条件 table = that.HoTimeDB.processTable(table) joinStr = that.HoTimeDB.GetProcessor().ProcessCondition(joinStr) that.Join(Map{"[>]" + table: joinStr}) return that } ``` ## 安全考虑 1. **避免误替换数据**:条件处理只处理 SQL 语法结构中的标识符,不处理: - 字符串字面量内的内容(通过检测引号边界) - 已经是占位符 `?` 的参数值 2. **正则表达式设计**:识别 `table.column` 模式时排除: - 数字开头的标识符 - 函数调用如 `NOW()` - 特殊运算符如 `>=`、`<=` ## 测试要点 1. 多数据库类型切换 2. 带前缀和不带前缀场景 3. 复杂 JOIN 查询 4. 嵌套条件查询 5. 特殊字符和保留字作为表名/字段名 ## 文件修改清单 | 文件 | 修改类型 | |------|---------| | [db/dialect.go](db/dialect.go) | 扩展接口,添加 IdentifierProcessor | | [db/db.go](db/db.go) | 添加 GetProcessor 和辅助方法 | | [db/crud.go](db/crud.go) | 修改所有 CRUD 方法 | | [db/where.go](db/where.go) | 修改条件处理逻辑 | | [db/builder.go](db/builder.go) | 修改链式构建器的 JOIN 方法 |