diff --git a/contrib/drivers/mysql/db_utils_test.go b/contrib/drivers/mysql/db_utils_test.go new file mode 100644 index 000000000..04fa21eb7 --- /dev/null +++ b/contrib/drivers/mysql/db_utils_test.go @@ -0,0 +1,169 @@ +// 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 mysql_test + +import ( + "database/sql" + "fmt" + "reflect" + "strings" + "testing" + + "github.com/gogf/gf/v2/test/gtest" +) + +// DBConfig represents database configuration +type DBConfig struct { + Username string + Password string + Host string + DBName string +} + +// QueryAndScan executes a query and scans the results into a slice of struct pointers +// Parameters: +// - query: SQL query string +// - args: Query arguments +// - dest: Pointer to slice of struct pointers where results will be stored +// +// Returns error if any occurs during the process +func QueryAndScan(config DBConfig, query string, args []interface{}, dest interface{}) error { + // Validate input parameters + destValue := reflect.ValueOf(dest) + if destValue.Kind() != reflect.Ptr || destValue.Elem().Kind() != reflect.Slice { + return fmt.Errorf("dest must be a pointer to slice") + } + + // Connect to database + dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=true", + config.Username, + config.Password, + config.Host, + config.DBName, + ) + + db, err := sql.Open("mysql", dsn) + if err != nil { + return fmt.Errorf("failed to connect to database: %v", err) + } + defer db.Close() + + // Execute query + rows, err := db.Query(query, args...) + if err != nil { + return fmt.Errorf("failed to execute query: %v", err) + } + defer rows.Close() + + // Get column names + columns, err := rows.Columns() + if err != nil { + return fmt.Errorf("failed to get column names: %v", err) + } + + // Get the type of slice elements + sliceType := destValue.Elem().Type() + elementType := sliceType.Elem() + if elementType.Kind() == reflect.Ptr { + elementType = elementType.Elem() + } + + // Create a map of field names to struct fields + fieldMap := make(map[string]int) + for i := 0; i < elementType.NumField(); i++ { + field := elementType.Field(i) + // Check orm tag first, then json tag, then field name + tagName := field.Tag.Get("orm") + if tagName == "" { + tagName = field.Tag.Get("json") + } + if tagName == "" { + tagName = strings.ToLower(field.Name) + } + fieldMap[tagName] = i + } + + // Prepare slice to store results + sliceValue := destValue.Elem() + + // Scan rows + for rows.Next() { + // Create a new struct instance + newElem := reflect.New(elementType) + + // Create scan destinations that point directly to struct fields + scanDest := make([]interface{}, len(columns)) + for i, colName := range columns { + if fieldIndex, ok := fieldMap[colName]; ok { + field := newElem.Elem().Field(fieldIndex) + if field.CanAddr() { + scanDest[i] = field.Addr().Interface() + } else { + // For fields that can't be addressed, use a temporary variable + var v interface{} + scanDest[i] = &v + } + } else { + // Column doesn't map to any field, use a placeholder + var v interface{} + scanDest[i] = &v + } + } + + // Scan the row directly into struct fields + if err := rows.Scan(scanDest...); err != nil { + return fmt.Errorf("failed to scan row: %v", err) + } + + // Append the new element to the result slice + if sliceType.Elem().Kind() == reflect.Ptr { + sliceValue.Set(reflect.Append(sliceValue, newElem)) + } else { + sliceValue.Set(reflect.Append(sliceValue, newElem.Elem())) + } + } + + // Check for errors from iterating over rows + if err := rows.Err(); err != nil { + return fmt.Errorf("error iterating over rows: %v", err) + } + + return nil +} + +func Test_Issue4086_2(t *testing.T) { + config := DBConfig{ + Username: "root", + Password: "12345678", + Host: "127.0.0.1", + DBName: "test1", + } + gtest.C(t, func(t *gtest.T) { + type ProxyParam struct { + ProxyId int64 `json:"proxyId" orm:"proxy_id"` + RecommendIds []int64 `json:"recommendIds" orm:"recommend_ids"` + Photos []string `json:"photos" orm:"photos"` + } + + var proxyParamList []*ProxyParam + + err := QueryAndScan(config, "SELECT * FROM issue4086", nil, &proxyParamList) + fmt.Println(err) + t.Assert(proxyParamList, []*ProxyParam{ + { + ProxyId: 1, + RecommendIds: []int64{584, 585}, + Photos: nil, + }, + { + ProxyId: 2, + RecommendIds: []int64{}, + Photos: nil, + }, + }) + }) +} diff --git a/contrib/drivers/mysql/mysql_z_unit_issue_test.go b/contrib/drivers/mysql/mysql_z_unit_issue_test.go index cac6c93be..5abc11ab2 100644 --- a/contrib/drivers/mysql/mysql_z_unit_issue_test.go +++ b/contrib/drivers/mysql/mysql_z_unit_issue_test.go @@ -1741,6 +1741,32 @@ func Test_Issue4086(t *testing.T) { }, }) }) + + gtest.C(t, func(t *gtest.T) { + type ProxyParam struct { + ProxyId int64 `json:"proxyId" orm:"proxy_id"` + RecommendIds []int64 `json:"recommendIds" orm:"recommend_ids"` + Photos []int64 `json:"photos" orm:"photos"` + } + + var proxyParamList []*ProxyParam + err := db.Model(table).Ctx(ctx).Scan(&proxyParamList) + t.AssertNil(err) + t.Assert(len(proxyParamList), 2) + t.Assert(proxyParamList, []*ProxyParam{ + { + ProxyId: 1, + RecommendIds: []int64{584, 585}, + Photos: nil, + }, + { + ProxyId: 2, + RecommendIds: []int64{}, + Photos: nil, + }, + }) + }) + gtest.C(t, func(t *gtest.T) { type ProxyParam struct { ProxyId int64 `json:"proxyId" orm:"proxy_id"` diff --git a/util/gconv/gconv_basic.go b/util/gconv/gconv_basic.go index 56a10e075..5eff3b78b 100644 --- a/util/gconv/gconv_basic.go +++ b/util/gconv/gconv_basic.go @@ -311,26 +311,3 @@ func doBool(any any) (bool, error) { } } } - -// checkJsonAndUnmarshalUseNumber checks if given `any` is JSON formatted string value and does converting using `json.UnmarshalUseNumber`. -func checkJsonAndUnmarshalUseNumber(any any, target any) bool { - switch r := any.(type) { - case []byte: - if json.Valid(r) { - if err := json.UnmarshalUseNumber(r, &target); err != nil { - return false - } - return true - } - - case string: - anyAsBytes := []byte(r) - if json.Valid(anyAsBytes) { - if err := json.UnmarshalUseNumber(anyAsBytes, &target); err != nil { - return false - } - return true - } - } - return false -} diff --git a/util/gconv/gconv_slice_any.go b/util/gconv/gconv_slice_any.go index 00ecee0c2..f61044330 100644 --- a/util/gconv/gconv_slice_any.go +++ b/util/gconv/gconv_slice_any.go @@ -65,13 +65,22 @@ func Interfaces(any interface{}) []interface{} { } case []uint8: if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]interface{}, len(value)) - for k, v := range value { - array[k] = v + if _ = json.UnmarshalUseNumber(value, &array); array != nil { + return array } } + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if _ = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array + } + } + case []uint16: array = make([]interface{}, len(value)) for k, v := range value { @@ -108,10 +117,7 @@ func Interfaces(any interface{}) []interface{} { if v, ok := any.(localinterface.IInterfaces); ok { return v.Interfaces() } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } + // Not a common type, it then uses reflection for conversion. originValueAndKind := reflection.OriginValueAndKind(any) switch originValueAndKind.OriginKind { diff --git a/util/gconv/gconv_slice_float.go b/util/gconv/gconv_slice_float.go index bca5af6b0..f497fa92b 100644 --- a/util/gconv/gconv_slice_float.go +++ b/util/gconv/gconv_slice_float.go @@ -11,6 +11,7 @@ import ( "github.com/gogf/gf/v2/internal/json" "github.com/gogf/gf/v2/internal/reflection" + "github.com/gogf/gf/v2/internal/utils" "github.com/gogf/gf/v2/util/gconv/internal/localinterface" ) @@ -43,11 +44,6 @@ func Float32s(any interface{}) []float32 { array []float32 = nil ) switch value := any.(type) { - case string: - if value == "" { - return []float32{} - } - return []float32{Float32(value)} case []string: array = make([]float32, len(value)) for k, v := range value { @@ -84,13 +80,27 @@ func Float32s(any interface{}) []float32 { } case []uint8: if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]float32, len(value)) - for k, v := range value { - array[k] = Float32(v) + if _ = json.UnmarshalUseNumber(value, &array); array != nil { + return array } } + array = make([]float32, len(value)) + for k, v := range value { + array[k] = Float32(v) + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if _ = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array + } + } + if value == "" { + return []float32{} + } + if utils.IsNumeric(value) { + return []float32{Float32(value)} + } case []uint16: array = make([]float32, len(value)) for k, v := range value { @@ -133,10 +143,6 @@ func Float32s(any interface{}) []float32 { if v, ok := any.(localinterface.IInterfaces); ok { return Float32s(v.Interfaces()) } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } // Not a common type, it then uses reflection for conversion. originValueAndKind := reflection.OriginValueAndKind(any) switch originValueAndKind.OriginKind { @@ -167,11 +173,6 @@ func Float64s(any interface{}) []float64 { array []float64 = nil ) switch value := any.(type) { - case string: - if value == "" { - return []float64{} - } - return []float64{Float64(value)} case []string: array = make([]float64, len(value)) for k, v := range value { @@ -208,13 +209,27 @@ func Float64s(any interface{}) []float64 { } case []uint8: if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]float64, len(value)) - for k, v := range value { - array[k] = Float64(v) + if _ = json.UnmarshalUseNumber(value, &array); array != nil { + return array } } + array = make([]float64, len(value)) + for k, v := range value { + array[k] = Float64(v) + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if _ = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array + } + } + if value == "" { + return []float64{} + } + if utils.IsNumeric(value) { + return []float64{Float64(value)} + } case []uint16: array = make([]float64, len(value)) for k, v := range value { @@ -257,10 +272,6 @@ func Float64s(any interface{}) []float64 { if v, ok := any.(localinterface.IInterfaces); ok { return Floats(v.Interfaces()) } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } // Not a common type, it then uses reflection for conversion. originValueAndKind := reflection.OriginValueAndKind(any) switch originValueAndKind.OriginKind { diff --git a/util/gconv/gconv_slice_int.go b/util/gconv/gconv_slice_int.go index 929acfa14..dc8ef41f4 100644 --- a/util/gconv/gconv_slice_int.go +++ b/util/gconv/gconv_slice_int.go @@ -11,6 +11,7 @@ import ( "github.com/gogf/gf/v2/internal/json" "github.com/gogf/gf/v2/internal/reflection" + "github.com/gogf/gf/v2/internal/utils" "github.com/gogf/gf/v2/util/gconv/internal/localinterface" ) @@ -72,13 +73,27 @@ func Ints(any interface{}) []int { } case []uint8: if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]int, len(value)) - for k, v := range value { - array[k] = int(v) + if _ = json.UnmarshalUseNumber(value, &array); array != nil { + return array } } + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if _ = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array + } + } + if value == "" { + return []int{} + } + if utils.IsNumeric(value) { + return []int{Int(value)} + } case []uint16: array = make([]int, len(value)) for k, v := range value { @@ -133,10 +148,6 @@ func Ints(any interface{}) []int { if v, ok := any.(localinterface.IInterfaces); ok { return Ints(v.Interfaces()) } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } // Not a common type, it then uses reflection for conversion. originValueAndKind := reflection.OriginValueAndKind(any) switch originValueAndKind.OriginKind { @@ -201,13 +212,27 @@ func Int32s(any interface{}) []int32 { } case []uint8: if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]int32, len(value)) - for k, v := range value { - array[k] = int32(v) + if _ = json.UnmarshalUseNumber(value, &array); array != nil { + return array } } + array = make([]int32, len(value)) + for k, v := range value { + array[k] = int32(v) + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if _ = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array + } + } + if value == "" { + return []int32{} + } + if utils.IsNumeric(value) { + return []int32{Int32(value)} + } case []uint16: array = make([]int32, len(value)) for k, v := range value { @@ -262,10 +287,6 @@ func Int32s(any interface{}) []int32 { if v, ok := any.(localinterface.IInterfaces); ok { return Int32s(v.Interfaces()) } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } // Not a common type, it then uses reflection for conversion. originValueAndKind := reflection.OriginValueAndKind(any) switch originValueAndKind.OriginKind { @@ -330,13 +351,27 @@ func Int64s(any interface{}) []int64 { } case []uint8: if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]int64, len(value)) - for k, v := range value { - array[k] = int64(v) + if _ = json.UnmarshalUseNumber(value, &array); array != nil { + return array } } + array = make([]int64, len(value)) + for k, v := range value { + array[k] = int64(v) + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if _ = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array + } + } + if value == "" { + return []int64{} + } + if utils.IsNumeric(value) { + return []int64{Int64(value)} + } case []uint16: array = make([]int64, len(value)) for k, v := range value { @@ -391,10 +426,6 @@ func Int64s(any interface{}) []int64 { if v, ok := any.(localinterface.IInterfaces); ok { return Int64s(v.Interfaces()) } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } // Not a common type, it then uses reflection for conversion. originValueAndKind := reflection.OriginValueAndKind(any) switch originValueAndKind.OriginKind { diff --git a/util/gconv/gconv_slice_str.go b/util/gconv/gconv_slice_str.go index 405e72ca5..2640be0d0 100644 --- a/util/gconv/gconv_slice_str.go +++ b/util/gconv/gconv_slice_str.go @@ -60,28 +60,26 @@ func Strings(any interface{}) []string { } case []uint8: if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } - if array == nil { - array = make([]string, len(value)) - for k, v := range value { - array[k] = String(v) + if _ = json.UnmarshalUseNumber(value, &array); array != nil { + return array } - return array } + array = make([]string, len(value)) + for k, v := range value { + array[k] = String(v) + } + return array case string: byteValue := []byte(value) if json.Valid(byteValue) { - _ = json.UnmarshalUseNumber(byteValue, &array) - } - if array == nil { - if value == "" { - return []string{} + if _ = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array } - // Prevent strings from being null - // See Issue 3465 for details - return []string{value} } + if value == "" { + return []string{} + } + return []string{value} case []uint16: array = make([]string, len(value)) for k, v := range value { @@ -134,10 +132,6 @@ func Strings(any interface{}) []string { if v, ok := any.(localinterface.IInterfaces); ok { return Strings(v.Interfaces()) } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } // Not a common type, it then uses reflection for conversion. originValueAndKind := reflection.OriginValueAndKind(any) switch originValueAndKind.OriginKind { diff --git a/util/gconv/gconv_slice_uint.go b/util/gconv/gconv_slice_uint.go index 8ab15dbf7..1b5160e4d 100644 --- a/util/gconv/gconv_slice_uint.go +++ b/util/gconv/gconv_slice_uint.go @@ -8,7 +8,6 @@ package gconv import ( "reflect" - "strings" "github.com/gogf/gf/v2/internal/json" "github.com/gogf/gf/v2/internal/reflection" @@ -36,20 +35,10 @@ func Uints(any interface{}) []uint { if any == nil { return nil } - var ( array []uint = nil ) switch value := any.(type) { - case string: - value = strings.TrimSpace(value) - if value == "" { - return []uint{} - } - if utils.IsNumeric(value) { - return []uint{Uint(value)} - } - case []string: array = make([]uint, len(value)) for k, v := range value { @@ -79,13 +68,27 @@ func Uints(any interface{}) []uint { array = value case []uint8: if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]uint, len(value)) - for k, v := range value { - array[k] = uint(v) + if _ = json.UnmarshalUseNumber(value, &array); array != nil { + return array } } + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if _ = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array + } + } + if value == "" { + return []uint{} + } + if utils.IsNumeric(value) { + return []uint{Uint(value)} + } case []uint16: array = make([]uint, len(value)) for k, v := range value { @@ -143,10 +146,6 @@ func Uints(any interface{}) []uint { if v, ok := any.(localinterface.IInterfaces); ok { return Uints(v.Interfaces()) } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } // Not a common type, it then uses reflection for conversion. originValueAndKind := reflection.OriginValueAndKind(any) switch originValueAndKind.OriginKind { @@ -177,14 +176,6 @@ func Uint32s(any interface{}) []uint32 { array []uint32 = nil ) switch value := any.(type) { - case string: - value = strings.TrimSpace(value) - if value == "" { - return []uint32{} - } - if utils.IsNumeric(value) { - return []uint32{Uint32(value)} - } case []string: array = make([]uint32, len(value)) for k, v := range value { @@ -217,13 +208,27 @@ func Uint32s(any interface{}) []uint32 { } case []uint8: if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]uint32, len(value)) - for k, v := range value { - array[k] = uint32(v) + if _ = json.UnmarshalUseNumber(value, &array); array != nil { + return array } } + array = make([]uint32, len(value)) + for k, v := range value { + array[k] = uint32(v) + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if _ = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array + } + } + if value == "" { + return []uint32{} + } + if utils.IsNumeric(value) { + return []uint32{Uint32(value)} + } case []uint16: array = make([]uint32, len(value)) for k, v := range value { @@ -277,10 +282,6 @@ func Uint32s(any interface{}) []uint32 { if v, ok := any.(localinterface.IInterfaces); ok { return Uint32s(v.Interfaces()) } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } // Not a common type, it then uses reflection for conversion. originValueAndKind := reflection.OriginValueAndKind(any) switch originValueAndKind.OriginKind { @@ -311,15 +312,6 @@ func Uint64s(any interface{}) []uint64 { array []uint64 = nil ) switch value := any.(type) { - case string: - value = strings.TrimSpace(value) - if value == "" { - return []uint64{} - } - if utils.IsNumeric(value) { - return []uint64{Uint64(value)} - } - case []string: array = make([]uint64, len(value)) for k, v := range value { @@ -352,13 +344,27 @@ func Uint64s(any interface{}) []uint64 { } case []uint8: if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]uint64, len(value)) - for k, v := range value { - array[k] = uint64(v) + if _ = json.UnmarshalUseNumber(value, &array); array != nil { + return array } } + array = make([]uint64, len(value)) + for k, v := range value { + array[k] = uint64(v) + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if _ = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array + } + } + if value == "" { + return []uint64{} + } + if utils.IsNumeric(value) { + return []uint64{Uint64(value)} + } case []uint16: array = make([]uint64, len(value)) for k, v := range value { @@ -411,10 +417,6 @@ func Uint64s(any interface{}) []uint64 { if v, ok := any.(localinterface.IInterfaces); ok { return Uint64s(v.Interfaces()) } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } // Not a common type, it then uses reflection for conversion. originValueAndKind := reflection.OriginValueAndKind(any) switch originValueAndKind.OriginKind {