diff --git a/.cursor/plans/多数据库方言与前缀支持_979672ee.plan.md b/.cursor/plans/多数据库方言与前缀支持_979672ee.plan.md deleted file mode 100644 index e244c73..0000000 --- a/.cursor/plans/多数据库方言与前缀支持_979672ee.plan.md +++ /dev/null @@ -1,208 +0,0 @@ ---- -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 方法 | \ No newline at end of file diff --git a/db/identifier.go b/db/identifier.go index 9dc23cd..e507ed4 100644 --- a/db/identifier.go +++ b/db/identifier.go @@ -20,9 +20,21 @@ func NewIdentifierProcessor(dialect Dialect, prefix string) *IdentifierProcessor } } +// 系统数据库列表,这些数据库不添加前缀 +var systemDatabases = map[string]bool{ + "INFORMATION_SCHEMA": true, + "information_schema": true, + "mysql": true, + "performance_schema": true, + "sys": true, + "pg_catalog": true, + "pg_toast": true, +} + // ProcessTableName 处理表名(添加前缀+引号) -// 输入: "order" 或 "`order`" 或 "\"order\"" +// 输入: "order" 或 "`order`" 或 "\"order\"" 或 "INFORMATION_SCHEMA.TABLES" // 输出: "`app_order`" (MySQL) 或 "\"app_order\"" (PostgreSQL/SQLite) +// 对于 database.table 格式,会分别处理,系统数据库不添加前缀 func (p *IdentifierProcessor) ProcessTableName(name string) string { // 去除已有的引号 name = p.stripQuotes(name) @@ -33,7 +45,23 @@ func (p *IdentifierProcessor) ProcessTableName(name string) string { parts := strings.SplitN(name, " ", 2) tableName := p.stripQuotes(parts[0]) alias := parts[1] - return p.dialect.QuoteIdentifier(p.prefix+tableName) + " " + alias + // 递归处理表名部分(可能包含点号) + return p.ProcessTableName(tableName) + " " + alias + } + + // 检查是否包含点号(database.table 格式) + if strings.Contains(name, ".") { + parts := p.splitTableColumn(name) + if len(parts) == 2 { + dbName := p.stripQuotes(parts[0]) + tableName := p.stripQuotes(parts[1]) + // 系统数据库不添加前缀 + if systemDatabases[dbName] { + return p.dialect.QuoteIdentifier(dbName) + "." + p.dialect.QuoteIdentifier(tableName) + } + // 非系统数据库,只给表名添加前缀 + return p.dialect.QuoteIdentifier(dbName) + "." + p.dialect.QuoteIdentifier(p.prefix+tableName) + } } // 添加前缀和引号 diff --git a/example/test_server.exe b/example/test_server.exe deleted file mode 100644 index 53c6319..0000000 Binary files a/example/test_server.exe and /dev/null differ