Skip to content

Commit

Permalink
fix issue #2594 (#3303)
Browse files Browse the repository at this point in the history
  • Loading branch information
gqcn authored Feb 5, 2024
1 parent 42ec1a0 commit 85c5b7f
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 128 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ jobs:
- 1521:1521

# dm8 server
# docker run -d --name dm -p 5236:5236 loads/dm:v8.1.2.128_ent_x86_64_ctm_pack4
# docker run -p 5236:5236 loads/dm:v8.1.2.128_ent_x86_64_ctm_pack4
dm-server:
image: loads/dm:v8.1.2.128_ent_x86_64_ctm_pack4
ports:
Expand Down
73 changes: 73 additions & 0 deletions contrib/drivers/dm/dm_z_unit_issue_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). 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 dm_test

import (
"testing"
"time"

"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/text/gstr"
)

func Test_Issue2594(t *testing.T) {
table := "HANDLE_INFO"
array := gstr.SplitAndTrim(gtest.DataContent(`issue`, `2594`, `sql.sql`), ";")
for _, v := range array {
if _, err := db.Exec(ctx, v); err != nil {
gtest.Error(err)
}
}
defer dropTable(table)

type HandleValueMysql struct {
Index int64 `orm:"index"`
Type string `orm:"type"`
Data []byte `orm:"data"`
}
type HandleInfoMysql struct {
Id int `orm:"id,primary" json:"id"`
SubPrefix string `orm:"sub_prefix"`
Prefix string `orm:"prefix"`
HandleName string `orm:"handle_name"`
CreateTime time.Time `orm:"create_time"`
UpdateTime time.Time `orm:"update_time"`
Value []HandleValueMysql `orm:"value"`
}

gtest.C(t, func(t *gtest.T) {
var h1 = HandleInfoMysql{
SubPrefix: "p_",
Prefix: "m_",
HandleName: "name",
CreateTime: gtime.Now().FormatTo("Y-m-d H:i:s").Time,
UpdateTime: gtime.Now().FormatTo("Y-m-d H:i:s").Time,
Value: []HandleValueMysql{
{
Index: 10,
Type: "t1",
Data: []byte("abc"),
},
{
Index: 20,
Type: "t2",
Data: []byte("def"),
},
},
}
_, err := db.Model(table).OmitEmptyData().Insert(h1)
t.AssertNil(err)

var h2 HandleInfoMysql
err = db.Model(table).Scan(&h2)
t.AssertNil(err)

h1.Id = 1
t.Assert(h1, h2)
})
}
10 changes: 10 additions & 0 deletions contrib/drivers/dm/testdata/issue/2594/sql.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE HANDLE_INFO (
ID INT IDENTITY (1, 1) NOT NULL,
SUB_PREFIX VARCHAR(128),
PREFIX VARCHAR(256),
HANDLE_NAME VARCHAR(1024) NOT NULL,
CREATE_TIME TIMESTAMP,
UPDATE_TIME TIMESTAMP,
VALUE BLOB ,
NOT CLUSTER PRIMARY KEY (ID)
);
2 changes: 1 addition & 1 deletion database/gdb/gdb_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -823,5 +823,5 @@ func (c *Core) FormatSqlBeforeExecuting(sql string, args []interface{}) (newSql
// sql = gstr.Trim(sql)
// sql = gstr.Replace(sql, "\n", " ")
// sql, _ = gregex.ReplaceString(`\s{2,}`, ` `, sql)
return handleArguments(sql, args)
return handleSliceAndStructArgsForSql(sql, args)
}
203 changes: 118 additions & 85 deletions database/gdb/gdb_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/internal/empty"
"github.com/gogf/gf/v2/internal/intlog"
"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/os/gstructs"
Expand Down Expand Up @@ -213,15 +215,36 @@ func GetInsertOperationByOption(option InsertOption) string {
}

