Skip to content

Commit

Permalink
up
Browse files Browse the repository at this point in the history
  • Loading branch information
gqcn committed Feb 28, 2025
1 parent 67bb75a commit ce2cf3c
Show file tree
Hide file tree
Showing 8 changed files with 377 additions and 161 deletions.
169 changes: 169 additions & 0 deletions contrib/drivers/mysql/db_utils_test.go
Original file line number Diff line number Diff line change
@@ -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,
},
})
})
}
26 changes: 26 additions & 0 deletions contrib/drivers/mysql/mysql_z_unit_issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
23 changes: 0 additions & 23 deletions util/gconv/gconv_basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
24 changes: 15 additions & 9 deletions util/gconv/gconv_slice_any.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
67 changes: 39 additions & 28 deletions util/gconv/gconv_slice_float.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit ce2cf3c

Please sign in to comment.