hotime/.cursor/plans/多数据库方言与前缀支持_979672ee.plan.md
hoteas c2955d2500 feat(db): 实现数据库查询中的数组参数展开和空数组处理
- 在 Get 方法中添加无参数时的默认字段和 LIMIT 1 处理
- 实现 expandArrayPlaceholder 方法,自动展开 IN (?) 和 NOT IN (?) 中的数组参数
- 为空数组的 IN 条件生成 1=0 永假条件,NOT IN 生成 1=1 永真条件
- 在 queryWithRetry 和 execWithRetry 中集成数组占位符预处理
- 修复 where.go 中空切片条件的处理逻辑
- 添加完整的 IN/NOT IN 数组查询测试用例
- 更新 .gitignore 规则格式
2026-01-22 07:16:42 +08:00

5.9 KiB
Raw Blame History

name overview todos
多数据库方言与前缀支持 为 HoTimeDB ORM 实现完整的多数据库MySQL/PostgreSQL/SQLite方言支持和自动表前缀功能同时保持完全向后兼容。
id content status
dialect-interface 扩展 Dialect 接口,添加 QuoteIdentifier 和 QuoteChar 方法 pending
id content status
identifier-processor 实现 IdentifierProcessor 结构体及其方法 pending
id content status
db-integration 在 HoTimeDB 中集成处理器,添加辅助方法 pending
id content status
crud-update 修改 crud.go 中的所有 CRUD 方法使用新处理器 pending
id content status
where-update 修改 where.go 中的条件处理逻辑 pending
id content status
builder-update 修改 builder.go 中的链式 JOIN 方法 pending
id content status
testing 测试多数据库和前缀功能 pending

HoTimeDB 多数据库方言与自动前缀支持计划

目标

  1. 多数据库方言支持:让所有 ORM 方法正确支持 MySQL、PostgreSQL、SQLite 的标识符引号格式
  2. 自动表前缀在主表、JOIN 表、WHERE/ON 条件中自动识别并添加表前缀
  3. 完全向后兼容:用户现有写法(order.name`order`.name)无需修改

核心设计

标识符处理流程

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

在现有 Dialect 接口中添加新方法:

// QuoteIdentifier 处理标识符(支持 table.column 格式)
// 输入: "order" 或 "order.name" 或 "`order`.name"
// 输出: 带正确引号的标识符
QuoteIdentifier(name string) string

// QuoteChar 获取引号字符(用于字符串中的替换检测)
QuoteChar() string

为三种数据库实现这些方法,核心逻辑:

  • 去除已有的引号(支持反引号和双引号)
  • 按点号分割,对每部分单独加引号
  • MySQL 使用反引号PostgreSQL/SQLite 使用双引号

第2步添加标识符处理器db/dialect.go

新增 IdentifierProcessor 结构体:

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

// 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

需要修改的方法及位置:

| 方法 | 修改内容 |

|------|---------|

| Select (L77-153) | 表名、字段名处理 |

| buildJoin (L156-222) | JOIN 表名、ON 条件处理 |

| Insert (L248-302) | 表名、字段名处理 |

| BatchInsert (L316-388) | 表名、字段名处理 |

| Update (L598-638) | 表名、字段名处理 |

| Delete (L640-661) | 表名处理 |

核心改动模式(以 Select 为例):

// 之前
query += " FROM `" + that.Prefix + table + "` "
// 之后
query += " FROM " + that.processTable(table) + " "

第5步修改 WHERE 条件处理(db/where.go

修改 varCond 方法L205-338中的字段名处理

// 之前
if !strings.Contains(k, ".") {
    k = "`" + k + "`"
}
// 之后
k = that.processColumn(k)

同样修改 handlePlainFieldhandleDefaultCondition 等方法。

第6步修改链式构建器db/builder.go

LeftJoinRightJoin 等方法中的表名和条件字符串也需要处理:

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 | 扩展接口,添加 IdentifierProcessor |

| db/db.go | 添加 GetProcessor 和辅助方法 |

| db/crud.go | 修改所有 CRUD 方法 |

| db/where.go | 修改条件处理逻辑 |

| db/builder.go | 修改链式构建器的 JOIN 方法 |