func anyValueToMapBeforeToRecord(value interface{}) map[string]interface{} {
return gconv.Map(value, gconv.MapOption{
convertedMap := gconv.Map(value, gconv.MapOption{
Tags: structTagPriority,
OmitEmpty: true, // To be compatible with old version from v2.6.0.
})
}

// DaToMapDeep is deprecated, use MapOrStructToMapDeep instead.
func DaToMapDeep(value interface{}) map[string]interface{} {
return MapOrStructToMapDeep(value, true)
if gutil.OriginValueAndKind(value).OriginKind != reflect.Struct {
return convertedMap
}
// It here converts all struct/map slice attributes to json string.
for k, v := range convertedMap {
originValueAndKind := gutil.OriginValueAndKind(v)
switch originValueAndKind.OriginKind {
// Check map item slice item.
case reflect.Array, reflect.Slice:
mapItemValue := originValueAndKind.OriginValue
if mapItemValue.Len() == 0 {
break
}
// Check slice item type struct/map type.
switch mapItemValue.Index(0).Kind() {
case reflect.Struct, reflect.Map:
mapItemJsonBytes, err := json.Marshal(v)
if err != nil {
// Do not eat any error.
intlog.Error(context.TODO(), err)
}
convertedMap[k] = mapItemJsonBytes
}
}
}
return convertedMap
}

// MapOrStructToMapDeep converts `value` to map type recursively(if attribute struct is embedded).
Expand Down Expand Up @@ -636,7 +659,7 @@ func formatWhereHolder(ctx context.Context, db DB, in formatWhereHolderInput) (n
}
}
}
return handleArguments(newWhere, newArgs)
return handleSliceAndStructArgsForSql(newWhere, newArgs)
}

// formatWhereInterfaces formats `where` as []interface{}.
Expand Down Expand Up @@ -761,97 +784,107 @@ func formatWhereKeyValue(in formatWhereKeyValueInput) (newArgs []interface{}) {
return in.Args
}

