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/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 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..4b23af05d 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`, gconv.String(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..0e89fbfe2 100644 --- a/database/gdb/gdb_core_stats.go +++ b/database/gdb/gdb_core_stats.go @@ -30,14 +30,14 @@ 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 { + // 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: &node, - stats: sqlDB.Stats(), + stats: v.Stats(), }) return true }) diff --git a/os/gcfg/gcfg_adapter_file.go b/os/gcfg/gcfg_adapter_file.go index b81cf6f6f..5b4f08e60 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 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. } 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 }