This commit is contained in:
John Guo
2025-02-28 11:54:12 +08:00
parent 67bb75a2d9
commit ce2cf3c04b
8 changed files with 377 additions and 161 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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