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

208 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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 方法 |