From 465324551a6cf6aa81bb112bd23173428d2f2519 Mon Sep 17 00:00:00 2001 From: shanyujie <1196661499@qq.com> Date: Mon, 19 Jan 2026 17:38:49 +0800 Subject: [PATCH 1/5] =?UTF-8?q?refactor(database):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E8=BF=9E=E6=8E=A5=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改 RegisterNilChecker 方法返回实例以支持链式调用 - 更新 Core 结构体中 links 字段类型为类型安全的 KVMap - 添加专门的链接检查器函数用于连接池管理 - 使用泛型 KVMap 替代原始 map 类型提升类型安全性 - 简化连接关闭逻辑并移除不必要的类型断言 - 优化统计功能中的迭代器实现提高性能 --- container/gmap/gmap_hash_k_v_map.go | 3 ++- container/gmap/gmap_list_k_v_map.go | 3 ++- container/gset/gset_t_set.go | 3 ++- container/gtree/gtree_k_v_avltree.go | 3 ++- container/gtree/gtree_k_v_btree.go | 3 ++- container/gtree/gtree_k_v_redblacktree.go | 3 ++- database/gdb/gdb.go | 33 ++++++++++++----------- database/gdb/gdb_core.go | 20 +++++++------- database/gdb/gdb_core_stats.go | 10 +++---- 9 files changed, 42 insertions(+), 39 deletions(-) diff --git a/container/gmap/gmap_hash_k_v_map.go b/container/gmap/gmap_hash_k_v_map.go index 6d65d59e9..7ad5ac924 100644 --- a/container/gmap/gmap_hash_k_v_map.go +++ b/container/gmap/gmap_hash_k_v_map.go @@ -66,10 +66,11 @@ func NewKVMapWithCheckerFrom[K comparable, V any](data map[K]V, checker NilCheck // This function is used to determine if a value should be considered as nil. // The nil checker function takes a value of type V and returns a boolean indicating // whether the value should be treated as nil. -func (m *KVMap[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) { +func (m *KVMap[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) *KVMap[K, V] { m.mu.Lock() defer m.mu.Unlock() m.nilChecker = nilChecker + return m } // isNil checks whether the given value is nil. diff --git a/container/gmap/gmap_list_k_v_map.go b/container/gmap/gmap_list_k_v_map.go index 6b37ed57c..eb273828a 100644 --- a/container/gmap/gmap_list_k_v_map.go +++ b/container/gmap/gmap_list_k_v_map.go @@ -85,10 +85,11 @@ func NewListKVMapWithCheckerFrom[K comparable, V any](data map[K]V, nilChecker N // This function is used to determine if a value should be considered as nil. // The nil checker function takes a value of type V and returns a boolean indicating // whether the value should be treated as nil. -func (m *ListKVMap[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) { +func (m *ListKVMap[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) *ListKVMap[K, V] { m.mu.Lock() defer m.mu.Unlock() m.nilChecker = nilChecker + return m } // isNil checks whether the given value is nil. diff --git a/container/gset/gset_t_set.go b/container/gset/gset_t_set.go index 02ffbf6cd..1537f61b9 100644 --- a/container/gset/gset_t_set.go +++ b/container/gset/gset_t_set.go @@ -70,10 +70,11 @@ func NewTSetWithCheckerFrom[T comparable](items []T, checker NilChecker[T], safe // This function is used to determine if an element should be considered as nil. // The nil checker function takes an element of type T and returns a boolean indicating // whether the element should be treated as nil. -func (set *TSet[T]) RegisterNilChecker(nilChecker NilChecker[T]) { +func (set *TSet[T]) RegisterNilChecker(nilChecker NilChecker[T]) *TSet[T] { set.mu.Lock() defer set.mu.Unlock() set.nilChecker = nilChecker + return set } // isNil checks whether the given value is nil. diff --git a/container/gtree/gtree_k_v_avltree.go b/container/gtree/gtree_k_v_avltree.go index 661364751..88a266fa1 100644 --- a/container/gtree/gtree_k_v_avltree.go +++ b/container/gtree/gtree_k_v_avltree.go @@ -82,10 +82,11 @@ func NewAVLKVTreeWithCheckerFrom[K comparable, V any](comparator func(v1, v2 K) // This function is used to determine if a value should be considered as nil. // The nil checker function takes a value of type V and returns a boolean indicating // whether the value should be treated as nil. -func (tree *AVLKVTree[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) { +func (tree *AVLKVTree[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) *AVLKVTree[K, V] { tree.mu.Lock() defer tree.mu.Unlock() tree.nilChecker = nilChecker + return tree } // isNil checks whether the given value is nil. diff --git a/container/gtree/gtree_k_v_btree.go b/container/gtree/gtree_k_v_btree.go index 2adcede1b..eda3a1278 100644 --- a/container/gtree/gtree_k_v_btree.go +++ b/container/gtree/gtree_k_v_btree.go @@ -81,10 +81,11 @@ func NewBKVTreeWithCheckerFrom[K comparable, V any](m int, comparator func(v1, v // This function is used to determine if a value should be considered as nil. // The nil checker function takes a value of type V and returns a boolean indicating // whether the value should be treated as nil. -func (tree *BKVTree[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) { +func (tree *BKVTree[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) *BKVTree[K, V] { tree.mu.Lock() defer tree.mu.Unlock() tree.nilChecker = nilChecker + return tree } // isNil checks whether the given value is nil. diff --git a/container/gtree/gtree_k_v_redblacktree.go b/container/gtree/gtree_k_v_redblacktree.go index 47585716a..b5b94af5b 100644 --- a/container/gtree/gtree_k_v_redblacktree.go +++ b/container/gtree/gtree_k_v_redblacktree.go @@ -100,10 +100,11 @@ func RedBlackKVTreeInitFrom[K comparable, V any](tree *RedBlackKVTree[K, V], com // This function is used to determine if a value should be considered as nil. // The nil checker function takes a value of type V and returns a boolean indicating // whether the value should be treated as nil. -func (tree *RedBlackKVTree[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) { +func (tree *RedBlackKVTree[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) *RedBlackKVTree[K, V] { tree.mu.Lock() defer tree.mu.Unlock() tree.nilChecker = nilChecker + return tree } // isNil checks whether the given value is nil. diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index 9c3bd7f81..cc99d8e5a 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -513,18 +513,18 @@ type StatsItem interface { // Core is the base struct for database management. type Core struct { - db DB // DB interface object. - ctx context.Context // Context for chaining operation only. Do not set a default value in Core initialization. - group string // Configuration group name. - schema string // Custom schema for this object. - debug *gtype.Bool // Enable debug mode for the database, which can be changed in runtime. - cache *gcache.Cache // Cache manager, SQL result cache only. - links *gmap.Map // links caches all created links by node. - logger glog.ILogger // Logger for logging functionality. - config *ConfigNode // Current config node. - localTypeMap *gmap.StrAnyMap // Local type map for database field type conversion. - dynamicConfig dynamicConfig // Dynamic configurations, which can be changed in runtime. - innerMemCache *gcache.Cache // Internal memory cache for storing temporary data. + db DB // DB interface object. + ctx context.Context // Context for chaining operation only. Do not set a default value in Core initialization. + group string // Configuration group name. + schema string // Custom schema for this object. + debug *gtype.Bool // Enable debug mode for the database, which can be changed in runtime. + cache *gcache.Cache // Cache manager, SQL result cache only. + links *gmap.KVMap[ConfigNode, *sql.DB] // links caches all created links by node. + logger glog.ILogger // Logger for logging functionality. + config *ConfigNode // Current config node. + localTypeMap *gmap.StrAnyMap // Local type map for database field type conversion. + dynamicConfig dynamicConfig // Dynamic configurations, which can be changed in runtime. + innerMemCache *gcache.Cache // Internal memory cache for storing temporary data. } type dynamicConfig struct { @@ -944,6 +944,9 @@ func NewByGroup(group ...string) (db DB, err error) { ) } +// linksChecker is the checker function for links map. +var linksChecker = func(v *sql.DB) bool { return v == nil } + // newDBByConfigNode creates and returns an ORM object with given configuration node and group name. // // Very Note: @@ -960,7 +963,7 @@ func newDBByConfigNode(node *ConfigNode, group string) (db DB, err error) { group: group, debug: gtype.NewBool(), cache: gcache.New(), - links: gmap.New(true), + links: gmap.NewKVMapWithChecker[ConfigNode, *sql.DB](linksChecker, true), logger: glog.New(), config: node, localTypeMap: gmap.NewStrAnyMap(true), @@ -1127,7 +1130,7 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error // Cache the underlying connection pool object by node. var ( - instanceCacheFunc = func() any { + instanceCacheFunc = func() *sql.DB { if sqlDb, err = c.db.Open(node); err != nil { return nil } @@ -1159,7 +1162,7 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error ) if instanceValue != nil && sqlDb == nil { // It reads from instance map. - sqlDb = instanceValue.(*sql.DB) + sqlDb = instanceValue } if node.Debug { c.db.SetDebug(node.Debug) diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 4bb712c02..6cca52c7e 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -113,19 +113,17 @@ func (c *Core) Close(ctx context.Context) (err error) { if err = c.cache.Close(ctx); err != nil { return err } - c.links.LockFunc(func(m map[any]any) { + c.links.LockFunc(func(m map[ConfigNode]*sql.DB) { for k, v := range m { - if db, ok := v.(*sql.DB); ok { - err = db.Close() - if err != nil { - err = gerror.WrapCode(gcode.CodeDbOperationError, err, `db.Close failed`) - } - intlog.Printf(ctx, `close link: %s, err: %v`, k, err) - if err != nil { - return - } - delete(m, k) + err = v.Close() + if err != nil { + err = gerror.WrapCode(gcode.CodeDbOperationError, err, `db.Close failed`) } + intlog.Printf(ctx, `close link: %s, err: %v`, k, err) + if err != nil { + return + } + delete(m, k) } }) return diff --git a/database/gdb/gdb_core_stats.go b/database/gdb/gdb_core_stats.go index 8b64f14e8..de7febdbd 100644 --- a/database/gdb/gdb_core_stats.go +++ b/database/gdb/gdb_core_stats.go @@ -30,14 +30,10 @@ func (item *localStatsItem) Stats() sql.DBStats { // Stats retrieves and returns the pool stat for all nodes that have been established. func (c *Core) Stats(ctx context.Context) []StatsItem { var items = make([]StatsItem, 0) - c.links.Iterator(func(k, v any) bool { - var ( - node = k.(ConfigNode) - sqlDB = v.(*sql.DB) - ) + c.links.Iterator(func(k ConfigNode, v *sql.DB) bool { items = append(items, &localStatsItem{ - node: &node, - stats: sqlDB.Stats(), + node: &k, + stats: v.Stats(), }) return true }) From 95a20ea1e439dc3c3d7757d357833d2e8d22e21e Mon Sep 17 00:00:00 2001 From: shanyujie <1196661499@qq.com> Date: Mon, 19 Jan 2026 17:44:07 +0800 Subject: [PATCH 2/5] =?UTF-8?q?refactor(config):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E9=80=82=E9=85=8D=E5=99=A8?= =?UTF-8?q?=E7=9A=84=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84=E5=92=8C=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=AE=89=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 jsonMap 从 StrAnyMap 类型更改为泛型 KVMap[string, *gjson.Json] 类型 - 添加 jsonMapChecker 函数用于 JSON 对象验证 - 使用 NewKVMapWithChecker 替代 NewStrAnyMap 提高类型安全性 - 修改 GetOrSetFuncLock 回调函数返回类型为 *gjson.Json - 简化数据库链接关闭日志中的键值转换逻辑 --- database/gdb/gdb_core.go | 2 +- os/gcfg/gcfg_adapter_file.go | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 6cca52c7e..4b23af05d 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -119,7 +119,7 @@ func (c *Core) Close(ctx context.Context) (err error) { if err != nil { err = gerror.WrapCode(gcode.CodeDbOperationError, err, `db.Close failed`) } - intlog.Printf(ctx, `close link: %s, err: %v`, k, err) + intlog.Printf(ctx, `close link: %s, err: %v`, gconv.String(k), err) if err != nil { return } diff --git a/os/gcfg/gcfg_adapter_file.go b/os/gcfg/gcfg_adapter_file.go index 459a40967..cb3c4837f 100644 --- a/os/gcfg/gcfg_adapter_file.go +++ b/os/gcfg/gcfg_adapter_file.go @@ -32,11 +32,11 @@ var ( // AdapterFile implements interface Adapter using file. type AdapterFile struct { - defaultFileNameOrPath *gtype.String // Default configuration file name or file path. - searchPaths *garray.StrArray // Searching the path array. - jsonMap *gmap.StrAnyMap // The pared JSON objects for configuration files. - violenceCheck bool // Whether it does violence check in value index searching. It affects the performance when set true(false in default). - watchers *WatcherRegistry // Watchers for watching file changes. + defaultFileNameOrPath *gtype.String // Default configuration file name or file path. + searchPaths *garray.StrArray // Searching the path array. + jsonMap *gmap.KVMap[string, *gjson.Json] // The pared JSON objects for configuration files. + violenceCheck bool // Whether it does violence check in value index searching. It affects the performance when set true(false in default). + watchers *WatcherRegistry // Watchers for watching file changes. } const ( @@ -58,6 +58,9 @@ var ( // Prefix array for trying searching in the local system. localSystemTryFolders = []string{"", "config/", "manifest/config"} + + // jsonMapChecker is the checker for JSON map. + jsonMapChecker = func(v *gjson.Json) bool { return v == nil } ) // NewAdapterFile returns a new configuration management object. @@ -78,7 +81,7 @@ func NewAdapterFile(fileNameOrPath ...string) (*AdapterFile, error) { config := &AdapterFile{ defaultFileNameOrPath: gtype.NewString(usedFileNameOrPath), searchPaths: garray.NewStrArray(true), - jsonMap: gmap.NewStrAnyMap(true), + jsonMap: gmap.NewKVMapWithChecker[string, *gjson.Json](jsonMapChecker, true), watchers: NewWatcherRegistry(), } // Customized dir path from env/cmd. @@ -257,7 +260,7 @@ func (a *AdapterFile) getJson(fileNameOrPath ...string) (configJson *gjson.Json, usedFileNameOrPath = fileNameOrPath[0] } // It uses JSON map to cache specified configuration file content. - result := a.jsonMap.GetOrSetFuncLock(usedFileNameOrPath, func() any { + result := a.jsonMap.GetOrSetFuncLock(usedFileNameOrPath, func() *gjson.Json { var ( content string filePath string @@ -326,7 +329,7 @@ func (a *AdapterFile) getJson(fileNameOrPath ...string) (configJson *gjson.Json, return configJson }) if result != nil { - return result.(*gjson.Json), err + return result, err } return } From 3b9f2b893eb742b3ea21e3bdf105ca693dda8c1c Mon Sep 17 00:00:00 2001 From: shanyujie <1196661499@qq.com> Date: Mon, 19 Jan 2026 17:57:19 +0800 Subject: [PATCH 3/5] =?UTF-8?q?refactor(drivers):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=8F=92=E5=85=A5=E6=93=8D=E4=BD=9C=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E9=9B=86=E5=90=88=E7=B1=BB=E5=9E=8B=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 dm 驱动中的 conflictKeySet 从 gset.New 修改为 gset.NewStrSet - 将 gaussdb 驱动中的 conflictKeySet 从 gset.New 修改为 gset.NewStrSet - 将 mssql 驱动中的 conflictKeySet 从 gset.New 修改为 gset.NewStrSet - 将 oracle 驱动中的 conflictKeySet 从 gset.New 修改为 gset.NewStrSet - 统一使用字符串集合类型以提高类型安全性 --- contrib/drivers/dm/dm_do_insert.go | 2 +- contrib/drivers/gaussdb/gaussdb_do_insert.go | 2 +- contrib/drivers/mssql/mssql_do_insert.go | 2 +- contrib/drivers/oracle/oracle_do_insert.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/drivers/dm/dm_do_insert.go b/contrib/drivers/dm/dm_do_insert.go index 72fc540b4..7f0aef7b4 100644 --- a/contrib/drivers/dm/dm_do_insert.go +++ b/contrib/drivers/dm/dm_do_insert.go @@ -108,7 +108,7 @@ func (d *Driver) doMergeInsert( one = list[0] oneLen = len(one) charL, charR = d.GetChars() - conflictKeySet = gset.New(false) + conflictKeySet = gset.NewStrSet(false) // queryHolders: Handle data with Holder that need to be merged // queryValues: Handle data that need to be merged diff --git a/contrib/drivers/gaussdb/gaussdb_do_insert.go b/contrib/drivers/gaussdb/gaussdb_do_insert.go index 16193239c..753e1ce71 100644 --- a/contrib/drivers/gaussdb/gaussdb_do_insert.go +++ b/contrib/drivers/gaussdb/gaussdb_do_insert.go @@ -307,7 +307,7 @@ func (d *Driver) doMergeInsert( one = list[0] oneLen = len(one) charL, charR = d.GetChars() - conflictKeySet = gset.New(false) + conflictKeySet = gset.NewStrSet(false) // queryHolders: Handle data with Holder that need to be merged // queryValues: Handle data that need to be merged diff --git a/contrib/drivers/mssql/mssql_do_insert.go b/contrib/drivers/mssql/mssql_do_insert.go index 93bc17cfa..fbdcba5d3 100644 --- a/contrib/drivers/mssql/mssql_do_insert.go +++ b/contrib/drivers/mssql/mssql_do_insert.go @@ -102,7 +102,7 @@ func (d *Driver) doMergeInsert( one = list[0] oneLen = len(one) charL, charR = d.GetChars() - conflictKeySet = gset.New(false) + conflictKeySet = gset.NewStrSet(false) // queryHolders: Handle data with Holder that need to be merged // queryValues: Handle data that need to be merged diff --git a/contrib/drivers/oracle/oracle_do_insert.go b/contrib/drivers/oracle/oracle_do_insert.go index 82f8373d5..ec0273f93 100644 --- a/contrib/drivers/oracle/oracle_do_insert.go +++ b/contrib/drivers/oracle/oracle_do_insert.go @@ -181,7 +181,7 @@ func (d *Driver) doMergeInsert( one = list[0] oneLen = len(one) charL, charR = d.GetChars() - conflictKeySet = gset.New(false) + conflictKeySet = gset.NewStrSet(false) // queryHolders: Handle data with Holder that need to be upsert // queryValues: Handle data that need to be upsert From d734f6ddd96ac821ac028264c7202e1f371fb5eb Mon Sep 17 00:00:00 2001 From: Lance Add <1196661499@qq.com> Date: Thu, 22 Jan 2026 14:45:48 +0800 Subject: [PATCH 4/5] Update os/gcfg/gcfg_adapter_file.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- os/gcfg/gcfg_adapter_file.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/os/gcfg/gcfg_adapter_file.go b/os/gcfg/gcfg_adapter_file.go index cb3c4837f..99bdc5874 100644 --- a/os/gcfg/gcfg_adapter_file.go +++ b/os/gcfg/gcfg_adapter_file.go @@ -34,7 +34,7 @@ var ( type AdapterFile struct { defaultFileNameOrPath *gtype.String // Default configuration file name or file path. searchPaths *garray.StrArray // Searching the path array. - jsonMap *gmap.KVMap[string, *gjson.Json] // The pared JSON objects for configuration files. + jsonMap *gmap.KVMap[string, *gjson.Json] // The parsed JSON objects for configuration files. violenceCheck bool // Whether it does violence check in value index searching. It affects the performance when set true(false in default). watchers *WatcherRegistry // Watchers for watching file changes. } From fb0e66a85c0623536e3161b0200049aba87fc5c2 Mon Sep 17 00:00:00 2001 From: shanyujie <1196661499@qq.com> Date: Thu, 22 Jan 2026 14:51:30 +0800 Subject: [PATCH 5/5] =?UTF-8?q?refactor(database):=20=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E5=BE=AA=E7=8E=AF=E5=8F=98=E9=87=8F=E5=9C=B0=E5=9D=80=E9=87=8D?= =?UTF-8?q?=E7=94=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database/gdb/gdb_core_stats.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/database/gdb/gdb_core_stats.go b/database/gdb/gdb_core_stats.go index de7febdbd..0e89fbfe2 100644 --- a/database/gdb/gdb_core_stats.go +++ b/database/gdb/gdb_core_stats.go @@ -31,8 +31,12 @@ func (item *localStatsItem) Stats() sql.DBStats { func (c *Core) Stats(ctx context.Context) []StatsItem { var items = make([]StatsItem, 0) c.links.Iterator(func(k ConfigNode, v *sql.DB) bool { + // Create a local copy of k to avoid loop variable address re-use issue + // In Go, loop variables are re-used with the same memory address across iterations, + // directly using &k would cause all localStatsItem instances to share the same address + node := k items = append(items, &localStatsItem{ - node: &k, + node: &node, stats: v.Stats(), }) return true