Merge branch 'fix/instances2' of https://github.com/LanceAdd/gf into fix/container-nil-check

This commit is contained in:
John Guo
2026-01-22 19:35:56 +08:00
14 changed files with 60 additions and 50 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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
})

View File

@ -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
}