feat(db): 实现数据库查询中的数组参数展开和空数组处理

- 在 Get 方法中添加无参数时的默认字段和 LIMIT 1 处理
- 实现 expandArrayPlaceholder 方法,自动展开 IN (?) 和 NOT IN (?) 中的数组参数
- 为空数组的 IN 条件生成 1=0 永假条件,NOT IN 生成 1=1 永真条件
- 在 queryWithRetry 和 execWithRetry 中集成数组占位符预处理
- 修复 where.go 中空切片条件的处理逻辑
- 添加完整的 IN/NOT IN 数组查询测试用例
- 更新 .gitignore 规则格式
This commit is contained in:
2026-01-22 07:16:42 +08:00
parent 595275e747
commit 60a12a09bd
7 changed files with 756 additions and 447 deletions
+125 -45
View File
@@ -1,9 +1,7 @@
package main
import (
"encoding/json"
"fmt"
"os"
"time"
. "code.hoteas.com/golang/hotime"
@@ -11,33 +9,7 @@ import (
. "code.hoteas.com/golang/hotime/db"
)
// 调试日志文件路径
const debugLogPath = `d:\work\hotimev1\.cursor\debug.log`
// debugLog 写入调试日志
func debugLog(location, message string, data interface{}, hypothesisId string) {
// #region agent log
logEntry := Map{
"location": location,
"message": message,
"data": data,
"timestamp": time.Now().UnixMilli(),
"sessionId": "debug-session",
"runId": "hotimedb-test-run",
"hypothesisId": hypothesisId,
}
jsonBytes, _ := json.Marshal(logEntry)
f, err := os.OpenFile(debugLogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err == nil {
f.WriteString(string(jsonBytes) + "\n")
f.Close()
}
// #endregion
}
func main() {
debugLog("main.go:35", "开始 HoTimeDB 全功能测试", nil, "START")
appIns := Init("config/config.json")
appIns.SetConnectListener(func(that *Context) (isFinished bool) {
return isFinished
@@ -49,7 +21,6 @@ func main() {
// 测试入口 - 运行所有测试
"all": func(that *Context) {
results := Map{}
debugLog("main.go:48", "开始所有测试", nil, "ALL_TESTS")
// 初始化测试表
initTestTables(that)
@@ -84,16 +55,13 @@ func main() {
// 10. 原生 SQL 测试
results["10_raw_sql"] = testRawSQL(that)
debugLog("main.go:80", "所有测试完成", results, "ALL_TESTS_DONE")
that.Display(0, results)
},
// 查询数据库表结构
"tables": func(that *Context) {
debugLog("main.go:tables", "查询数据库表结构", nil, "TABLES")
// 查询所有表
tables := that.Db.Query("SHOW TABLES")
debugLog("main.go:tables", "表列表", tables, "TABLES")
that.Display(0, Map{"tables": tables})
},
@@ -108,7 +76,6 @@ func main() {
columns := that.Db.Query("DESCRIBE " + tableName)
// 查询表数据(前10条)
data := that.Db.Select(tableName, Map{"LIMIT": 10})
debugLog("main.go:describe", "表结构", Map{"table": tableName, "columns": columns, "data": data}, "DESCRIBE")
that.Display(0, Map{"table": tableName, "columns": columns, "sample_data": data})
},
@@ -144,11 +111,6 @@ func initTestTables(that *Context) {
adminCount := that.Db.Count("admin")
articleCount := that.Db.Count("article")
debugLog("main.go:init", "MySQL数据库初始化检查完成", Map{
"adminCount": adminCount,
"articleCount": articleCount,
"dbType": "MySQL",
}, "INIT")
}
// ==================== 1. 基础 CRUD 测试 ====================
@@ -156,8 +118,6 @@ func testBasicCRUD(that *Context) Map {
result := Map{"name": "基础CRUD测试", "tests": Slice{}}
tests := Slice{}
debugLog("main.go:103", "开始基础 CRUD 测试 (MySQL)", nil, "H1_CRUD")
// 1.1 Insert 测试 - 使用 admin 表
insertTest := Map{"name": "Insert 插入测试 (admin表)"}
adminId := that.Db.Insert("admin", Map{
@@ -173,7 +133,6 @@ func testBasicCRUD(that *Context) Map {
insertTest["result"] = adminId > 0
insertTest["adminId"] = adminId
insertTest["lastQuery"] = that.Db.LastQuery
debugLog("main.go:118", "Insert 测试", Map{"adminId": adminId, "success": adminId > 0, "query": that.Db.LastQuery}, "H1_INSERT")
tests = append(tests, insertTest)
// 1.2 Get 测试
@@ -181,7 +140,6 @@ func testBasicCRUD(that *Context) Map {
admin := that.Db.Get("admin", "*", Map{"id": adminId})
getTest["result"] = admin != nil && admin.GetInt64("id") == adminId
getTest["admin"] = admin
debugLog("main.go:126", "Get 测试", Map{"admin": admin, "success": admin != nil}, "H1_GET")
tests = append(tests, getTest)
// 1.3 Select 测试 - 单条件
@@ -189,7 +147,6 @@ func testBasicCRUD(that *Context) Map {
admins1 := that.Db.Select("admin", "*", Map{"state": 1, "LIMIT": 5})
selectTest1["result"] = len(admins1) >= 0 // 可能表中没有数据
selectTest1["count"] = len(admins1)
debugLog("main.go:134", "Select 单条件测试", Map{"count": len(admins1)}, "H1_SELECT1")
tests = append(tests, selectTest1)
// 1.4 Select 测试 - 多条件(自动 AND)
@@ -203,7 +160,6 @@ func testBasicCRUD(that *Context) Map {
selectTest2["result"] = true // 只要不报错就算成功
selectTest2["count"] = len(admins2)
selectTest2["lastQuery"] = that.Db.LastQuery
debugLog("main.go:149", "Select 多条件自动AND测试", Map{"count": len(admins2), "query": that.Db.LastQuery}, "H1_SELECT2")
tests = append(tests, selectTest2)
// 1.5 Update 测试
@@ -214,7 +170,6 @@ func testBasicCRUD(that *Context) Map {
}, Map{"id": adminId})
updateTest["result"] = affected > 0
updateTest["affected"] = affected
debugLog("main.go:160", "Update 测试", Map{"affected": affected}, "H1_UPDATE")
tests = append(tests, updateTest)
// 1.6 Delete 测试 - 使用 test_batch 表
@@ -956,6 +911,131 @@ func testRawSQL(that *Context) Map {
debugLog("main.go:899", "Exec 原生执行测试", test2, "H10_EXEC")
tests = append(tests, test2)
// ==================== IN/NOT IN 数组测试 ====================
// H1: IN (?) 配合非空数组能正确展开
test3 := Map{"name": "H1: IN (?) 非空数组展开"}
// #region agent log
debugLog("main.go:test3", "H1测试开始: IN非空数组", Map{"ids": []int{1, 2, 3, 4, 5}}, "H1")
// #endregion
articles3 := that.Db.Query("SELECT id, title FROM `article` WHERE id IN (?) LIMIT 10", []int{1, 2, 3, 4, 5})
// #region agent log
debugLog("main.go:test3", "H1测试结果: IN非空数组", Map{
"count": len(articles3),
"lastQuery": that.Db.LastQuery,
"data": articles3,
}, "H1")
// #endregion
test3["result"] = len(articles3) >= 0
test3["count"] = len(articles3)
test3["lastQuery"] = that.Db.LastQuery
tests = append(tests, test3)
// H2: IN (?) 配合空数组替换为 1=0
test4 := Map{"name": "H2: IN (?) 空数组替换为1=0"}
// #region agent log
debugLog("main.go:test4", "H2测试开始: IN空数组", Map{"ids": []int{}}, "H2")
// #endregion
articles4 := that.Db.Query("SELECT id, title FROM `article` WHERE id IN (?) LIMIT 10", []int{})
// #region agent log
debugLog("main.go:test4", "H2测试结果: IN空数组", Map{
"count": len(articles4),
"lastQuery": that.Db.LastQuery,
"expected": "应包含1=0,返回0条记录",
}, "H2")
// #endregion
test4["result"] = len(articles4) == 0 // 空数组的IN应该返回0条
test4["count"] = len(articles4)
test4["lastQuery"] = that.Db.LastQuery
test4["expected"] = "count=0, SQL应包含1=0"
tests = append(tests, test4)
// H3: NOT IN (?) 配合空数组替换为 1=1
test5 := Map{"name": "H3: NOT IN (?) 空数组替换为1=1"}
// #region agent log
debugLog("main.go:test5", "H3测试开始: NOT IN空数组", Map{"ids": []int{}}, "H3")
// #endregion
articles5 := that.Db.Query("SELECT id, title FROM `article` WHERE id NOT IN (?) LIMIT 10", []int{})
// #region agent log
debugLog("main.go:test5", "H3测试结果: NOT IN空数组", Map{
"count": len(articles5),
"lastQuery": that.Db.LastQuery,
"expected": "应包含1=1,返回所有记录(限制10条)",
}, "H3")
// #endregion
test5["result"] = len(articles5) > 0 // NOT IN空数组应该返回记录
test5["count"] = len(articles5)
test5["lastQuery"] = that.Db.LastQuery
test5["expected"] = "count>0, SQL应包含1=1"
tests = append(tests, test5)
// H4: NOT IN (?) 配合非空数组正常展开
test6 := Map{"name": "H4: NOT IN (?) 非空数组展开"}
// #region agent log
debugLog("main.go:test6", "H4测试开始: NOT IN非空数组", Map{"ids": []int{1, 2, 3}}, "H4")
// #endregion
articles6 := that.Db.Query("SELECT id, title FROM `article` WHERE id NOT IN (?) LIMIT 10", []int{1, 2, 3})
// #region agent log
debugLog("main.go:test6", "H4测试结果: NOT IN非空数组", Map{
"count": len(articles6),
"lastQuery": that.Db.LastQuery,
}, "H4")
// #endregion
test6["result"] = len(articles6) >= 0
test6["count"] = len(articles6)
test6["lastQuery"] = that.Db.LastQuery
tests = append(tests, test6)
// H5: 普通 ? 占位符保持原有行为
test7 := Map{"name": "H5: 普通?占位符不受影响"}
// #region agent log
debugLog("main.go:test7", "H5测试开始: 普通占位符", Map{"state": 0, "limit": 5}, "H5")
// #endregion
articles7 := that.Db.Query("SELECT id, title FROM `article` WHERE state = ? LIMIT ?", 0, 5)
// #region agent log
debugLog("main.go:test7", "H5测试结果: 普通占位符", Map{
"count": len(articles7),
"lastQuery": that.Db.LastQuery,
}, "H5")
// #endregion
test7["result"] = len(articles7) >= 0
test7["count"] = len(articles7)
test7["lastQuery"] = that.Db.LastQuery
tests = append(tests, test7)
// 额外测试: Select ORM方法的空数组处理
test8 := Map{"name": "ORM Select: IN空数组"}
// #region agent log
debugLog("main.go:test8", "ORM测试开始: Select IN空数组", nil, "H2_ORM")
// #endregion
articles8 := that.Db.Select("article", "id,title", Map{"id": []int{}, "LIMIT": 10})
// #region agent log
debugLog("main.go:test8", "ORM测试结果: Select IN空数组", Map{
"count": len(articles8),
"lastQuery": that.Db.LastQuery,
}, "H2_ORM")
// #endregion
test8["result"] = len(articles8) == 0
test8["count"] = len(articles8)
test8["lastQuery"] = that.Db.LastQuery
tests = append(tests, test8)
// 额外测试: Select ORM方法的NOT IN空数组处理
test9 := Map{"name": "ORM Select: NOT IN空数组"}
// #region agent log
debugLog("main.go:test9", "ORM测试开始: Select NOT IN空数组", nil, "H3_ORM")
// #endregion
articles9 := that.Db.Select("article", "id,title", Map{"id[!]": []int{}, "LIMIT": 10})
// #region agent log
debugLog("main.go:test9", "ORM测试结果: Select NOT IN空数组", Map{
"count": len(articles9),
"lastQuery": that.Db.LastQuery,
}, "H3_ORM")
// #endregion
test9["result"] = len(articles9) > 0 // NOT IN 空数组应返回记录
test9["count"] = len(articles9)
test9["lastQuery"] = that.Db.LastQuery
tests = append(tests, test9)
result["tests"] = tests
result["success"] = true
return result