feat(db): 添加对达梦数据库的支持

- 在应用程序中新增对达梦数据库(DM)的配置和连接支持
- 实现 SetDmDB 函数以配置达梦数据库连接
- 更新数据库操作逻辑,支持达梦特有的 SQL 语法和功能
- 在相关文件中添加达梦数据库的处理逻辑,包括表创建、数据插入和查询
- 更新 go.mod 和 go.sum 文件以引入达梦数据库驱动
- 增强文档,详细说明达梦数据库的配置和使用方法
This commit is contained in:
2026-03-20 10:46:51 +08:00
parent 1546967918
commit 7f7b585ffb
194 changed files with 211502 additions and 2328 deletions
+43
View File
@@ -0,0 +1,43 @@
package app
import (
"os"
"testing"
. "code.hoteas.com/golang/hotime"
)
var ProjectTest = ProjTest{
"test": TestTest,
"mysql": MysqlTest,
"dmdb": DmdbTest,
"cache": CacheTest,
}
var testApp *TestApp
func TestMain(m *testing.M) {
// Go test 的 CWD 是包目录 example/app/,切回 example/ 使相对路径正确
_ = os.Chdir("..")
testApp = NewTestApp("config/config.json",
TestProj{
"app": {
Proj: Project,
Tests: ProjectTest,
},
},
)
// 在测试前初始化各数据库的测试表和种子数据
SetupDatabase(&testApp.Db)
code := m.Run()
testApp.PrintCoverage()
testApp.GenerateSwagger("My API", "1.0.0", testApp.Config.GetString("tpt"))
os.Exit(code)
}
func TestApi(t *testing.T) {
testApp.RunTests(t)
}
+294
View File
@@ -0,0 +1,294 @@
package app
import (
"fmt"
"time"
. "code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
)
// CacheCtr 缓存测试控制器(数据库无关)
var CacheCtr = Ctr{
"all": func(that *Context) { that.Display(0, testCacheAll(that)) },
"compat": func(that *Context) { that.Display(0, testCacheCompatible(that)) },
"batch": func(that *Context) {
that.Display(0, Map{"message": "批量缓存测试完成,请查看控制台输出和日志文件"})
},
}
func testCacheAll(that *Context) Map {
result := Map{"name": "数据库缓存测试", "tests": Slice{}}
tests := Slice{}
cacheMode := "unknown"
if that.Application.HoTimeCache != nil && that.Application.HoTimeCache.Config != nil {
dbConfig := that.Application.HoTimeCache.Config.GetMap("db")
if dbConfig != nil {
cacheMode = dbConfig.GetString("mode")
if cacheMode == "" {
cacheMode = "new"
}
}
}
result["cache_mode"] = cacheMode
testPrefix := fmt.Sprintf("cache_test_%d_", time.Now().UnixNano())
// 1. 基础读写测试
test1 := Map{"name": "1. 基础 set/get 测试"}
testKey1 := testPrefix + "basic"
testValue1 := Map{"name": "测试数据", "count": 123, "active": true}
that.Application.Cache(testKey1, testValue1)
cached1 := that.Application.Cache(testKey1)
if cached1.Data != nil {
cachedMap := cached1.ToMap()
test1["result"] = cachedMap.GetString("name") == "测试数据" && cachedMap.GetInt("count") == 123
test1["cached_value"] = cachedMap
} else {
test1["result"] = false
test1["error"] = "缓存读取返回 nil"
}
tests = append(tests, test1)
// 2. 删除缓存测试
test2 := Map{"name": "2. delete 删除缓存测试"}
testKey2 := testPrefix + "delete"
that.Application.Cache(testKey2, "删除测试值")
before := that.Application.Cache(testKey2)
beforeExists := before.Data != nil
that.Application.Cache(testKey2, nil)
after := that.Application.Cache(testKey2)
afterExists := after.Data != nil
test2["result"] = beforeExists && !afterExists
test2["before_exists"] = beforeExists
test2["after_exists"] = afterExists
tests = append(tests, test2)
// 3. 过期时间测试
test3 := Map{"name": "3. 过期时间测试(短超时)"}
testKey3 := testPrefix + "expire"
that.Application.Cache(testKey3, "短期数据", 2)
immediate := that.Application.Cache(testKey3)
immediateExists := immediate.Data != nil
test3["result"] = immediateExists
test3["immediate_exists"] = immediateExists
test3["note"] = "设置了2秒过期,可等待后再次访问验证过期"
tests = append(tests, test3)
// 4. 不存在的 key 读取测试
test4 := Map{"name": "4. 不存在的 key 读取测试"}
nonExistKey := testPrefix + "non_exist_key_" + fmt.Sprintf("%d", time.Now().UnixNano())
nonExist := that.Application.Cache(nonExistKey)
test4["result"] = nonExist.Data == nil
test4["value"] = nonExist.Data
tests = append(tests, test4)
// 5. 重复 set 同一个 key 测试
test5 := Map{"name": "5. 重复 set 同一个 key 测试"}
testKey5 := testPrefix + "repeat"
that.Application.Cache(testKey5, "第一次值")
first := that.Application.Cache(testKey5).ToStr()
that.Application.Cache(testKey5, "第二次值")
second := that.Application.Cache(testKey5).ToStr()
that.Application.Cache(testKey5, Map{"version": 3})
third := that.Application.Cache(testKey5).ToMap()
test5["result"] = first == "第一次值" && second == "第二次值" && third.GetInt("version") == 3
test5["first"] = first
test5["second"] = second
test5["third"] = third
tests = append(tests, test5)
// 6. 通配删除测试
test6 := Map{"name": "6. 通配删除测试 (key*)"}
wildcardPrefix := testPrefix + "wildcard_"
that.Application.Cache(wildcardPrefix+"a", "值A")
that.Application.Cache(wildcardPrefix+"b", "值B")
that.Application.Cache(wildcardPrefix+"c", "值C")
aExists := that.Application.Cache(wildcardPrefix+"a").Data != nil
bExists := that.Application.Cache(wildcardPrefix+"b").Data != nil
cExists := that.Application.Cache(wildcardPrefix+"c").Data != nil
allExistBefore := aExists && bExists && cExists
that.Application.Cache(wildcardPrefix+"*", nil)
aAfter := that.Application.Cache(wildcardPrefix+"a").Data != nil
bAfter := that.Application.Cache(wildcardPrefix+"b").Data != nil
cAfter := that.Application.Cache(wildcardPrefix+"c").Data != nil
allDeletedAfter := !aAfter && !bAfter && !cAfter
test6["result"] = allExistBefore && allDeletedAfter
test6["before"] = Map{"a": aExists, "b": bExists, "c": cExists}
test6["after"] = Map{"a": aAfter, "b": bAfter, "c": cAfter}
tests = append(tests, test6)
// 7. 不同数据类型测试
test7 := Map{"name": "7. 不同数据类型存储测试"}
that.Application.Cache(testPrefix+"type_string", "字符串值")
typeString := that.Application.Cache(testPrefix + "type_string").ToStr()
that.Application.Cache(testPrefix+"type_int", 12345)
typeInt := that.Application.Cache(testPrefix + "type_int").ToInt()
that.Application.Cache(testPrefix+"type_float", 3.14159)
typeFloat := that.Application.Cache(testPrefix + "type_float").ToFloat64()
that.Application.Cache(testPrefix+"type_bool", true)
typeBoolData := that.Application.Cache(testPrefix + "type_bool").Data
typeBool := typeBoolData == true || typeBoolData == "true" || typeBoolData == 1.0
that.Application.Cache(testPrefix+"type_map", Map{"key": "value", "num": 100})
typeMap := that.Application.Cache(testPrefix + "type_map").ToMap()
that.Application.Cache(testPrefix+"type_slice", Slice{1, 2, 3, "four", Map{"five": 5}})
typeSlice := that.Application.Cache(testPrefix + "type_slice").ToSlice()
test7["result"] = typeString == "字符串值" &&
typeInt == 12345 &&
typeFloat > 3.14 && typeFloat < 3.15 &&
typeBool == true &&
typeMap.GetString("key") == "value" &&
len(typeSlice) == 5
test7["string"] = typeString
test7["int"] = typeInt
test7["float"] = typeFloat
test7["bool"] = typeBool
test7["map"] = typeMap
test7["slice"] = typeSlice
tests = append(tests, test7)
// 8. 自定义超时时间测试
test8 := Map{"name": "8. 自定义超时时间参数测试"}
testKey8 := testPrefix + "custom_timeout"
that.Application.Cache(testKey8, "长期数据", 3600)
longTerm := that.Application.Cache(testKey8)
test8["result"] = longTerm.Data != nil
test8["value"] = longTerm.ToStr()
tests = append(tests, test8)
// 9. 查询缓存表状态
test9 := Map{"name": "9. 缓存表状态查询"}
prefix := that.Db.GetPrefix()
newTableName := prefix + "hotime_cache"
newCount := that.Db.Count(newTableName)
test9["new_table_count"] = newCount
test9["new_table_name"] = newTableName
test9["result"] = newCount >= 0
tests = append(tests, test9)
that.Application.Cache(testPrefix+"*", nil)
result["tests"] = tests
result["success"] = true
result["cleanup"] = "已清理所有测试缓存数据"
return result
}
func testCacheCompatible(that *Context) Map {
result := Map{
"test_name": "兼容模式白盒测试",
"timestamp": time.Now().Format("2006-01-02 15:04:05"),
}
prefix := that.Db.GetPrefix()
newTableName := prefix + "hotime_cache"
legacyTableName := prefix + "cached"
tests := Slice{}
test1 := Map{"name": "1. 查询当前缓存模式"}
cacheConfig := that.Application.Config.GetMap("cache")
dbConfig := cacheConfig.GetMap("db")
mode := dbConfig.GetString("mode")
if mode == "" {
mode = "默认(compatible)"
}
test1["mode"] = mode
test1["result"] = true
tests = append(tests, test1)
// 根据数据库类型使用不同的引号
q := "`"
if that.Db.Dialect != nil {
q = that.Db.Dialect.QuoteChar()
}
test2 := Map{"name": "2. 查询老表cached现有数据"}
legacyData := that.Db.Query("SELECT * FROM " + q + legacyTableName + q + " LIMIT 5")
test2["legacy_table"] = legacyTableName
test2["count"] = len(legacyData)
test2["data"] = legacyData
test2["result"] = true
tests = append(tests, test2)
test3 := Map{"name": "3. 查询新表hotime_cache现有数据"}
newData := that.Db.Query("SELECT * FROM " + q + newTableName + q + " LIMIT 5")
test3["new_table"] = newTableName
test3["count"] = len(newData)
test3["data"] = newData
test3["result"] = true
tests = append(tests, test3)
test4 := Map{"name": "4. 测试兼容模式老表回退读取"}
testKey4 := "test_compat_fallback_" + ObjToStr(time.Now().UnixNano())
testValue4 := Map{"admin_id": 999, "admin_name": "测试老数据"}
testValueJson4 := ObjToStr(Map{"data": testValue4})
that.Db.Insert(legacyTableName, Map{
"key": testKey4,
"value": testValueJson4,
"endtime": time.Now().Unix() + 3600,
"time": time.Now().UnixNano(),
})
test4["test_key"] = testKey4
newExists := that.Db.Get(newTableName, "*", Map{"key": testKey4})
test4["key_in_new_table"] = newExists != nil
cacheValue := that.Application.Cache(testKey4)
test4["cache_api_result"] = cacheValue.Data
test4["result"] = newExists == nil && cacheValue.Data != nil
tests = append(tests, test4)
test5 := Map{"name": "5. 测试兼容模式写新删老"}
testKey5 := "test_compat_write_" + ObjToStr(time.Now().UnixNano())
testValue5 := "兼容模式测试数据"
that.Db.Insert(legacyTableName, Map{
"key": testKey5,
"value": `{"data":"老表原始数据"}`,
"endtime": time.Now().Unix() + 3600,
"time": time.Now().UnixNano(),
})
legacyBefore := that.Db.Get(legacyTableName, "*", Map{"key": testKey5})
test5["step1_legacy_before"] = legacyBefore != nil
that.Application.Cache(testKey5, testValue5)
newAfter := that.Db.Get(newTableName, "*", Map{"key": testKey5})
test5["step2_new_after"] = newAfter != nil
if newAfter != nil {
test5["new_value"] = newAfter.GetString("value")
}
legacyAfter := that.Db.Get(legacyTableName, "*", Map{"key": testKey5})
test5["step3_legacy_after_deleted"] = legacyAfter == nil
test5["result"] = legacyBefore != nil && newAfter != nil && legacyAfter == nil
tests = append(tests, test5)
test6 := Map{"name": "6. 测试兼容模式删除(删除两表)"}
testKey6 := "test_compat_delete_" + ObjToStr(time.Now().UnixNano())
that.Db.Insert(legacyTableName, Map{
"key": testKey6,
"value": `{"data":"待删除老数据"}`,
"endtime": time.Now().Unix() + 3600,
"time": time.Now().UnixNano(),
})
that.Db.Insert(newTableName, Map{
"key": testKey6,
"value": `"待删除新数据"`,
"end_time": time.Now().Add(time.Hour).Format("2006-01-02 15:04:05"),
"state": 0,
"create_time": time.Now().Format("2006-01-02 15:04:05"),
"modify_time": time.Now().Format("2006-01-02 15:04:05"),
})
test6["before_legacy"] = that.Db.Get(legacyTableName, "*", Map{"key": testKey6}) != nil
test6["before_new"] = that.Db.Get(newTableName, "*", Map{"key": testKey6}) != nil
that.Application.Cache(testKey6, nil)
test6["after_legacy_deleted"] = that.Db.Get(legacyTableName, "*", Map{"key": testKey6}) == nil
test6["after_new_deleted"] = that.Db.Get(newTableName, "*", Map{"key": testKey6}) == nil
test6["result"] = test6.GetBool("before_legacy") && test6.GetBool("before_new") &&
test6.GetBool("after_legacy_deleted") && test6.GetBool("after_new_deleted")
tests = append(tests, test6)
that.Db.Delete(newTableName, Map{"key[~]": "test_compat_%"})
that.Db.Delete(legacyTableName, Map{"key[~]": "test_compat_%"})
result["tests"] = tests
result["success"] = true
return result
}
+11
View File
@@ -0,0 +1,11 @@
package app
import (
. "code.hoteas.com/golang/hotime"
)
var CacheTest = CtrTest{
"all": {Desc: "缓存全部测试", Func: func(a *Api) { a.Get("缓存全部测试", 0) }},
"compat": {Desc: "缓存兼容模式测试", Func: func(a *Api) { a.Get("兼容模式测试", 0) }},
"batch": {Desc: "批量缓存测试", Func: func(a *Api) { a.Get("批量缓存测试", 0) }},
}
+88
View File
@@ -0,0 +1,88 @@
package app
import (
. "code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
)
// DmdbCtr 达梦数据库特有测试控制器
var DmdbCtr = Ctr{
"tables": func(that *Context) {
if that.Db.Type != "dm" && that.Db.Type != "dameng" {
that.Display(0, Map{"skip": true, "reason": "当前非达梦数据库"})
return
}
tables := that.Db.Query(`SELECT TABLE_NAME AS "name", COMMENTS AS "label" FROM USER_TAB_COMMENTS WHERE TABLE_TYPE='TABLE'`)
that.Display(0, Map{"tables": tables})
},
"describe": func(that *Context) {
if that.Db.Type != "dm" && that.Db.Type != "dameng" {
that.Display(0, Map{"skip": true, "reason": "当前非达梦数据库"})
return
}
tableName := that.Req.FormValue("table")
if tableName == "" {
that.Display(1, "请提供 table 参数")
return
}
columns := that.Db.Query(`SELECT c.COLUMN_NAME AS "name", c.DATA_TYPE AS "type", m.COMMENTS AS "label", c.NULLABLE AS "must" FROM USER_TAB_COLUMNS c LEFT JOIN USER_COL_COMMENTS m ON c.TABLE_NAME=m.TABLE_NAME AND c.COLUMN_NAME=m.COLUMN_NAME WHERE c.TABLE_NAME='` + tableName + `' ORDER BY c.COLUMN_ID`)
data := that.Db.Select(tableName, "*", Map{"LIMIT": 10})
that.Display(0, Map{"table": tableName, "columns": columns, "sample_data": data})
},
"rawsql": func(that *Context) {
if that.Db.Type != "dm" && that.Db.Type != "dameng" {
that.Display(0, Map{"skip": true, "reason": "当前非达梦数据库"})
return
}
result := testDmRawSQL(that)
that.Display(0, result)
},
}
func testDmRawSQL(that *Context) Map {
result := Map{"name": "达梦原生SQL测试", "tests": Slice{}}
tests := Slice{}
test1 := Map{"name": "Query 原生查询 (双引号)"}
articles1 := that.Db.Query(`SELECT "id", "title", "author" FROM "article" WHERE "state" = ? LIMIT ?`, 0, 5)
test1["result"] = len(articles1) >= 0
test1["count"] = len(articles1)
tests = append(tests, test1)
test2 := Map{"name": "Exec 原生执行 (双引号)"}
testArticle := that.Db.Get("article", "id", Map{"state": 0})
if testArticle != nil {
res, err := that.Db.Exec(`UPDATE "article" SET "modify_time" = NOW() WHERE "id" = ?`, testArticle.GetInt64("id"))
if err.GetError() == nil && res != nil {
affected, _ := res.RowsAffected()
test2["result"] = affected >= 0
test2["affected"] = affected
} else {
test2["result"] = false
test2["error"] = err.GetError()
}
} else {
test2["result"] = true
test2["note"] = "无可用测试数据"
}
tests = append(tests, test2)
test3 := Map{"name": "USER_TABLES 查询"}
tables := that.Db.Query(`SELECT TABLE_NAME FROM USER_TABLES`)
test3["result"] = len(tables) >= 0
test3["count"] = len(tables)
tests = append(tests, test3)
test4 := Map{"name": "USER_TAB_COLUMNS 查询"}
cols := that.Db.Query(`SELECT COLUMN_NAME, DATA_TYPE FROM USER_TAB_COLUMNS WHERE TABLE_NAME='admin' ORDER BY COLUMN_ID`)
test4["result"] = len(cols) >= 0
test4["count"] = len(cols)
test4["columns"] = cols
tests = append(tests, test4)
result["tests"] = tests
result["success"] = true
return result
}
+12
View File
@@ -0,0 +1,12 @@
package app
import (
. "code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
)
var DmdbTest = CtrTest{
"tables": {Desc: "查询达梦所有表", Func: func(a *Api) { a.Get("USER_TABLES 查询", 0) }},
"describe": {Desc: "查询达梦表结构", Func: func(a *Api) { a.Query(Map{"table": "admin"}).Get("USER_TAB_COLUMNS 查询", 0) }},
"rawsql": {Desc: "达梦原生 SQL 测试", Func: func(a *Api) { a.Get("达梦原生SQL", 0) }},
}
+12
View File
@@ -0,0 +1,12 @@
package app
import (
. "code.hoteas.com/golang/hotime"
)
var Project = Proj{
"test": TestCtr,
"mysql": MysqlCtr,
"dmdb": DmdbCtr,
"cache": CacheCtr,
}
+96
View File
@@ -0,0 +1,96 @@
package app
import (
. "code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
)
// MysqlCtr MySQL 特有测试控制器
var MysqlCtr = Ctr{
"tables": func(that *Context) {
if that.Db.Type != "mysql" {
that.Display(0, Map{"skip": true, "reason": "当前非 MySQL 数据库"})
return
}
tables := that.Db.Query("SHOW TABLES")
that.Display(0, Map{"tables": tables})
},
"describe": func(that *Context) {
if that.Db.Type != "mysql" {
that.Display(0, Map{"skip": true, "reason": "当前非 MySQL 数据库"})
return
}
tableName := that.Req.FormValue("table")
if tableName == "" {
that.Display(1, "请提供 table 参数")
return
}
columns := that.Db.Query("DESCRIBE " + tableName)
data := that.Db.Select(tableName, Map{"LIMIT": 10})
that.Display(0, Map{"table": tableName, "columns": columns, "sample_data": data})
},
"rawsql": func(that *Context) {
if that.Db.Type != "mysql" {
that.Display(0, Map{"skip": true, "reason": "当前非 MySQL 数据库"})
return
}
result := testMysqlRawSQL(that)
that.Display(0, result)
},
}
func testMysqlRawSQL(that *Context) Map {
result := Map{"name": "MySQL原生SQL测试", "tests": Slice{}}
tests := Slice{}
test1 := Map{"name": "Query 原生查询 (反引号)"}
articles1 := that.Db.Query("SELECT id, title, author FROM `article` WHERE state = ? LIMIT ?", 0, 5)
test1["result"] = len(articles1) >= 0
test1["count"] = len(articles1)
tests = append(tests, test1)
test2 := Map{"name": "Exec 原生执行 (反引号)"}
testArticle := that.Db.Get("article", "id", Map{"state": 0})
if testArticle != nil {
res, err := that.Db.Exec("UPDATE `article` SET modify_time = NOW() WHERE id = ?", testArticle.GetInt64("id"))
if err.GetError() == nil && res != nil {
affected, _ := res.RowsAffected()
test2["result"] = affected >= 0
test2["affected"] = affected
} else {
test2["result"] = false
test2["error"] = err.GetError()
}
} else {
test2["result"] = true
test2["note"] = "无可用测试数据"
}
tests = append(tests, test2)
test3 := Map{"name": "IN (?) 非空数组展开"}
articles3 := that.Db.Query("SELECT id, title FROM `article` WHERE id IN (?) LIMIT 10", []int{1, 2, 3, 4, 5})
test3["result"] = len(articles3) >= 0
test3["count"] = len(articles3)
test3["lastQuery"] = that.Db.LastQuery
tests = append(tests, test3)
test4 := Map{"name": "IN (?) 空数组替换为1=0"}
articles4 := that.Db.Query("SELECT id, title FROM `article` WHERE id IN (?) LIMIT 10", []int{})
test4["result"] = len(articles4) == 0
test4["count"] = len(articles4)
test4["lastQuery"] = that.Db.LastQuery
tests = append(tests, test4)
test5 := Map{"name": "NOT IN (?) 空数组替换为1=1"}
articles5 := that.Db.Query("SELECT id, title FROM `article` WHERE id NOT IN (?) LIMIT 10", []int{})
test5["result"] = len(articles5) > 0
test5["count"] = len(articles5)
test5["lastQuery"] = that.Db.LastQuery
tests = append(tests, test5)
result["tests"] = tests
result["success"] = true
return result
}
+12
View File
@@ -0,0 +1,12 @@
package app
import (
. "code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
)
var MysqlTest = CtrTest{
"tables": {Desc: "查询 MySQL 所有表", Func: func(a *Api) { a.Get("SHOW TABLES", 0) }},
"describe": {Desc: "查询 MySQL 表结构", Func: func(a *Api) { a.Query(Map{"table": "admin"}).Get("DESCRIBE admin", 0) }},
"rawsql": {Desc: "MySQL 原生 SQL 测试", Func: func(a *Api) { a.Get("MySQL原生SQL", 0) }},
}
+126
View File
@@ -0,0 +1,126 @@
package app
import (
"fmt"
. "code.hoteas.com/golang/hotime/db"
)
// SetupDMDatabase 为达梦数据库创建测试表并灌入测试数据
func SetupDMDatabase(db *HoTimeDB) {
if db.Type != "dm" && db.Type != "dameng" {
return
}
fmt.Println("[DM Setup] 开始初始化达梦数据库测试环境...")
prefix := db.GetPrefix()
createDMTables(db, prefix)
seedTestData(db, "DM")
fmt.Println("[DM Setup] 达梦数据库测试环境初始化完成")
}
func dmTableExists(db *HoTimeDB, tableName string) bool {
// COUNT(*) 即使表为空也会返回一行;若表不存在则报错返回空切片。
// 相比 USER_TABLES,这里直接检测当前 schema 下表的可访问性,避免
// 连接 schema?schema=TEST)与 USER_TABLESSYSDBA 自身 schema)错配。
res := db.Query(`SELECT COUNT(*) as cnt FROM "` + tableName + `"`)
return len(res) > 0
}
func createDMTables(db *HoTimeDB, prefix string) {
// admin 表
tbl := prefix + "admin"
if !dmTableExists(db, tbl) {
fmt.Println("[DM Setup] 创建表:", tbl)
db.Exec(`CREATE TABLE "` + tbl + `" (
"id" INT IDENTITY(1,1) PRIMARY KEY,
"name" VARCHAR(100),
"phone" VARCHAR(20),
"state" INT DEFAULT 0,
"password" VARCHAR(100),
"role_id" INT DEFAULT 0,
"title" VARCHAR(100),
"create_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"modify_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`)
db.Exec(`CREATE UNIQUE INDEX "uk_` + tbl + `_phone" ON "` + tbl + `" ("phone")`)
}
// ctg 分类表
tbl = prefix + "ctg"
if !dmTableExists(db, tbl) {
fmt.Println("[DM Setup] 创建表:", tbl)
db.Exec(`CREATE TABLE "` + tbl + `" (
"id" INT IDENTITY(1,1) PRIMARY KEY,
"name" VARCHAR(100),
"state" INT DEFAULT 0,
"create_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"modify_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`)
}
// article 文章表
tbl = prefix + "article"
if !dmTableExists(db, tbl) {
fmt.Println("[DM Setup] 创建表:", tbl)
db.Exec(`CREATE TABLE "` + tbl + `" (
"id" INT IDENTITY(1,1) PRIMARY KEY,
"title" VARCHAR(200),
"author" VARCHAR(100),
"content" CLOB,
"state" INT DEFAULT 0,
"click_num" INT DEFAULT 0,
"sort" INT DEFAULT 0,
"img" VARCHAR(500),
"ctg_id" INT DEFAULT 0,
"admin_id" INT DEFAULT 0,
"create_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"modify_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`)
}
// test_batch 测试批量表
tbl = prefix + "test_batch"
if !dmTableExists(db, tbl) {
fmt.Println("[DM Setup] 创建表:", tbl)
db.Exec(`CREATE TABLE "` + tbl + `" (
"id" INT IDENTITY(1,1) PRIMARY KEY,
"name" VARCHAR(100),
"title" VARCHAR(200),
"state" INT DEFAULT 0,
"create_time" TIMESTAMP
)`)
}
// cached 老版本缓存表(兼容模式测试需要)
tbl = prefix + "cached"
if !dmTableExists(db, tbl) {
fmt.Println("[DM Setup] 创建表:", tbl)
db.Exec(`CREATE TABLE "` + tbl + `" (
"id" INT IDENTITY(1,1) PRIMARY KEY,
"key" VARCHAR(64),
"value" CLOB,
"endtime" BIGINT,
"time" BIGINT
)`)
}
// hotime_cache 新版缓存表
tbl = prefix + "hotime_cache"
if !dmTableExists(db, tbl) {
fmt.Println("[DM Setup] 创建表:", tbl)
db.Exec(`CREATE TABLE "` + tbl + `" (
"id" INT IDENTITY(1,1) PRIMARY KEY,
"key" VARCHAR(64) NOT NULL,
"value" TEXT,
"end_time" TIMESTAMP,
"state" INT DEFAULT 0,
"create_time" TIMESTAMP,
"modify_time" TIMESTAMP,
CONSTRAINT "uk_` + tbl + `_key" UNIQUE ("key")
)`)
db.Exec(`CREATE INDEX "idx_` + tbl + `_end_time" ON "` + tbl + `" ("end_time")`)
}
}
+131
View File
@@ -0,0 +1,131 @@
package app
import (
"fmt"
. "code.hoteas.com/golang/hotime/db"
)
// SetupMySQLDatabase 为 MySQL 数据库创建测试表并灌入测试数据
func SetupMySQLDatabase(db *HoTimeDB) {
if db.Type != "mysql" {
return
}
fmt.Println("[MySQL Setup] 开始初始化 MySQL 数据库测试环境...")
prefix := db.GetPrefix()
createMySQLTables(db, prefix)
seedTestData(db, "MySQL")
fmt.Println("[MySQL Setup] MySQL 数据库测试环境初始化完成")
}
func mysqlTableExists(db *HoTimeDB, tableName string) bool {
rows := db.Query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=?", tableName)
return len(rows) > 0
}
func createMySQLTables(db *HoTimeDB, prefix string) {
// admin 表
tbl := prefix + "admin"
if !mysqlTableExists(db, tbl) {
fmt.Println("[MySQL Setup] 创建表:", tbl)
db.Exec("CREATE TABLE `" + tbl + "` (" +
"`id` int(11) unsigned NOT NULL AUTO_INCREMENT," +
"`name` varchar(100) DEFAULT NULL," +
"`phone` varchar(20) DEFAULT NULL," +
"`state` int(2) DEFAULT '0'," +
"`password` varchar(100) DEFAULT NULL," +
"`role_id` int(11) DEFAULT '0'," +
"`title` varchar(100) DEFAULT NULL," +
"`create_time` datetime DEFAULT NULL," +
"`modify_time` datetime DEFAULT NULL," +
"PRIMARY KEY (`id`)," +
"UNIQUE KEY `uk_phone` (`phone`)" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4")
}
// ctg 分类表
tbl = prefix + "ctg"
if !mysqlTableExists(db, tbl) {
fmt.Println("[MySQL Setup] 创建表:", tbl)
db.Exec("CREATE TABLE `" + tbl + "` (" +
"`id` int(11) unsigned NOT NULL AUTO_INCREMENT," +
"`name` varchar(100) DEFAULT NULL," +
"`state` int(2) DEFAULT '0'," +
"`create_time` datetime DEFAULT NULL," +
"`modify_time` datetime DEFAULT NULL," +
"PRIMARY KEY (`id`)" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4")
}
// article 文章表
tbl = prefix + "article"
if !mysqlTableExists(db, tbl) {
fmt.Println("[MySQL Setup] 创建表:", tbl)
db.Exec("CREATE TABLE `" + tbl + "` (" +
"`id` int(11) unsigned NOT NULL AUTO_INCREMENT," +
"`title` varchar(200) DEFAULT NULL," +
"`author` varchar(100) DEFAULT NULL," +
"`content` text," +
"`state` int(2) DEFAULT '0'," +
"`click_num` int(11) DEFAULT '0'," +
"`sort` int(11) DEFAULT '0'," +
"`img` varchar(500) DEFAULT NULL," +
"`ctg_id` int(11) DEFAULT '0'," +
"`admin_id` int(11) DEFAULT '0'," +
"`create_time` datetime DEFAULT NULL," +
"`modify_time` datetime DEFAULT NULL," +
"PRIMARY KEY (`id`)," +
"KEY `idx_state` (`state`)," +
"KEY `idx_ctg_id` (`ctg_id`)" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4")
}
// test_batch 测试批量表
tbl = prefix + "test_batch"
if !mysqlTableExists(db, tbl) {
fmt.Println("[MySQL Setup] 创建表:", tbl)
db.Exec("CREATE TABLE `" + tbl + "` (" +
"`id` int(11) unsigned NOT NULL AUTO_INCREMENT," +
"`name` varchar(100) DEFAULT NULL," +
"`title` varchar(200) DEFAULT NULL," +
"`state` int(2) DEFAULT '0'," +
"`create_time` datetime DEFAULT NULL," +
"PRIMARY KEY (`id`)" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4")
}
// cached 老版本缓存表(兼容模式测试需要)
tbl = prefix + "cached"
if !mysqlTableExists(db, tbl) {
fmt.Println("[MySQL Setup] 创建表:", tbl)
db.Exec("CREATE TABLE `" + tbl + "` (" +
"`id` int(11) unsigned NOT NULL AUTO_INCREMENT," +
"`key` varchar(64) DEFAULT NULL," +
"`value` text," +
"`endtime` bigint(20) DEFAULT NULL," +
"`time` bigint(20) DEFAULT NULL," +
"PRIMARY KEY (`id`)" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4")
}
// hotime_cache 新版缓存表
tbl = prefix + "hotime_cache"
if !mysqlTableExists(db, tbl) {
fmt.Println("[MySQL Setup] 创建表:", tbl)
db.Exec("CREATE TABLE `" + tbl + "` (" +
"`id` int(11) unsigned NOT NULL AUTO_INCREMENT," +
"`key` varchar(64) NOT NULL," +
"`value` text," +
"`end_time` datetime DEFAULT NULL," +
"`state` int(2) DEFAULT '0'," +
"`create_time` datetime DEFAULT NULL," +
"`modify_time` datetime DEFAULT NULL," +
"PRIMARY KEY (`id`)," +
"UNIQUE KEY `uk_key` (`key`)," +
"KEY `idx_end_time` (`end_time`)" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4")
}
}
+79
View File
@@ -0,0 +1,79 @@
package app
import (
"fmt"
. "code.hoteas.com/golang/hotime/db"
)
// SetupDatabase 根据数据库类型自动初始化测试表和种子数据
// 在 TestMain 中调用一次即可
func SetupDatabase(db *HoTimeDB) {
switch db.Type {
case "dm", "dameng":
SetupDMDatabase(db)
case "mysql":
SetupMySQLDatabase(db)
}
}
// seedTestData 通用种子数据灌入(DM 和 MySQL 共用)
// 使用 ORM 的 Insert 方法,与数据库方言无关
func seedTestData(db *HoTimeDB, tag string) {
// 已有数据则跳过
if db.Count("ctg") > 0 {
fmt.Printf("[%s Setup] 表中已有数据,跳过数据灌入\n", tag)
return
}
fmt.Printf("[%s Setup] 灌入测试数据...\n", tag)
// 分类数据
ctgs := []map[string]interface{}{
{"name": "新闻资讯", "state": 0},
{"name": "技术文档", "state": 0},
{"name": "公告通知", "state": 0},
{"name": "产品介绍", "state": 0},
{"name": "已归档", "state": 1},
}
for _, c := range ctgs {
c["create_time[#]"] = "NOW()"
c["modify_time[#]"] = "NOW()"
db.Insert("ctg", c)
}
// 管理员数据
admins := []map[string]interface{}{
{"name": "超级管理员", "phone": "13800000001", "state": 1, "password": "admin123", "role_id": 1, "title": "系统管理员"},
{"name": "编辑员", "phone": "13800000002", "state": 1, "password": "editor123", "role_id": 2, "title": "内容编辑"},
{"name": "审核员", "phone": "13800000003", "state": 1, "password": "review123", "role_id": 3, "title": "内容审核"},
}
for _, a := range admins {
a["create_time[#]"] = "NOW()"
a["modify_time[#]"] = "NOW()"
db.Insert("admin", a)
}
// 文章数据 — 覆盖不同状态、分类、点击数、排序
articles := []map[string]interface{}{
{"title": "数据库入门指南", "author": "管理员", "content": "国产关系型数据库管理系统入门...", "state": 0, "click_num": 128, "sort": 10, "ctg_id": 1, "admin_id": 1},
{"title": "新闻发布系统上线公告", "author": "编辑员", "content": "本系统已正式上线运行...", "state": 0, "click_num": 256, "sort": 5, "ctg_id": 1, "admin_id": 2},
{"title": "Go语言最佳实践", "author": "管理员", "content": "Go语言在后端开发中的最佳实践总结...", "state": 0, "click_num": 512, "sort": 8, "ctg_id": 2, "admin_id": 1},
{"title": "API接口设计规范", "author": "审核员", "content": "RESTful API设计的基本规范和标准...", "state": 0, "click_num": 64, "sort": 15, "ctg_id": 2, "admin_id": 3},
{"title": "系统维护通知", "author": "管理员", "content": "系统将于本周日凌晨进行维护升级...", "state": 0, "click_num": 32, "sort": 1, "ctg_id": 3, "admin_id": 1},
{"title": "产品功能更新说明", "author": "编辑员", "content": "新版本增加了以下功能...", "state": 0, "click_num": 96, "sort": 12, "ctg_id": 4, "admin_id": 2},
{"title": "数据库性能优化指南", "author": "管理员", "content": "数据库性能优化的常见方法和技巧...", "state": 0, "click_num": 1024, "sort": 3, "ctg_id": 2, "admin_id": 1},
{"title": "缓存机制深入分析", "author": "审核员", "content": "缓存在Web应用中的重要作用...", "state": 0, "click_num": 200, "sort": 7, "ctg_id": 2, "admin_id": 3},
{"title": "旧版公告(已归档)", "author": "管理员", "content": "此公告已过期归档...", "state": 1, "click_num": 10, "sort": 0, "ctg_id": 5, "admin_id": 1},
{"title": "测试文章(隐藏)", "author": "编辑员", "content": "测试用文章不公开显示...", "state": 2, "click_num": 0, "sort": 0, "ctg_id": 1, "admin_id": 2},
{"title": "高点击量文章", "author": "编辑员", "content": "这是一篇点击量很高的文章...", "state": 0, "click_num": 9999, "sort": 2, "ctg_id": 1, "admin_id": 2},
}
for _, a := range articles {
a["create_time[#]"] = "NOW()"
a["modify_time[#]"] = "NOW()"
db.Insert("article", a)
}
fmt.Printf("[%s Setup] 数据灌入完成: ctg=%d, admin=%d, article=%d\n",
tag, db.Count("ctg"), db.Count("admin"), db.Count("article"))
}
+747
View File
@@ -0,0 +1,747 @@
package app
import (
"fmt"
"time"
. "code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
. "code.hoteas.com/golang/hotime/db"
)
// initTestTables 根据数据库类型初始化测试表
func initTestTables(that *Context) {
switch that.Db.Type {
case "dm", "dameng":
// 用 COUNT(*) 直接探查表可访问性,而非 USER_TABLES(后者受 schema 错配影响)
exists := that.Db.Query(`SELECT COUNT(*) as cnt FROM "test_batch"`)
if len(exists) == 0 {
that.Db.Exec(`CREATE TABLE "test_batch" (
"id" INT IDENTITY(1,1) PRIMARY KEY,
"name" VARCHAR(100),
"title" VARCHAR(200),
"state" INT DEFAULT 0,
"create_time" TIMESTAMP
)`)
}
default:
that.Db.Exec(`CREATE TABLE IF NOT EXISTS test_batch (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
title VARCHAR(200),
state INT DEFAULT 0,
create_time DATETIME
)`)
}
_ = that.Db.Count("admin")
_ = that.Db.Count("article")
}
// nowFunc 返回当前数据库的 NOW() 函数表达式
func nowFunc(that *Context) string {
return "NOW()"
}
// TestCtr 通用 ORM 测试控制器(数据库无关)
var TestCtr = Ctr{
"test": func(that *Context) {
that.Display(2, "dsadasd")
},
"all": func(that *Context) {
results := Map{}
initTestTables(that)
results["1_basic_crud"] = testBasicCRUD(that)
results["2_condition_syntax"] = testConditionSyntax(that)
results["3_chain_query"] = testChainQuery(that)
results["4_join_query"] = testJoinQuery(that)
results["5_aggregate"] = testAggregate(that)
results["6_pagination"] = testPagination(that)
results["7_batch_insert"] = testInserts(that)
results["8_upsert"] = testUpsert(that)
results["9_transaction"] = testTransaction(that)
that.Display(0, results)
},
"crud": func(that *Context) { initTestTables(that); that.Display(0, testBasicCRUD(that)) },
"condition": func(that *Context) { that.Display(0, testConditionSyntax(that)) },
"chain": func(that *Context) { that.Display(0, testChainQuery(that)) },
"join": func(that *Context) { that.Display(0, testJoinQuery(that)) },
"aggregate": func(that *Context) { that.Display(0, testAggregate(that)) },
"pagination": func(that *Context) { that.Display(0, testPagination(that)) },
"batch": func(that *Context) { initTestTables(that); that.Display(0, testInserts(that)) },
"upsert": func(that *Context) { that.Display(0, testUpsert(that)) },
"transaction": func(that *Context) { initTestTables(that); that.Display(0, testTransaction(that)) },
}
func testBasicCRUD(that *Context) Map {
result := Map{"name": "基础CRUD测试", "tests": Slice{}}
tests := Slice{}
now := nowFunc(that)
insertTest := Map{"name": "Insert 插入测试 (admin表)"}
adminId := that.Db.Insert("admin", Map{
"name": "测试管理员_" + fmt.Sprintf("%d", time.Now().Unix()),
"phone": fmt.Sprintf("138%d", time.Now().Unix()%100000000),
"state": 1,
"password": "test123456",
"role_id": 1,
"title": "测试职位",
"create_time[#]": now,
"modify_time[#]": now,
})
insertTest["result"] = adminId > 0
insertTest["adminId"] = adminId
insertTest["lastQuery"] = that.Db.LastQuery
tests = append(tests, insertTest)
getTest := Map{"name": "Get 获取单条记录测试"}
admin := that.Db.Get("admin", "*", Map{"id": adminId})
getTest["result"] = admin != nil && admin.GetInt64("id") == adminId
getTest["admin"] = admin
tests = append(tests, getTest)
selectTest1 := Map{"name": "Select 单条件查询测试"}
admins1 := that.Db.Select("admin", "*", Map{"state": 1, "LIMIT": 5})
selectTest1["result"] = len(admins1) >= 0
selectTest1["count"] = len(admins1)
tests = append(tests, selectTest1)
selectTest2 := Map{"name": "Select 多条件自动AND测试"}
admins2 := that.Db.Select("admin", "*", Map{
"state": 1,
"role_id[>]": 0,
"ORDER": "id DESC",
"LIMIT": 5,
})
selectTest2["result"] = true
selectTest2["count"] = len(admins2)
selectTest2["lastQuery"] = that.Db.LastQuery
tests = append(tests, selectTest2)
updateTest := Map{"name": "Update 更新测试"}
affected := that.Db.Update("admin", Map{
"title": "更新后的职位",
"modify_time[#]": now,
}, Map{"id": adminId})
updateTest["result"] = affected > 0
updateTest["affected"] = affected
tests = append(tests, updateTest)
deleteTest := Map{"name": "Delete 删除测试"}
tempId := that.Db.Insert("test_batch", Map{
"name": "临时删除测试",
"title": "测试标题",
"state": 1,
"create_time[#]": now,
})
deleteAffected := that.Db.Delete("test_batch", Map{"id": tempId})
deleteTest["result"] = deleteAffected > 0
deleteTest["affected"] = deleteAffected
tests = append(tests, deleteTest)
result["tests"] = tests
result["success"] = true
return result
}
func testConditionSyntax(that *Context) Map {
result := Map{"name": "条件查询语法测试", "tests": Slice{}}
tests := Slice{}
test1 := Map{"name": "等于条件 (=)"}
articles1 := that.Db.Select("article", "id,title", Map{"state": 0, "LIMIT": 3})
test1["result"] = true
test1["count"] = len(articles1)
tests = append(tests, test1)
test2 := Map{"name": "不等于条件 ([!])"}
articles2 := that.Db.Select("article", "id,title,state", Map{"state[!]": -1, "LIMIT": 3})
test2["result"] = true
test2["count"] = len(articles2)
test2["lastQuery"] = that.Db.LastQuery
tests = append(tests, test2)
test3 := Map{"name": "大于小于条件 ([>], [<])"}
articles3 := that.Db.Select("article", "id,title,click_num", Map{
"click_num[>]": 0,
"click_num[<]": 100000,
"LIMIT": 3,
})
test3["result"] = true
test3["count"] = len(articles3)
tests = append(tests, test3)
test4 := Map{"name": "大于等于小于等于条件 ([>=], [<=])"}
articles4 := that.Db.Select("article", "id,title,sort", Map{
"sort[>=]": 0,
"sort[<=]": 100,
"LIMIT": 3,
})
test4["result"] = true
test4["count"] = len(articles4)
tests = append(tests, test4)
test5 := Map{"name": "LIKE 模糊查询 ([~])"}
articles5 := that.Db.Select("article", "id,title", Map{"title[~]": "新闻", "LIMIT": 3})
test5["result"] = true
test5["count"] = len(articles5)
test5["lastQuery"] = that.Db.LastQuery
tests = append(tests, test5)
test6 := Map{"name": "右模糊查询 ([~!])"}
articles6 := that.Db.Select("admin", "id,name", Map{"name[~!]": "管理", "LIMIT": 3})
test6["result"] = true
test6["count"] = len(articles6)
tests = append(tests, test6)
test7 := Map{"name": "BETWEEN 区间查询 ([<>])"}
articles7 := that.Db.Select("article", "id,title,click_num", Map{"click_num[<>]": Slice{0, 1000}, "LIMIT": 3})
test7["result"] = true
test7["count"] = len(articles7)
test7["lastQuery"] = that.Db.LastQuery
tests = append(tests, test7)
test8 := Map{"name": "NOT BETWEEN 查询 ([><])"}
articles8 := that.Db.Select("article", "id,title,sort", Map{"sort[><]": Slice{-10, 0}, "LIMIT": 3})
test8["result"] = true
test8["count"] = len(articles8)
tests = append(tests, test8)
test9 := Map{"name": "IN 查询"}
articles9 := that.Db.Select("article", "id,title", Map{"id": Slice{1, 2, 3, 4, 5}, "LIMIT": 5})
test9["result"] = true
test9["count"] = len(articles9)
test9["lastQuery"] = that.Db.LastQuery
tests = append(tests, test9)
test10 := Map{"name": "NOT IN 查询 ([!])"}
articles10 := that.Db.Select("article", "id,title", Map{"id[!]": Slice{1, 2, 3}, "LIMIT": 5})
test10["result"] = true
test10["count"] = len(articles10)
tests = append(tests, test10)
test11 := Map{"name": "IS NULL 查询"}
articles11 := that.Db.Select("article", "id,title,img", Map{"img": nil, "LIMIT": 3})
test11["result"] = true
test11["count"] = len(articles11)
tests = append(tests, test11)
test12 := Map{"name": "IS NOT NULL 查询 ([!])"}
articles12 := that.Db.Select("article", "id,title,create_time", Map{"create_time[!]": nil, "LIMIT": 3})
test12["result"] = true
test12["count"] = len(articles12)
tests = append(tests, test12)
// SQL 片段查询根据数据库类型适配
test13 := Map{"name": "直接 SQL 片段查询 ([##])"}
var sqlFragment string
switch that.Db.Type {
case "dm", "dameng":
sqlFragment = "\"create_time\" > DATEADD(DAY, -365, NOW())"
default:
sqlFragment = "create_time > DATE_SUB(NOW(), INTERVAL 365 DAY)"
}
articles13 := that.Db.Select("article", "id,title,create_time", Map{
"[##]": sqlFragment,
"LIMIT": 3,
})
test13["result"] = true
test13["count"] = len(articles13)
test13["lastQuery"] = that.Db.LastQuery
tests = append(tests, test13)
test14 := Map{"name": "显式 AND 条件"}
articles14 := that.Db.Select("article", "id,title,state,click_num", Map{
"AND": Map{
"state": 0,
"click_num[>=]": 0,
},
"LIMIT": 3,
})
test14["result"] = true
test14["count"] = len(articles14)
tests = append(tests, test14)
test15 := Map{"name": "OR 条件"}
articles15 := that.Db.Select("article", "id,title,sort,click_num", Map{
"OR": Map{
"sort": 0,
"click_num[>]": 10,
},
"LIMIT": 5,
})
test15["result"] = true
test15["count"] = len(articles15)
tests = append(tests, test15)
test16 := Map{"name": "嵌套 AND/OR 条件"}
articles16 := that.Db.Select("article", "id,title,sort,state", Map{
"AND": Map{
"state": 0,
"OR": Map{
"sort[>=]": 0,
"click_num[>]": 0,
},
},
"LIMIT": 5,
})
test16["result"] = true
test16["count"] = len(articles16)
test16["lastQuery"] = that.Db.LastQuery
tests = append(tests, test16)
result["tests"] = tests
result["success"] = true
return result
}
func testChainQuery(that *Context) Map {
result := Map{"name": "链式查询测试", "tests": Slice{}}
tests := Slice{}
test1 := Map{"name": "基本链式查询 Table().Where().Select()"}
articles1 := that.Db.Table("article").
Where("state", 0).
Select("id,title,author")
test1["result"] = len(articles1) >= 0
test1["count"] = len(articles1)
tests = append(tests, test1)
test2 := Map{"name": "链式 And 条件"}
articles2 := that.Db.Table("article").
Where("state", 0).
And("click_num[>=]", 0).
And("sort[>=]", 0).
Select("id,title,click_num")
test2["result"] = len(articles2) >= 0
test2["count"] = len(articles2)
tests = append(tests, test2)
test3 := Map{"name": "链式 Or 条件"}
articles3 := that.Db.Table("article").
Where("state", 0).
Or(Map{
"sort": 0,
"click_num[>]": 10,
}).
Select("id,title,sort,click_num")
test3["result"] = len(articles3) >= 0
test3["count"] = len(articles3)
tests = append(tests, test3)
test4 := Map{"name": "链式 Order 排序"}
articles4 := that.Db.Table("article").
Where("state", 0).
Order("create_time DESC", "id ASC").
Limit(0, 5).
Select("id,title,create_time")
test4["result"] = len(articles4) >= 0
test4["count"] = len(articles4)
tests = append(tests, test4)
test5 := Map{"name": "链式 Limit 限制"}
articles5 := that.Db.Table("article").
Where("state", 0).
Limit(0, 3).
Select("id,title")
test5["result"] = len(articles5) <= 3
test5["count"] = len(articles5)
tests = append(tests, test5)
test6 := Map{"name": "链式 Get 获取单条"}
article6 := that.Db.Table("article").
Where("state", 0).
Get("id,title,author")
test6["result"] = article6 != nil || true
test6["article"] = article6
tests = append(tests, test6)
test7 := Map{"name": "链式 Count 统计"}
count7 := that.Db.Table("article").
Where("state", 0).
Count()
test7["result"] = count7 >= 0
test7["count"] = count7
tests = append(tests, test7)
test8 := Map{"name": "链式 Page 分页"}
articles8 := that.Db.Table("article").
Where("state", 0).
Page(1, 5).
Select("id,title")
test8["result"] = len(articles8) <= 5
test8["count"] = len(articles8)
tests = append(tests, test8)
test9 := Map{"name": "链式 Group 分组"}
stats9 := that.Db.Table("article").
Where("state", 0).
Group("ctg_id").
Select("ctg_id, COUNT(*) as cnt")
test9["result"] = len(stats9) >= 0
test9["stats"] = stats9
tests = append(tests, test9)
test10 := Map{"name": "链式 Update 更新"}
testArticle := that.Db.Table("article").Where("state", 0).Get("id")
if testArticle != nil {
affected := that.Db.Table("article").
Where("id", testArticle.GetInt64("id")).
Update(Map{"modify_time[#]": nowFunc(that)})
test10["result"] = affected >= 0
test10["affected"] = affected
} else {
test10["result"] = true
test10["note"] = "无可用测试数据"
}
tests = append(tests, test10)
result["tests"] = tests
result["success"] = true
return result
}
func testJoinQuery(that *Context) Map {
result := Map{"name": "JOIN查询测试", "tests": Slice{}}
tests := Slice{}
test1 := Map{"name": "LEFT JOIN 链式查询"}
articles1 := that.Db.Table("article").
LeftJoin("ctg", "article.ctg_id = ctg.id").
Where("article.state", 0).
Limit(0, 5).
Select("article.id, article.title, ctg.name as ctg_name")
test1["result"] = len(articles1) >= 0
test1["count"] = len(articles1)
test1["data"] = articles1
tests = append(tests, test1)
test2 := Map{"name": "传统 JOIN 语法"}
articles2 := that.Db.Select("article",
Slice{
Map{"[>]ctg": "article.ctg_id = ctg.id"},
},
"article.id, article.title, ctg.name as ctg_name",
Map{
"article.state": 0,
"LIMIT": 5,
})
test2["result"] = len(articles2) >= 0
test2["count"] = len(articles2)
test2["lastQuery"] = that.Db.LastQuery
tests = append(tests, test2)
test3 := Map{"name": "多表 JOIN"}
articles3 := that.Db.Table("article").
LeftJoin("ctg", "article.ctg_id = ctg.id").
LeftJoin("admin", "article.admin_id = admin.id").
Where("article.state", 0).
Limit(0, 5).
Select("article.id, article.title, ctg.name as ctg_name, admin.name as admin_name")
test3["result"] = len(articles3) >= 0
test3["count"] = len(articles3)
test3["data"] = articles3
tests = append(tests, test3)
test4 := Map{"name": "INNER JOIN"}
articles4 := that.Db.Table("article").
InnerJoin("ctg", "article.ctg_id = ctg.id").
Where("ctg.state", 0).
Limit(0, 5).
Select("article.id, article.title, ctg.name as ctg_name")
test4["result"] = len(articles4) >= 0
test4["count"] = len(articles4)
tests = append(tests, test4)
result["tests"] = tests
result["success"] = true
return result
}
func testAggregate(that *Context) Map {
result := Map{"name": "聚合函数测试", "tests": Slice{}}
tests := Slice{}
test1 := Map{"name": "Count 总数统计"}
count1 := that.Db.Count("article")
test1["result"] = count1 >= 0
test1["count"] = count1
tests = append(tests, test1)
test2 := Map{"name": "Count 条件统计"}
count2 := that.Db.Count("article", Map{"state": 0})
test2["result"] = count2 >= 0
test2["count"] = count2
tests = append(tests, test2)
test3 := Map{"name": "Sum 求和 (单字段名)"}
sum3 := that.Db.Sum("article", "click_num", Map{"state": 0})
test3["result"] = sum3 >= 0
test3["sum"] = sum3
test3["lastQuery"] = that.Db.LastQuery
tests = append(tests, test3)
test4 := Map{"name": "Avg 平均值 (单字段名)"}
avg4 := that.Db.Avg("article", "click_num", Map{"state": 0})
test4["result"] = avg4 >= 0
test4["avg"] = avg4
test4["lastQuery"] = that.Db.LastQuery
tests = append(tests, test4)
test5 := Map{"name": "Max 最大值 (单字段名)"}
max5 := that.Db.Max("article", "click_num", Map{"state": 0})
test5["result"] = max5 >= 0
test5["max"] = max5
test5["lastQuery"] = that.Db.LastQuery
tests = append(tests, test5)
test6 := Map{"name": "Min 最小值 (单字段名)"}
min6 := that.Db.Min("article", "sort", Map{"state": 0})
test6["result"] = true
test6["min"] = min6
test6["lastQuery"] = that.Db.LastQuery
tests = append(tests, test6)
test7 := Map{"name": "GROUP BY 分组统计"}
stats7 := that.Db.Select("article",
"ctg_id, COUNT(*) as article_count, AVG(click_num) as avg_clicks, SUM(click_num) as total_clicks",
Map{
"state": 0,
"GROUP": "ctg_id",
"ORDER": "article_count DESC",
"LIMIT": 10,
})
test7["result"] = len(stats7) >= 0
test7["stats"] = stats7
tests = append(tests, test7)
test8 := Map{"name": "Sum 求和 (table.column 格式)"}
sum8 := that.Db.Sum("article", "article.click_num", Map{"state": 0})
test8["result"] = sum8 >= 0
test8["sum"] = sum8
test8["match_single_field"] = sum8 == sum3
test8["lastQuery"] = that.Db.LastQuery
tests = append(tests, test8)
test9 := Map{"name": "聚合函数一致性验证"}
avg9 := that.Db.Avg("article", "article.click_num", Map{"state": 0})
max10 := that.Db.Max("article", "article.click_num", Map{"state": 0})
min11 := that.Db.Min("article", "article.sort", Map{"state": 0})
allMatch := (sum8 == sum3) && (avg9 == avg4) && (max10 == max5) && (min11 == min6)
test9["result"] = allMatch
test9["summary"] = fmt.Sprintf("Sum: %v=%v, Avg: %v=%v, Max: %v=%v, Min: %v=%v",
sum3, sum8, avg4, avg9, max5, max10, min6, min11)
tests = append(tests, test9)
joinSlice := Slice{
Map{"[>]ctg": "article.ctg_id = ctg.id"},
}
test10 := Map{"name": "Sum 带 JOIN (table.column 格式)"}
sum10 := that.Db.Sum("article", "article.click_num", joinSlice, Map{"article.state": 0})
test10["result"] = sum10 >= 0
test10["sum"] = sum10
test10["lastQuery"] = that.Db.LastQuery
tests = append(tests, test10)
test11 := Map{"name": "Count 带 JOIN"}
count11 := that.Db.Count("article", joinSlice, Map{"article.state": 0})
test11["result"] = count11 >= 0
test11["count"] = count11
test11["lastQuery"] = that.Db.LastQuery
tests = append(tests, test11)
result["tests"] = tests
result["success"] = true
return result
}
func testPagination(that *Context) Map {
result := Map{"name": "分页查询测试", "tests": Slice{}}
tests := Slice{}
test1 := Map{"name": "PageSelect 分页查询"}
articles1 := that.Db.Page(1, 5).PageSelect("article", "*", Map{
"state": 0,
"ORDER": "id DESC",
})
test1["result"] = len(articles1) <= 5
test1["count"] = len(articles1)
tests = append(tests, test1)
test2 := Map{"name": "PageSelect 第二页"}
articles2 := that.Db.Page(2, 5).PageSelect("article", "*", Map{
"state": 0,
"ORDER": "id DESC",
})
test2["result"] = len(articles2) <= 5
test2["count"] = len(articles2)
tests = append(tests, test2)
test3 := Map{"name": "链式 Page 分页"}
articles3 := that.Db.Table("article").
Where("state", 0).
Order("id DESC").
Page(1, 3).
Select("id,title,author")
test3["result"] = len(articles3) <= 3
test3["count"] = len(articles3)
tests = append(tests, test3)
test4 := Map{"name": "Offset 偏移查询"}
articles4 := that.Db.Table("article").
Where("state", 0).
Limit(3).
Offset(2).
Select("id,title")
test4["result"] = len(articles4) <= 3
test4["count"] = len(articles4)
test4["lastQuery"] = that.Db.LastQuery
tests = append(tests, test4)
result["tests"] = tests
result["success"] = true
return result
}
func testInserts(that *Context) Map {
result := Map{"name": "批量插入测试", "tests": Slice{}}
tests := Slice{}
now := nowFunc(that)
test1 := Map{"name": "Inserts 批量插入"}
timestamp := time.Now().UnixNano()
affected1 := that.Db.Inserts("test_batch", []Map{
{"name": fmt.Sprintf("批量测试1_%d", timestamp), "title": "标题1", "state": 1},
{"name": fmt.Sprintf("批量测试2_%d", timestamp), "title": "标题2", "state": 1},
{"name": fmt.Sprintf("批量测试3_%d", timestamp), "title": "标题3", "state": 1},
})
test1["result"] = affected1 >= 0
test1["affected"] = affected1
test1["lastQuery"] = that.Db.LastQuery
tests = append(tests, test1)
test2 := Map{"name": "Inserts 带 [#] 标记"}
timestamp2 := time.Now().UnixNano()
affected2 := that.Db.Inserts("test_batch", []Map{
{"name": fmt.Sprintf("带时间测试1_%d", timestamp2), "title": "标题带时间1", "state": 1, "create_time[#]": now},
{"name": fmt.Sprintf("带时间测试2_%d", timestamp2), "title": "标题带时间2", "state": 1, "create_time[#]": now},
})
test2["result"] = affected2 >= 0
test2["affected"] = affected2
tests = append(tests, test2)
that.Db.Delete("test_batch", Map{"name[~]": fmt.Sprintf("_%d", timestamp)})
that.Db.Delete("test_batch", Map{"name[~]": fmt.Sprintf("_%d", timestamp2)})
result["tests"] = tests
result["success"] = true
return result
}
func testUpsert(that *Context) Map {
result := Map{"name": "Upsert测试", "tests": Slice{}}
tests := Slice{}
now := nowFunc(that)
timestamp := time.Now().Unix()
testPhone := fmt.Sprintf("199%08d", timestamp%100000000)
test1 := Map{"name": "Upsert 插入新记录 (admin表)"}
affected1 := that.Db.Upsert("admin",
Map{
"name": "Upsert测试管理员",
"phone": testPhone,
"state": 1,
"password": "test123",
"role_id": 1,
"title": "测试职位",
"create_time[#]": now,
"modify_time[#]": now,
},
Slice{"phone"},
Slice{"name", "state", "title", "modify_time"},
)
test1["result"] = affected1 >= 0
test1["affected"] = affected1
test1["lastQuery"] = that.Db.LastQuery
tests = append(tests, test1)
test2 := Map{"name": "Upsert 更新已存在记录"}
affected2 := that.Db.Upsert("admin",
Map{
"name": "Upsert更新后管理员",
"phone": testPhone,
"state": 1,
"password": "updated123",
"role_id": 2,
"title": "更新后职位",
"create_time[#]": now,
"modify_time[#]": now,
},
Slice{"phone"},
Slice{"name", "title", "role_id", "modify_time"},
)
test2["result"] = affected2 >= 0
test2["affected"] = affected2
tests = append(tests, test2)
updatedAdmin := that.Db.Get("admin", "*", Map{"phone": testPhone})
test2["updatedAdmin"] = updatedAdmin
that.Db.Delete("admin", Map{"phone": testPhone})
result["tests"] = tests
result["success"] = true
return result
}
func testTransaction(that *Context) Map {
result := Map{"name": "事务测试", "tests": Slice{}}
tests := Slice{}
now := nowFunc(that)
timestamp := time.Now().Unix()
testName1 := fmt.Sprintf("事务测试_%d", timestamp)
test1 := Map{"name": "事务成功提交"}
success1 := that.Db.Action(func(tx HoTimeDB) bool {
recordId := tx.Insert("test_batch", Map{
"name": testName1,
"title": "事务提交测试",
"state": 1,
"create_time[#]": now,
})
return recordId != 0
})
test1["result"] = success1
checkRecord := that.Db.Get("test_batch", "*", Map{"name": testName1})
test1["recordExists"] = checkRecord != nil
tests = append(tests, test1)
test2 := Map{"name": "事务回滚"}
testName2 := fmt.Sprintf("事务回滚测试_%d", timestamp)
success2 := that.Db.Action(func(tx HoTimeDB) bool {
_ = tx.Insert("test_batch", Map{
"name": testName2,
"title": "事务回滚测试",
"state": 1,
"create_time[#]": now,
})
return false
})
test2["result"] = !success2
checkRecord2 := that.Db.Get("test_batch", "*", Map{"name": testName2})
test2["recordRolledBack"] = checkRecord2 == nil
tests = append(tests, test2)
that.Db.Delete("test_batch", Map{"name": testName1})
result["tests"] = tests
result["success"] = true
return result
}
+18
View File
@@ -0,0 +1,18 @@
package app
import (
. "code.hoteas.com/golang/hotime"
)
var TestTest = CtrTest{
"all": {Desc: "运行全部通用测试", Func: func(a *Api) { a.Get("全部测试", 0) }},
"crud": {Desc: "基础 CRUD 测试", Func: func(a *Api) { a.Get("CRUD测试", 0) }},
"condition": {Desc: "条件查询语法测试", Func: func(a *Api) { a.Get("条件查询", 0) }},
"chain": {Desc: "链式查询测试", Func: func(a *Api) { a.Get("链式查询", 0) }},
"join": {Desc: "JOIN 查询测试", Func: func(a *Api) { a.Get("JOIN查询", 0) }},
"aggregate": {Desc: "聚合函数测试", Func: func(a *Api) { a.Get("聚合函数", 0) }},
"pagination": {Desc: "分页查询测试", Func: func(a *Api) { a.Get("分页查询", 0) }},
"batch": {Desc: "批量插入测试", Func: func(a *Api) { a.Get("批量插入", 0) }},
"upsert": {Desc: "Upsert 测试", Func: func(a *Api) { a.Get("Upsert测试", 0) }},
"transaction": {Desc: "事务测试", Func: func(a *Api) { a.Get("事务测试", 0) }},
}