// handleArguments is an important function, which handles the sql and all its arguments
// handleSliceAndStructArgsForSql is an important function, which handles the sql and all its arguments
// before committing them to underlying driver.
func handleArguments(sql string, args []interface{}) (newSql string, newArgs []interface{}) {
newSql = sql
func handleSliceAndStructArgsForSql(
oldSql string, oldArgs []interface{},
) (newSql string, newArgs []interface{}) {
newSql = oldSql
if len(oldArgs) == 0 {
return
}
// insertHolderCount is used to calculate the inserting position for the '?' holder.
insertHolderCount := 0
// Handles the slice arguments.
if len(args) > 0 {
for index, arg := range args {
reflectInfo := reflection.OriginValueAndKind(arg)
switch reflectInfo.OriginKind {
case reflect.Slice, reflect.Array:
// It does not split the type of []byte.
// Eg: table.Where("name = ?", []byte("john"))
if _, ok := arg.([]byte); ok {
newArgs = append(newArgs, arg)
continue
}

if reflectInfo.OriginValue.Len() == 0 {
// Empty slice argument, it converts the sql to a false sql.
// Eg:
// Query("select * from xxx where id in(?)", g.Slice{}) -> select * from xxx where 0=1
// Where("id in(?)", g.Slice{}) -> WHERE 0=1
if gstr.Contains(newSql, "?") {
whereKeyWord := " WHERE "
if p := gstr.PosI(newSql, whereKeyWord); p == -1 {
return "0=1", []interface{}{}
} else {
return gstr.SubStr(newSql, 0, p+len(whereKeyWord)) + "0=1", []interface{}{}
}
}
} else {
for i := 0; i < reflectInfo.OriginValue.Len(); i++ {
newArgs = append(newArgs, reflectInfo.OriginValue.Index(i).Interface())
// Handles the slice and struct type argument item.
for index, oldArg := range oldArgs {
argReflectInfo := reflection.OriginValueAndKind(oldArg)
switch argReflectInfo.OriginKind {
case reflect.Slice, reflect.Array:
// It does not split the type of []byte.
// Eg: table.Where("name = ?", []byte("john"))
if _, ok := oldArg.([]byte); ok {
newArgs = append(newArgs, oldArg)
continue
}
var (
valueHolderCount = gstr.Count(newSql, "?")
argSliceLength = argReflectInfo.OriginValue.Len()
)
if argSliceLength == 0 {
// Empty slice argument, it converts the sql to a false sql.
// Example:
// Query("select * from xxx where id in(?)", g.Slice{}) -> select * from xxx where 0=1
// Where("id in(?)", g.Slice{}) -> WHERE 0=1
if gstr.Contains(newSql, "?") {
whereKeyWord := " WHERE "
if p := gstr.PosI(newSql, whereKeyWord); p == -1 {
return "0=1", []interface{}{}
} else {
return gstr.SubStr(newSql, 0, p+len(whereKeyWord)) + "0=1", []interface{}{}
}
}

// If the '?' holder count equals the length of the slice,
// it does not implement the arguments splitting logic.
// Eg: db.Query("SELECT ?+?", g.Slice{1, 2})
if len(args) == 1 && gstr.Count(newSql, "?") == reflectInfo.OriginValue.Len() {
break
} else {
// Example:
// Query("SELECT ?+?", g.Slice{1,2})
// WHERE("id=?", g.Slice{1,2})
for i := 0; i < argSliceLength; i++ {
newArgs = append(newArgs, argReflectInfo.OriginValue.Index(i).Interface())
}
// counter is used to finding the inserting position for the '?' holder.
var (
counter = 0
replaced = false
)
newSql, _ = gregex.ReplaceStringFunc(`\?`, newSql, func(s string) string {
if replaced {
return s
}
counter++
if counter == index+insertHolderCount+1 {
replaced = true
insertHolderCount += reflectInfo.OriginValue.Len() - 1
return "?" + strings.Repeat(",?", reflectInfo.OriginValue.Len()-1)
}
return s
})
}

// Special struct handling.
case reflect.Struct:
switch arg.(type) {
// The underlying driver supports time.Time/*time.Time types.
case time.Time, *time.Time:
newArgs = append(newArgs, arg)
continue
// If the '?' holder count equals the length of the slice,
// it does not implement the arguments splitting logic.
// Eg: db.Query("SELECT ?+?", g.Slice{1, 2})
if len(oldArgs) == 1 && valueHolderCount == argSliceLength {
break
}

case gtime.Time:
newArgs = append(newArgs, arg.(gtime.Time).Time)
continue
// counter is used to finding the inserting position for the '?' holder.
var (
counter = 0
replaced = false
)
newSql, _ = gregex.ReplaceStringFunc(`\?`, newSql, func(s string) string {
if replaced {
return s
}
counter++
if counter == index+insertHolderCount+1 {
replaced = true
insertHolderCount += argSliceLength - 1
return "?" + strings.Repeat(",?", argSliceLength-1)
}
return s
})

case *gtime.Time:
newArgs = append(newArgs, arg.(*gtime.Time).Time)
continue
// Special struct handling.
case reflect.Struct:
switch oldArg.(type) {
// The underlying driver supports time.Time/*time.Time types.
case time.Time, *time.Time:
newArgs = append(newArgs, oldArg)
continue

default:
// It converts the struct to string in default
// if it has implemented the String interface.
if v, ok := arg.(iString); ok {
newArgs = append(newArgs, v.String())
continue
}
}
newArgs = append(newArgs, arg)
case gtime.Time:
newArgs = append(newArgs, oldArg.(gtime.Time).Time)
continue

case *gtime.Time:
newArgs = append(newArgs, oldArg.(*gtime.Time).Time)
continue

default:
newArgs = append(newArgs, arg)
// It converts the struct to string in default
// if it has implemented the String interface.
if v, ok := oldArg.(iString); ok {
newArgs = append(newArgs, v.String())
continue
}
}
newArgs = append(newArgs, oldArg)

default:
newArgs = append(newArgs, oldArg)
}
}
return
Expand Down
Loading

0 comments on commit 85c5b7f

Please sign in to comment.