diff --git a/container/garray/garray_normal_t.go b/container/garray/garray_normal_t.go index fa0ce054013..98a8ec7b81d 100644 --- a/container/garray/garray_normal_t.go +++ b/container/garray/garray_normal_t.go @@ -749,7 +749,9 @@ func (a *TArray[T]) String() string { } // MarshalJSON implements the interface MarshalJSON for json.Marshal. -// Note that do not use pointer as its receiver here. +// DO NOT change this receiver to pointer type, as the TArray can be used as a var defined variable, like: +// var a TArray[int] +// Please refer to corresponding tests for more details. func (a TArray[T]) MarshalJSON() ([]byte, error) { a.mu.RLock() defer a.mu.RUnlock() diff --git a/container/garray/garray_sorted_t.go b/container/garray/garray_sorted_t.go index 25d62f2ff0e..981785f9b5a 100644 --- a/container/garray/garray_sorted_t.go +++ b/container/garray/garray_sorted_t.go @@ -46,7 +46,7 @@ func NewSortedTArray[T comparable](comparator func(a, b T) int, safe ...bool) *S return NewSortedTArraySize(0, comparator, safe...) } -// NewSortedTArraySize create and returns an sorted array with given size and cap. +// NewSortedTArraySize create and returns a sorted array with given size and cap. // The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewSortedTArraySize[T comparable](cap int, comparator func(a, b T) int, safe ...bool) *SortedTArray[T] { @@ -718,7 +718,9 @@ func (a *SortedTArray[T]) String() string { } // MarshalJSON implements the interface MarshalJSON for json.Marshal. -// Note that do not use pointer as its receiver here. +// DO NOT change this receiver to pointer type, as the TArray can be used as a var defined variable, like: +// var a SortedTArray[int] +// Please refer to corresponding tests for more details. func (a SortedTArray[T]) MarshalJSON() ([]byte, error) { a.mu.RLock() defer a.mu.RUnlock() diff --git a/container/gmap/gmap_hash_k_v_map.go b/container/gmap/gmap_hash_k_v_map.go index 6d65d59e9ee..c8f6372e09e 100644 --- a/container/gmap/gmap_hash_k_v_map.go +++ b/container/gmap/gmap_hash_k_v_map.go @@ -22,8 +22,11 @@ type NilChecker[V any] func(V) bool // KVMap wraps map type `map[K]V` and provides more map features. type KVMap[K comparable, V any] struct { - mu rwmutex.RWMutex - data map[K]V + mu rwmutex.RWMutex + data map[K]V + + // nilChecker is the custom nil checker function. + // It uses empty.IsNil if it's nil. nilChecker NilChecker[V] } @@ -58,15 +61,15 @@ func NewKVMapFrom[K comparable, V any](data map[K]V, safe ...bool) *KVMap[K, V] // The parameter `safe` is used to specify whether to use the map in concurrent-safety mode, which is false by default. func NewKVMapWithCheckerFrom[K comparable, V any](data map[K]V, checker NilChecker[V], safe ...bool) *KVMap[K, V] { m := NewKVMapFrom[K, V](data, safe...) - m.RegisterNilChecker(checker) + m.SetNilChecker(checker) return m } -// RegisterNilChecker registers a custom nil checker function for the map values. +// SetNilChecker registers a custom nil checker function for the map values. // 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]) SetNilChecker(nilChecker NilChecker[V]) { m.mu.Lock() defer m.mu.Unlock() m.nilChecker = nilChecker @@ -74,12 +77,12 @@ func (m *KVMap[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) { // isNil checks whether the given value is nil. // It first checks if a custom nil checker function is registered and uses it if available, -// otherwise it performs a standard nil check using any(v) == nil. +// otherwise it falls back to the default empty.IsNil function. func (m *KVMap[K, V]) isNil(v V) bool { if m.nilChecker != nil { return m.nilChecker(v) } - return any(v) == nil + return empty.IsNil(v) } // Iterator iterates the hash map readonly with custom callback function `f`. @@ -242,11 +245,12 @@ func (m *KVMap[K, V]) Pops(size int) map[K]V { return newMap } -// doSetWithLockCheck checks whether value of the key exists with mutex.Lock, -// if not exists, set value to the map with given `key`, -// or else just return the existing value. +// doSetWithLockCheck sets value with given `value` if it does not exist, +// and then returns this value and whether it exists. +// +// It is a helper function for GetOrSet* functions. // -// It returns value with given `key`. +// Note that, it does not add the value to the map if the given `value` is nil. func (m *KVMap[K, V]) doSetWithLockCheck(key K, value V) (val V, ok bool) { m.mu.Lock() defer m.mu.Unlock() @@ -274,6 +278,8 @@ func (m *KVMap[K, V]) GetOrSet(key K, value V) V { // GetOrSetFunc returns the value by key, // or sets value with returned value of callback function `f` if it does not exist // and then returns this value. +// +// Note that, it does not add the value to the map if the returned value of `f` is nil. func (m *KVMap[K, V]) GetOrSetFunc(key K, f func() V) V { v, _ := m.doSetWithLockCheck(key, f()) return v @@ -285,6 +291,8 @@ func (m *KVMap[K, V]) GetOrSetFunc(key K, f func() V) V { // // GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f` // with mutex.Lock of the hash map. +// +// Note that, it does not add the value to the map if the returned value of `f` is nil. func (m *KVMap[K, V]) GetOrSetFuncLock(key K, f func() V) V { m.mu.Lock() defer m.mu.Unlock() @@ -524,6 +532,9 @@ func (m *KVMap[K, V]) String() string { } // MarshalJSON implements the interface MarshalJSON for json.Marshal. +// DO NOT change this receiver to pointer type, as the KVMap can be used as a var defined variable, like: +// var m gmap.KVMap[int, string] +// Please refer to corresponding tests for more details. func (m KVMap[K, V]) MarshalJSON() ([]byte, error) { return json.Marshal(gconv.Map(m.Map())) } diff --git a/container/gmap/gmap_list_k_v_map.go b/container/gmap/gmap_list_k_v_map.go index 6b37ed57c31..61d99f0b909 100644 --- a/container/gmap/gmap_list_k_v_map.go +++ b/container/gmap/gmap_list_k_v_map.go @@ -56,7 +56,7 @@ func NewListKVMap[K comparable, V any](safe ...bool) *ListKVMap[K, V] { // which is false by default. func NewListKVMapWithChecker[K comparable, V any](checker NilChecker[V], safe ...bool) *ListKVMap[K, V] { m := NewListKVMap[K, V](safe...) - m.RegisterNilChecker(checker) + m.SetNilChecker(checker) return m } @@ -81,11 +81,11 @@ func NewListKVMapWithCheckerFrom[K comparable, V any](data map[K]V, nilChecker N return m } -// RegisterNilChecker registers a custom nil checker function for the map values. +// SetNilChecker registers a custom nil checker function for the map values. // 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]) SetNilChecker(nilChecker NilChecker[V]) { m.mu.Lock() defer m.mu.Unlock() m.nilChecker = nilChecker @@ -93,12 +93,12 @@ func (m *ListKVMap[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) { // isNil checks whether the given value is nil. // It first checks if a custom nil checker function is registered and uses it if available, -// otherwise it performs a standard nil check using any(v) == nil. +// otherwise it falls back to the default empty.IsNil function. func (m *ListKVMap[K, V]) isNil(v V) bool { if m.nilChecker != nil { return m.nilChecker(v) } - return any(v) == nil + return empty.IsNil(v) } // Iterator is alias of IteratorAsc. @@ -402,6 +402,8 @@ func (m *ListKVMap[K, V]) GetVarOrSetFuncLock(key K, f func() V) *gvar.Var { // SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true. // It returns false if `key` exists, and `value` would be ignored. +// +// Note that it does not add the value to the map if `value` is nil. func (m *ListKVMap[K, V]) SetIfNotExist(key K, value V) bool { m.mu.Lock() defer m.mu.Unlock() @@ -421,6 +423,8 @@ func (m *ListKVMap[K, V]) SetIfNotExist(key K, value V) bool { // SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true. // It returns false if `key` exists, and `value` would be ignored. +// +// Note that, it does not add the value to the map if the returned value of `f` is nil. func (m *ListKVMap[K, V]) SetIfNotExistFunc(key K, f func() V) bool { m.mu.Lock() defer m.mu.Unlock() @@ -444,6 +448,8 @@ func (m *ListKVMap[K, V]) SetIfNotExistFunc(key K, f func() V) bool { // // SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that // it executes function `f` with mutex.Lock of the map. +// +// Note that, it does not add the value to the map if the returned value of `f` is nil. func (m *ListKVMap[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool { m.mu.Lock() defer m.mu.Unlock() @@ -609,6 +615,9 @@ func (m *ListKVMap[K, V]) String() string { } // MarshalJSON implements the interface MarshalJSON for json.Marshal. +// DO NOT change this receiver to pointer type, as the ListKVMap can be used as a var defined variable, like: +// var m gmap.ListKVMap[string]string +// Please refer to corresponding tests for more details. func (m ListKVMap[K, V]) MarshalJSON() (jsonBytes []byte, err error) { if m.data == nil { return []byte("{}"), nil diff --git a/container/gmap/gmap_z_unit_k_v_map_test.go b/container/gmap/gmap_z_unit_k_v_map_test.go index 4bd9779596f..8d95f4fa8f4 100644 --- a/container/gmap/gmap_z_unit_k_v_map_test.go +++ b/container/gmap/gmap_z_unit_k_v_map_test.go @@ -774,6 +774,13 @@ func Test_KVMap_MarshalJSON(t *testing.T) { t.Assert(data["a"], 1) t.Assert(data["b"], 2) }) + gtest.C(t, func(t *gtest.T) { + var m gmap.KVMap[int, int] + m.Set(1, 10) + b, err := json.Marshal(m) + t.AssertNil(err) + t.Assert(string(b), `{"1":10}`) + }) } func Test_KVMap_UnmarshalJSON(t *testing.T) { @@ -1647,9 +1654,10 @@ func Test_KVMap_TypedNil(t *testing.T) { return nil }) } - t.Assert(m1.Size(), 10) + t.Assert(m1.Size(), 5) + m2 := gmap.NewKVMap[int, *Student](true) - m2.RegisterNilChecker(func(student *Student) bool { + m2.SetNilChecker(func(student *Student) bool { return student == nil }) for i := 0; i < 10; i++ { @@ -1679,7 +1687,8 @@ func Test_NewKVMapWithChecker_TypedNil(t *testing.T) { return nil }) } - t.Assert(m1.Size(), 10) + t.Assert(m1.Size(), 5) + m2 := gmap.NewKVMapWithChecker[int, *Student](func(student *Student) bool { return student == nil }, true) diff --git a/container/gmap/gmap_z_unit_list_k_v_map_race_test.go b/container/gmap/gmap_z_unit_list_k_v_map_race_test.go index d3156866201..5c5e869f159 100644 --- a/container/gmap/gmap_z_unit_list_k_v_map_race_test.go +++ b/container/gmap/gmap_z_unit_list_k_v_map_race_test.go @@ -183,77 +183,6 @@ func Test_ListKVMap_SetIfNotExistFuncLock_MultipleKeys(t *testing.T) { }) } -// Test_ListKVMap_GetOrSetFuncLock_NilValue tests that nil values are handled correctly. -func Test_ListKVMap_GetOrSetFuncLock_NilValue(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - m := gmap.NewListKVMap[string, *int](true) - key := "nilKey" - callCount := int32(0) - - var wg sync.WaitGroup - goroutines := 50 - wg.Add(goroutines) - - for i := 0; i < goroutines; i++ { - go func() { - defer wg.Done() - m.GetOrSetFuncLock(key, func() *int { - atomic.AddInt32(&callCount, 1) - return nil - }) - }() - } - - wg.Wait() - - // Callback should be called once - t.Assert(atomic.LoadInt32(&callCount), 1) - // Typed nil pointer (*int)(nil) is stored because any(value) != nil for typed nil - // This is a Go language feature: typed nil is not the same as interface nil - t.Assert(m.Contains(key), true) - t.Assert(m.Get(key), (*int)(nil)) - t.Assert(m.Size(), 1) - }) -} - -// Test_ListKVMap_SetIfNotExistFuncLock_NilValue tests that nil values are handled correctly. -func Test_ListKVMap_SetIfNotExistFuncLock_NilValue(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - m := gmap.NewListKVMap[string, *string](true) - key := "nilKey" - callCount := int32(0) - successCount := int32(0) - - var wg sync.WaitGroup - goroutines := 50 - wg.Add(goroutines) - - for i := 0; i < goroutines; i++ { - go func() { - defer wg.Done() - success := m.SetIfNotExistFuncLock(key, func() *string { - atomic.AddInt32(&callCount, 1) - return nil - }) - if success { - atomic.AddInt32(&successCount, 1) - } - }() - } - - wg.Wait() - - // Callback should be called once - t.Assert(atomic.LoadInt32(&callCount), 1) - // Should report success once - t.Assert(atomic.LoadInt32(&successCount), 1) - // Typed nil pointer (*string)(nil) is stored because any(value) != nil for typed nil - t.Assert(m.Contains(key), true) - t.Assert(m.Get(key), (*string)(nil)) - t.Assert(m.Size(), 1) - }) -} - // Test_ListKVMap_GetOrSetFuncLock_ExistingKey tests behavior when key already exists. func Test_ListKVMap_GetOrSetFuncLock_ExistingKey(t *testing.T) { gtest.C(t, func(t *gtest.T) { diff --git a/container/gmap/gmap_z_unit_list_k_v_map_test.go b/container/gmap/gmap_z_unit_list_k_v_map_test.go index 5b9db095f90..63e6fbc46d9 100644 --- a/container/gmap/gmap_z_unit_list_k_v_map_test.go +++ b/container/gmap/gmap_z_unit_list_k_v_map_test.go @@ -1159,6 +1159,13 @@ func Test_ListKVMap_MarshalJSON_Error(t *testing.T) { t.AssertNil(err) t.Assert(string(b), `{"a":"1"}`) }) + gtest.C(t, func(t *gtest.T) { + var m gmap.ListKVMap[int, int] + m.Set(1, 10) + b, err := json.Marshal(m) + t.AssertNil(err) + t.Assert(string(b), `{"1":10}`) + }) } // Test empty map operations @@ -1358,9 +1365,10 @@ func Test_ListKVMap_TypedNil(t *testing.T) { return nil }) } - t.Assert(m1.Size(), 10) + t.Assert(m1.Size(), 5) + m2 := gmap.NewListKVMap[int, *Student](true) - m2.RegisterNilChecker(func(student *Student) bool { + m2.SetNilChecker(func(student *Student) bool { return student == nil }) for i := 0; i < 10; i++ { @@ -1390,7 +1398,8 @@ func Test_NewListKVMapWithChecker_TypedNil(t *testing.T) { return nil }) } - t.Assert(m1.Size(), 10) + t.Assert(m1.Size(), 5) + m2 := gmap.NewListKVMapWithChecker[int, *Student](func(student *Student) bool { return student == nil }, true) diff --git a/container/gset/gset_t_set.go b/container/gset/gset_t_set.go index 02ffbf6cd0f..15962e57ecb 100644 --- a/container/gset/gset_t_set.go +++ b/container/gset/gset_t_set.go @@ -9,6 +9,7 @@ package gset import ( "bytes" + "github.com/gogf/gf/v2/internal/empty" "github.com/gogf/gf/v2/internal/json" "github.com/gogf/gf/v2/internal/rwmutex" "github.com/gogf/gf/v2/text/gstr" @@ -39,7 +40,7 @@ func NewTSet[T comparable](safe ...bool) *TSet[T] { // The parameter `safe` is used to specify whether using set in concurrent-safety mode. func NewTSetWithChecker[T comparable](checker NilChecker[T], safe ...bool) *TSet[T] { s := NewTSet[T](safe...) - s.RegisterNilChecker(checker) + s.SetNilChecker(checker) return s } @@ -66,11 +67,11 @@ func NewTSetWithCheckerFrom[T comparable](items []T, checker NilChecker[T], safe return set } -// RegisterNilChecker registers a custom nil checker function for the set elements. +// SetNilChecker registers a custom nil checker function for the set elements. // 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]) SetNilChecker(nilChecker NilChecker[T]) { set.mu.Lock() defer set.mu.Unlock() set.nilChecker = nilChecker @@ -78,12 +79,12 @@ func (set *TSet[T]) RegisterNilChecker(nilChecker NilChecker[T]) { // isNil checks whether the given value is nil. // It first checks if a custom nil checker function is registered and uses it if available, -// otherwise it performs a standard nil check using any(v) == nil. +// otherwise it falls back to the default empty.IsNil function. func (set *TSet[T]) isNil(v T) bool { if set.nilChecker != nil { return set.nilChecker(v) } - return any(v) == nil + return empty.IsNil(v) } // Iterator iterates the set readonly with given callback function `f`, @@ -109,7 +110,7 @@ func (set *TSet[T]) Add(items ...T) { } // AddIfNotExist checks whether item exists in the set, -// it adds the item to set and returns true if it does not exists in the set, +// it adds the item to set and returns true if it does not exist in the set, // or else it does nothing and returns false. // // Note that, if `item` is nil, it does nothing and returns false. diff --git a/container/gset/gset_z_unit_t_set_test.go b/container/gset/gset_z_unit_t_set_test.go index 10558a707ad..66cfb326bc5 100644 --- a/container/gset/gset_z_unit_t_set_test.go +++ b/container/gset/gset_z_unit_t_set_test.go @@ -601,10 +601,10 @@ func Test_TSet_TypedNil(t *testing.T) { set := gset.NewTSet[*Student](true) var s *Student = nil exist := set.AddIfNotExist(s) - t.Assert(exist, true) + t.Assert(exist, false) set2 := gset.NewTSet[*Student](true) - set2.RegisterNilChecker(func(student *Student) bool { + set2.SetNilChecker(func(student *Student) bool { return student == nil }) exist2 := set2.AddIfNotExist(s) @@ -621,7 +621,7 @@ func Test_NewTSetWithChecker_TypedNil(t *testing.T) { set := gset.NewTSet[*Student](true) var s *Student = nil exist := set.AddIfNotExist(s) - t.Assert(exist, true) + t.Assert(exist, false) set2 := gset.NewTSetWithChecker[*Student](func(student *Student) bool { return student == nil diff --git a/container/gtree/gtree_k_v_avltree.go b/container/gtree/gtree_k_v_avltree.go index 6613647512c..b0fe2d293a4 100644 --- a/container/gtree/gtree_k_v_avltree.go +++ b/container/gtree/gtree_k_v_avltree.go @@ -12,6 +12,7 @@ import ( "github.com/emirpasic/gods/v2/trees/avltree" "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/internal/empty" "github.com/gogf/gf/v2/internal/json" "github.com/gogf/gf/v2/internal/rwmutex" "github.com/gogf/gf/v2/text/gstr" @@ -52,7 +53,7 @@ func NewAVLKVTree[K comparable, V any](comparator func(v1, v2 K) int, safe ...bo // The parameter `checker` is used to specify whether the given value is nil. func NewAVLKVTreeWithChecker[K comparable, V any](comparator func(v1, v2 K) int, checker NilChecker[V], safe ...bool) *AVLKVTree[K, V] { t := NewAVLKVTree[K, V](comparator, safe...) - t.RegisterNilChecker(checker) + t.SetNilChecker(checker) return t } @@ -78,11 +79,11 @@ func NewAVLKVTreeWithCheckerFrom[K comparable, V any](comparator func(v1, v2 K) return tree } -// RegisterNilChecker registers a custom nil checker function for the map values. +// SetNilChecker registers a custom nil checker function for the map values. // 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]) SetNilChecker(nilChecker NilChecker[V]) { tree.mu.Lock() defer tree.mu.Unlock() tree.nilChecker = nilChecker @@ -90,12 +91,12 @@ func (tree *AVLKVTree[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) { // isNil checks whether the given value is nil. // It first checks if a custom nil checker function is registered and uses it if available, -// otherwise it performs a standard nil check using any(v) == nil. -func (tree *AVLKVTree[K, V]) isNil(value V) bool { +// otherwise it falls back to the default empty.IsNil function. +func (tree *AVLKVTree[K, V]) isNil(v V) bool { if tree.nilChecker != nil { - return tree.nilChecker(value) + return tree.nilChecker(v) } - return any(value) == nil + return empty.IsNil(v) } // Clone clones and returns a new tree from current tree. diff --git a/container/gtree/gtree_k_v_btree.go b/container/gtree/gtree_k_v_btree.go index 2adcede1b9b..7586622aebb 100644 --- a/container/gtree/gtree_k_v_btree.go +++ b/container/gtree/gtree_k_v_btree.go @@ -12,6 +12,7 @@ import ( "github.com/emirpasic/gods/v2/trees/btree" "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/internal/empty" "github.com/gogf/gf/v2/internal/json" "github.com/gogf/gf/v2/internal/rwmutex" "github.com/gogf/gf/v2/text/gstr" @@ -51,7 +52,7 @@ func NewBKVTree[K comparable, V any](m int, comparator func(v1, v2 K) int, safe // The parameter `checker` is used to specify whether the given value is nil. func NewBKVTreeWithChecker[K comparable, V any](m int, comparator func(v1, v2 K) int, checker NilChecker[V], safe ...bool) *BKVTree[K, V] { t := NewBKVTree[K, V](m, comparator, safe...) - t.RegisterNilChecker(checker) + t.SetNilChecker(checker) return t } @@ -77,11 +78,11 @@ func NewBKVTreeWithCheckerFrom[K comparable, V any](m int, comparator func(v1, v return tree } -// RegisterNilChecker registers a custom nil checker function for the map values. +// SetNilChecker registers a custom nil checker function for the map values. // 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]) SetNilChecker(nilChecker NilChecker[V]) { tree.mu.Lock() defer tree.mu.Unlock() tree.nilChecker = nilChecker @@ -89,12 +90,12 @@ func (tree *BKVTree[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) { // isNil checks whether the given value is nil. // It first checks if a custom nil checker function is registered and uses it if available, -// otherwise it performs a standard nil check using any(v) == nil. -func (tree *BKVTree[K, V]) isNil(value V) bool { +// otherwise it falls back to the default empty.IsNil function. +func (tree *BKVTree[K, V]) isNil(v V) bool { if tree.nilChecker != nil { - return tree.nilChecker(value) + return tree.nilChecker(v) } - return any(value) == nil + return empty.IsNil(v) } // Clone clones and returns a new tree from current tree. diff --git a/container/gtree/gtree_k_v_redblacktree.go b/container/gtree/gtree_k_v_redblacktree.go index 47585716a98..49936d73bf6 100644 --- a/container/gtree/gtree_k_v_redblacktree.go +++ b/container/gtree/gtree_k_v_redblacktree.go @@ -12,6 +12,7 @@ import ( "github.com/emirpasic/gods/v2/trees/redblacktree" "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/internal/empty" "github.com/gogf/gf/v2/internal/json" "github.com/gogf/gf/v2/internal/rwmutex" "github.com/gogf/gf/v2/text/gstr" @@ -47,7 +48,7 @@ func NewRedBlackKVTree[K comparable, V any](comparator func(v1, v2 K) int, safe // The parameter `checker` is used to specify whether the given value is nil. func NewRedBlackKVTreeWithChecker[K comparable, V any](comparator func(v1, v2 K) int, checker NilChecker[V], safe ...bool) *RedBlackKVTree[K, V] { t := NewRedBlackKVTree[K, V](comparator, safe...) - t.RegisterNilChecker(checker) + t.SetNilChecker(checker) return t } @@ -96,11 +97,11 @@ func RedBlackKVTreeInitFrom[K comparable, V any](tree *RedBlackKVTree[K, V], com } } -// RegisterNilChecker registers a custom nil checker function for the map values. +// SetNilChecker registers a custom nil checker function for the map values. // 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]) SetNilChecker(nilChecker NilChecker[V]) { tree.mu.Lock() defer tree.mu.Unlock() tree.nilChecker = nilChecker @@ -108,12 +109,12 @@ func (tree *RedBlackKVTree[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) { // isNil checks whether the given value is nil. // It first checks if a custom nil checker function is registered and uses it if available, -// otherwise it performs a standard nil check using any(v) == nil. -func (tree *RedBlackKVTree[K, V]) isNil(value V) bool { +// otherwise it falls back to the default empty.IsNil function. +func (tree *RedBlackKVTree[K, V]) isNil(v V) bool { if tree.nilChecker != nil { - return tree.nilChecker(value) + return tree.nilChecker(v) } - return any(value) == nil + return empty.IsNil(v) } // SetComparator sets/changes the comparator for sorting. diff --git a/container/gtree/gtree_z_k_v_tree_test.go b/container/gtree/gtree_z_k_v_tree_test.go index a769bf9b88c..92a229d2f8b 100644 --- a/container/gtree/gtree_z_k_v_tree_test.go +++ b/container/gtree/gtree_z_k_v_tree_test.go @@ -29,9 +29,10 @@ func Test_KVAVLTree_TypedNil(t *testing.T) { avlTree.Set(i, s) } } - t.Assert(avlTree.Size(), 10) + t.Assert(avlTree.Size(), 5) + avlTree2 := gtree.NewAVLKVTree[int, *Student](gutil.ComparatorTStr[int], true) - avlTree2.RegisterNilChecker(func(student *Student) bool { + avlTree2.SetNilChecker(func(student *Student) bool { return student == nil }) for i := 0; i < 10; i++ { @@ -62,9 +63,10 @@ func Test_KVBTree_TypedNil(t *testing.T) { btree.Set(i, s) } } - t.Assert(btree.Size(), 10) + t.Assert(btree.Size(), 5) + btree2 := gtree.NewBKVTree[int, *Student](100, gutil.ComparatorTStr[int], true) - btree2.RegisterNilChecker(func(student *Student) bool { + btree2.SetNilChecker(func(student *Student) bool { return student == nil }) for i := 0; i < 10; i++ { @@ -95,10 +97,10 @@ func Test_KVRedBlackTree_TypedNil(t *testing.T) { redBlackTree.Set(i, s) } } - t.Assert(redBlackTree.Size(), 10) - redBlackTree2 := gtree.NewRedBlackKVTree[int, *Student](gutil.ComparatorTStr[int], true) + t.Assert(redBlackTree.Size(), 5) - redBlackTree2.RegisterNilChecker(func(student *Student) bool { + redBlackTree2 := gtree.NewRedBlackKVTree[int, *Student](gutil.ComparatorTStr[int], true) + redBlackTree2.SetNilChecker(func(student *Student) bool { return student == nil }) for i := 0; i < 10; i++ { @@ -128,7 +130,8 @@ func Test_NewKVAVLTreeWithChecker_TypedNil(t *testing.T) { avlTree.Set(i, s) } } - t.Assert(avlTree.Size(), 10) + t.Assert(avlTree.Size(), 5) + avlTree2 := gtree.NewAVLKVTreeWithChecker[int, *Student](gutil.ComparatorTStr[int], func(student *Student) bool { return student == nil }, true) @@ -160,7 +163,8 @@ func Test_NewKVBTreeWithChecker_TypedNil(t *testing.T) { btree.Set(i, s) } } - t.Assert(btree.Size(), 10) + t.Assert(btree.Size(), 5) + btree2 := gtree.NewBKVTreeWithChecker[int, *Student](100, gutil.ComparatorTStr[int], func(student *Student) bool { return student == nil }, true) @@ -192,7 +196,8 @@ func Test_NewRedBlackKVTreeWithChecker_TypedNil(t *testing.T) { redBlackTree.Set(i, s) } } - t.Assert(redBlackTree.Size(), 10) + t.Assert(redBlackTree.Size(), 5) + redBlackTree2 := gtree.NewRedBlackKVTreeWithChecker[int, *Student](gutil.ComparatorTStr[int], func(student *Student) bool { return student == nil }, true) diff --git a/contrib/drivers/dm/dm_do_insert.go b/contrib/drivers/dm/dm_do_insert.go index 72fc540b4f9..7f0aef7b44b 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 16193239c16..753e1ce7152 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 93bc17cfa81..fbdcba5d381 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 82f8373d5e4..ec0273f93f0 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 9c3bd7f8159..cc99d8e5a3d 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 4bb712c02a1..4b23af05deb 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 8b64f14e842..0e89fbfe20e 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 b81cf6f6f96..5b4f08e600f 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 }