Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix issue #2594 #3303

Merged
merged 1 commit into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading