- 在应用程序中新增对达梦数据库(DM)的配置和连接支持 - 实现 SetDmDB 函数以配置达梦数据库连接 - 更新数据库操作逻辑,支持达梦特有的 SQL 语法和功能 - 在相关文件中添加达梦数据库的处理逻辑,包括表创建、数据插入和查询 - 更新 go.mod 和 go.sum 文件以引入达梦数据库驱动 - 增强文档,详细说明达梦数据库的配置和使用方法
13 KiB
name, overview, todos, isProject
| name | overview | todos | isProject | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 支持达梦数据库 | 在 HoTime 框架的现有 Dialect 方言体系下,新增达梦(DM8)数据库方言实现,涉及驱动引入、方言适配、连接配置、缓存层/代码生成/备份模块的 DM 分支补充。 |
|
false |
支持达梦(DM8)数据库
现状分析
项目已有完善的 Dialect 接口体系(db/dialect.go),以及 initDialect() 工厂方法(db/db.go),当前支持 MySQL、PostgreSQL、SQLite 三种方言。需要新增达梦方言并在各处补充 case "dm" 分支。
flowchart TB
subgraph currentState [当前架构]
DialectIf["Dialect 接口<br/>(db/dialect.go)"]
MySQL["MySQLDialect"]
PG["PostgreSQLDialect"]
SQLite["SQLiteDialect"]
DialectIf --> MySQL
DialectIf --> PG
DialectIf --> SQLite
end
subgraph newState [新增]
DM["DMDialect"]
DialectIf --> DM
end
达梦 vs MySQL 关键 SQL 差异
- 标识符引号: 达梦使用双引号
"name"(同 PostgreSQL),MySQL 用反引号 - 自增列: 达梦用
IDENTITY(1,1),MySQL 用AUTO_INCREMENT - 占位符: 达梦使用
?(同 MySQL) - LastInsertId: 达梦不支持
LAST_INSERT_ID(),需用RETURNING "id"子句获取插入 ID(同 PostgreSQL) - Upsert: 达梦使用
MERGE INTO ... USING ... WHEN MATCHED ... WHEN NOT MATCHED ...(Oracle 风格) - 表存在检查:
SELECT TABLE_NAME FROM USER_TABLES WHERE TABLE_NAME='xxx' - 列信息查询:
SELECT COLUMN_NAME, DATA_TYPE, COMMENTS FROM USER_COL_COMMENTS ... - 时间戳转换: 无
FROM_UNIXTIME,需用DATEADD(SECOND, ts, TIMESTAMP '1970-01-01 00:00:00') - INSERT IGNORE: 不支持,需用
MERGE INTO替代 - DDL 导出: 无
SHOW CREATE TABLE,使用系统视图组合
涉及修改的文件
1. 新增达梦 Go 驱动(go module 方式)
使用达梦官方 module 化驱动 gitee.com/chunanyong/dm,通过标准 go module 流程引入:
go get gitee.com/chunanyong/dm
go mod vendor
- go.mod - 自动添加
gitee.com/chunanyong/dm依赖 - db/db.go - 添加
import _ "gitee.com/chunanyong/dm"并在initDialect()添加case "dm" vendor/目录通过go mod vendor自动同步
驱动注册名为 "dm",连接串格式:dm://user:password@host:port?schema=dbName
2. 新增 DMDialect 方言
在 db/dialect.go 末尾新增 DMDialect 结构体,实现 Dialect 接口全部方法:
Quote/QuoteIdentifier/QuoteChar- 使用双引号Placeholder- 返回?Placeholders- 返回逗号分隔的?SupportsLastInsertId- 返回false(达梦不支持 MySQL 的LAST_INSERT_ID())ReturningClause- 返回RETURNING "id"(用于 Insert 后获取自增 ID)UpsertSQL- 生成MERGE INTO ... USING (SELECT ? AS col1, ...) src ON (...) WHEN MATCHED THEN UPDATE SET ... WHEN NOT MATCHED THEN INSERT ...
3. 连接配置
- application.go - 新增
SetDmDB()函数,连接串格式:dm://user:password@host:port?schema=dbName - application.go 的
SetDB()中添加dbDm := db.GetMap("dm")分支 - var.go - 在
Config的"db"和ConfigNote的"db"下新增"dm"配置项
var.go 中 ConfigNote 新增的达梦配置说明如下:
"dm": Map{
"注释": "达梦数据库配置,除prefix及主从数据库slave项,其他全部必须",
"host": "默认127.0.0.1,必须,数据库ip地址",
"name": "默认SYSDBA,必须,数据库schema名称",
"user": "默认SYSDBA,必须,数据库用户名",
"password": "默认SYSDBA,必须,数据库密码",
"port": "默认5236,必须,数据库端口",
"prefix": "默认空,非必须,数据表前缀",
"slave": Map{
"注释": "从数据库配置,dm里配置slave项即启用主从读写,减少数据库压力",
"host": "默认127.0.0.1,必须,数据库ip地址",
"name": "默认SYSDBA,必须,数据库schema名称",
"user": "默认SYSDBA,必须,数据库用户名",
"password": "默认SYSDBA,必须,数据库密码",
"port": "默认5236,必须,数据库端口",
},
},
4. Insert 返回 ID 适配(关键兼容点)
当前 db/crud.go 的 Insert() 直接调用 res.LastInsertId() 获取插入 ID,但达梦不支持 LAST_INSERT_ID()。Dialect 接口已预留了 SupportsLastInsertId() 和 ReturningClause() 两个方法但 Insert() 未使用。
修改 Insert() 函数逻辑:
if that.Dialect != nil && !that.Dialect.SupportsLastInsertId() {
// 达梦/PostgreSQL: 用 RETURNING 子句 + QueryRow 获取 ID
returningClause := that.Dialect.ReturningClause("id")
query = strings.TrimSuffix(query, ";") + returningClause + ";"
var insertedId int64
err := that.QueryRow(query, values...).Scan(&insertedId)
// ...
id = insertedId
} else {
// MySQL/SQLite: 继续使用 Exec + LastInsertId
res, err := that.Exec(query, values...)
id1, _ := res.LastInsertId()
id = id1
}
同理,Upsert() 函数也需在 switch dbType 中增加 case "dm" 分支,调用新增的 buildDMUpsert() 方法生成 MERGE INTO 语法。
RowsAffected 无需额外处理:Update() 和 Delete() 使用的 res.RowsAffected() 在达梦中正常工作。
5. 缓存层适配
cache/cache_db.go 中需在以下函数增加 case "dm" 分支:
tableExists()- 使用SELECT TABLE_NAME FROM USER_TABLES WHERE TABLE_NAME='...'createMainTable()- 达梦建表 DDL(IDENTITY(1,1)代替AUTO_INCREMENT)createHistoryTable()- 同上migrateFromCached()- 使用MERGE INTO代替INSERT IGNORE,用DATEADD代替FROM_UNIXTIMEset()- 使用MERGE INTO实现 upsert
6. 代码生成适配
code/makecode.go 第 160-165 行,增加 case "dm" 分支:
if db.Type == "dm" {
tableInfo = db.Select("USER_TAB_COLUMNS",
"COLUMN_NAME AS name, DATA_TYPE AS type, COMMENTS AS label, NULLABLE AS must, DATA_DEFAULT AS dflt_value",
Map{"TABLE_NAME": v.GetString("name")})
}
(注:需要 JOIN USER_COL_COMMENTS 获取列注释)
7. 备份模块适配
db/backup.go 中:
backupDdl()-SHOW CREATE TABLE替换为通过系统视图(USER_CONS_COLUMNS+USER_TAB_COLUMNS)拼接 DDLbackupSave()/backupCol()- 将反引号替换为通过 Dialect 的Quote()方法动态生成
8. initDialect 注册
db/db.go 的 initDialect() 添加:
case "dm", "dameng":
that.Dialect = &DMDialect{}
驱动引入与 vendor 更新流程
采用标准 go module 方式,驱动包为 gitee.com/chunanyong/dm(达梦官方维护的 module 化版本):
# 1. 添加依赖到 go.mod
go get gitee.com/chunanyong/dm
# 2. 同步到 vendor 目录
go mod vendor
go mod vendor 会自动将 gitee.com/chunanyong/dm 及其传递依赖(github.com/golang/snappy、golang.org/x/text)下载并复制到 vendor/ 目录。后续只需提交 go.mod、go.sum 和 vendor/ 的变更即可。
9. 数据类型映射补充
db/query.go 的 classifyDBType() 函数(第 388-417 行)需要补充达梦特有类型:
NUMBER->dbTypeDecimal(达梦/Oracle 的通用数值类型)VARCHAR2->dbTypeUnknown(字符串类型,保持默认即可)
case "DECIMAL", "NUMERIC", "NEWDECIMAL", "NUMBER":
return dbTypeDecimal
10. 重构 example 目录 + 集成测试框架
当前问题:example/main.go 有 1427 行,所有测试逻辑堆在一个文件。
重构后目录结构:
example/
main.go -- 最小化:只有 Init + Run + 路由引用
config/
config.json -- mysql- (假注释禁用) + dm (启用)
app/
init.go -- Project 路由注册
test.go -- TestCtr: 通用 ORM 测试(数据库无关)
test_test.go -- TestTest: 测试定义
mysql.go -- MysqlCtr: MySQL 特有测试
mysql_test.go -- MysqlTest: MySQL 测试定义
dmdb.go -- DmdbCtr: 达梦特有测试
dmdb_test.go -- DmdbTest: 达梦测试定义
cache.go -- CacheCtr: 缓存测试(共用)
cache_test.go -- CacheTest: 缓存测试定义
app_test.go -- TestMain + TestApi + ProjectTest
拆分原则:
- TestCtr(通用 ORM 测试,数据库无关):
init(根据that.Db.Type建表)、crud、condition、chain、join、aggregate、pagination、batch、upsert、transaction - MysqlCtr(MySQL 特有):
tables(SHOW TABLES)、describe(DESCRIBE)、rawsql(反引号原生 SQL) - DmdbCtr(达梦特有):
tables(USER_TABLES)、describe(USER_TAB_COLUMNS)、rawsql(双引号原生 SQL) - CacheCtr(缓存测试,共用):
all、compat、batch
main.go 最小化:
package main
import (
. "code.hoteas.com/golang/hotime"
"code.hoteas.com/golang/hotime/example/app"
)
func main() {
appIns := Init("config/config.json")
appIns.Run(Router{"app": app.Project})
}
config.json 修改(mysql 改为 mysql- 假注释禁用,新增 dm):
{
"db": {
"mysql-": {
"host": "192.168.6.253",
"name": "dgs-cms2601301",
"password": "dasda8454456",
"port": "3306",
"user": "root"
},
"dm": {
"host": "172.31.31.5",
"port": "5236",
"user": "SYSDBA",
"password": "SC_sysdba2026",
"name": "SYSDBA"
}
},
"mode": 2,
"port": "8081"
}
测试框架集成(app_test.go),运行 go test ./example/app/... -v
注意事项
- 达梦默认对标识符大小写敏感(默认转大写),建表和查询时需注意统一使用双引号包裹
- 达梦的
MERGE INTO语法与 Oracle 基本一致,这是替代 MySQLON DUPLICATE KEY UPDATE和INSERT IGNORE的标准方式 - 达梦默认端口为 5236,默认管理员账户为 SYSDBA
- 达梦的
NOW()函数在 DM8 中可用,SYSDATE和CURRENT_TIMESTAMP也可用 - 达梦支持
LIMIT语法,分页查询无需额外适配 - 达梦 Go 驱动使用
?作为参数占位符,与 MySQL/SQLite 一致