mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
refactor(util/gconv): add Converter feature for more flexable and extensible type converting (#4107)
This commit is contained in:
@ -516,8 +516,9 @@ type Core struct {
|
||||
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
|
||||
innerMemCache *gcache.Cache // Internal memory cache for storing temporary data.
|
||||
}
|
||||
|
||||
type dynamicConfig struct {
|
||||
@ -768,6 +769,8 @@ const (
|
||||
SqlTypeStmtQueryRowContext SqlType = "DB.Statement.QueryRowContext"
|
||||
)
|
||||
|
||||
// LocalType is a type that defines the local storage type of a field value.
|
||||
// It is used to specify how the field value should be processed locally.
|
||||
type LocalType string
|
||||
|
||||
const (
|
||||
@ -925,6 +928,7 @@ func newDBByConfigNode(node *ConfigNode, group string) (db DB, err error) {
|
||||
links: gmap.New(true),
|
||||
logger: glog.New(),
|
||||
config: node,
|
||||
localTypeMap: gmap.NewStrAnyMap(true),
|
||||
innerMemCache: gcache.New(),
|
||||
dynamicConfig: dynamicConfig{
|
||||
MaxIdleConnCount: node.MaxIdleConnCount,
|
||||
|
||||
82
database/gdb/gdb_converter.go
Normal file
82
database/gdb/gdb_converter.go
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// iVal is used for type assert api for Val().
|
||||
type iVal interface {
|
||||
Val() any
|
||||
}
|
||||
|
||||
var (
|
||||
// converter is the internal type converter for gdb.
|
||||
converter = gconv.NewConverter()
|
||||
)
|
||||
|
||||
func init() {
|
||||
converter.RegisterAnyConverterFunc(
|
||||
sliceTypeConverterFunc,
|
||||
reflect.TypeOf([]string{}),
|
||||
reflect.TypeOf([]float32{}),
|
||||
reflect.TypeOf([]float64{}),
|
||||
reflect.TypeOf([]int{}),
|
||||
reflect.TypeOf([]int32{}),
|
||||
reflect.TypeOf([]int64{}),
|
||||
reflect.TypeOf([]uint{}),
|
||||
reflect.TypeOf([]uint32{}),
|
||||
reflect.TypeOf([]uint64{}),
|
||||
)
|
||||
}
|
||||
|
||||
// GetConverter returns the internal type converter for gdb.
|
||||
func GetConverter() gconv.Converter {
|
||||
return converter
|
||||
}
|
||||
|
||||
func sliceTypeConverterFunc(from any, to reflect.Value) (err error) {
|
||||
v, ok := from.(iVal)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
fromVal := v.Val()
|
||||
switch x := fromVal.(type) {
|
||||
case []byte:
|
||||
dst := to.Addr().Interface()
|
||||
err = json.Unmarshal(x, dst)
|
||||
case string:
|
||||
dst := to.Addr().Interface()
|
||||
err = json.Unmarshal([]byte(x), dst)
|
||||
default:
|
||||
fromType := reflect.TypeOf(fromVal)
|
||||
switch fromType.Kind() {
|
||||
case reflect.Slice:
|
||||
convertOption := gconv.ConvertOption{
|
||||
SliceOption: gconv.SliceOption{ContinueOnError: true},
|
||||
MapOption: gconv.MapOption{ContinueOnError: true},
|
||||
StructOption: gconv.StructOption{ContinueOnError: true},
|
||||
}
|
||||
dv, err := converter.ConvertWithTypeName(fromVal, to.Type().String(), convertOption)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
to.Set(reflect.ValueOf(dv))
|
||||
default:
|
||||
err = gerror.Newf(
|
||||
`unsupported type converting from type "%T" to type "%T"`,
|
||||
fromVal, to,
|
||||
)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -223,6 +223,8 @@ func (c *Core) GetScan(ctx context.Context, pointer interface{}, sql string, arg
|
||||
|
||||
case reflect.Struct:
|
||||
return c.db.GetCore().doGetStruct(ctx, pointer, sql, args...)
|
||||
|
||||
default:
|
||||
}
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
@ -735,6 +737,7 @@ func (c *Core) HasTable(name string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GetInnerMemCache retrieves and returns the inner memory cache object.
|
||||
func (c *Core) GetInnerMemCache() *gcache.Cache {
|
||||
return c.innerMemCache
|
||||
}
|
||||
|
||||
@ -223,7 +223,9 @@ Default:
|
||||
}
|
||||
|
||||
// CheckLocalTypeForField checks and returns corresponding type for given db type.
|
||||
func (c *Core) CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (LocalType, error) {
|
||||
// The `fieldType` is retrieved from ColumnTypes of db driver, example:
|
||||
// UNSIGNED INT
|
||||
func (c *Core) CheckLocalTypeForField(ctx context.Context, fieldType string, _ interface{}) (LocalType, error) {
|
||||
var (
|
||||
typeName string
|
||||
typePattern string
|
||||
@ -233,7 +235,12 @@ func (c *Core) CheckLocalTypeForField(ctx context.Context, fieldType string, fie
|
||||
typeName = gstr.Trim(match[1])
|
||||
typePattern = gstr.Trim(match[2])
|
||||
} else {
|
||||
typeName = gstr.Split(fieldType, " ")[0]
|
||||
var array = gstr.SplitAndTrim(fieldType, " ")
|
||||
if len(array) > 1 && gstr.Equal(array[0], "unsigned") {
|
||||
typeName = array[1]
|
||||
} else if len(array) > 0 {
|
||||
typeName = array[0]
|
||||
}
|
||||
}
|
||||
|
||||
typeName = strings.ToLower(typeName)
|
||||
@ -291,11 +298,6 @@ func (c *Core) CheckLocalTypeForField(ctx context.Context, fieldType string, fie
|
||||
if typePattern == "1" {
|
||||
return LocalTypeBool, nil
|
||||
}
|
||||
s := gconv.String(fieldValue)
|
||||
// mssql is true|false string.
|
||||
if strings.EqualFold(s, "true") || strings.EqualFold(s, "false") {
|
||||
return LocalTypeBool, nil
|
||||
}
|
||||
if gstr.ContainsI(fieldType, "unsigned") {
|
||||
return LocalTypeUint64Bytes, nil
|
||||
}
|
||||
|
||||
@ -478,8 +478,11 @@ func (c *Core) RowsToResult(ctx context.Context, rows *sql.Rows) (Result, error)
|
||||
// which will cause struct converting issue.
|
||||
record[columnTypes[i].Name()] = nil
|
||||
} else {
|
||||
var convertedValue interface{}
|
||||
if convertedValue, err = c.columnValueToLocalValue(ctx, value, columnTypes[i]); err != nil {
|
||||
var (
|
||||
convertedValue interface{}
|
||||
columnType = columnTypes[i]
|
||||
)
|
||||
if convertedValue, err = c.columnValueToLocalValue(ctx, value, columnType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
record[columnTypes[i].Name()] = gvar.New(convertedValue)
|
||||
@ -498,7 +501,9 @@ func (c *Core) OrderRandomFunction() string {
|
||||
return "RAND()"
|
||||
}
|
||||
|
||||
func (c *Core) columnValueToLocalValue(ctx context.Context, value interface{}, columnType *sql.ColumnType) (interface{}, error) {
|
||||
func (c *Core) columnValueToLocalValue(
|
||||
ctx context.Context, value interface{}, columnType *sql.ColumnType,
|
||||
) (interface{}, error) {
|
||||
var scanType = columnType.ScanType()
|
||||
if scanType != nil {
|
||||
// Common basic builtin types.
|
||||
|
||||
@ -53,7 +53,10 @@ func (r Record) Struct(pointer interface{}) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return gconv.StructTag(r, pointer, OrmTagForStruct)
|
||||
return converter.Struct(r, pointer, gconv.StructOption{
|
||||
PriorityTag: OrmTagForStruct,
|
||||
ContinueOnError: true,
|
||||
})
|
||||
}
|
||||
|
||||
// IsEmpty checks and returns whether `r` is empty.
|
||||
|
||||
@ -200,5 +200,12 @@ func (r Result) Structs(pointer interface{}) (err error) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return gconv.StructsTag(r, pointer, OrmTagForStruct)
|
||||
var (
|
||||
sliceOption = gconv.SliceOption{ContinueOnError: true}
|
||||
mapOption = gconv.StructOption{
|
||||
PriorityTag: OrmTagForStruct,
|
||||
ContinueOnError: true,
|
||||
}
|
||||
)
|
||||
return converter.Structs(r, pointer, sliceOption, mapOption)
|
||||
}
|
||||
|
||||
@ -14,6 +14,15 @@ import (
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
)
|
||||
|
||||
func Test_GetConverter(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
c := GetConverter()
|
||||
s, err := c.String(1)
|
||||
t.AssertNil(err)
|
||||
t.AssertEQ(s, "1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_HookSelect_Regex(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
|
||||
Reference in New Issue
Block a user