This commit is contained in:
John Guo
2024-02-05 20:40:03 +08:00
committed by GitHub
parent 42ec1a0076
commit 85c5b7f19e
7 changed files with 211 additions and 130 deletions

View File

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

View File

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

View File

@ -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)
);

View File

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

View File

@ -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"
@ -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).
@ -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{}.
@ -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
// 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{}{}
}
}
} 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())
}
}
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())
}
}
// 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
}
// 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
}
// 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)
}
// 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
})
// 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
case gtime.Time:
newArgs = append(newArgs, arg.(gtime.Time).Time)
continue
case *gtime.Time:
newArgs = append(newArgs, arg.(*gtime.Time).Time)
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)
counter++
if counter == index+insertHolderCount+1 {
replaced = true
insertHolderCount += argSliceLength - 1
return "?" + strings.Repeat(",?", argSliceLength-1)
}
return s
})
// 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
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

View File

@ -248,57 +248,18 @@ func (m *Model) doInsertWithOption(ctx context.Context, insertOption InsertOptio
fieldNameCreate = m.getSoftFieldNameCreated("", m.tablesInit)
fieldNameUpdate = m.getSoftFieldNameUpdated("", m.tablesInit)
)
// m.data was already converted to type List/Map by function Data
newData, err := m.filterDataForInsertOrUpdate(m.data)
if err != nil {
return nil, err
}
// It converts any data to List type for inserting.
switch value := newData.(type) {
case Result:
list = value.List()
case Record:
list = List{value.Map()}
case List:
list = value
case Map:
list = List{value}
default:
// It uses gconv.Map here to simply fo the type converting from interface{} to map[string]interface{},
// as there's another MapOrStructToMapDeep in next logic to do the deep converting.
reflectInfo := reflection.OriginValueAndKind(newData)
switch reflectInfo.OriginKind {
// If it's slice type, it then converts it to List type.
case reflect.Slice, reflect.Array:
list = make(List, reflectInfo.OriginValue.Len())
for i := 0; i < reflectInfo.OriginValue.Len(); i++ {
list[i] = anyValueToMapBeforeToRecord(reflectInfo.OriginValue.Index(i).Interface())
}
case reflect.Map:
list = List{anyValueToMapBeforeToRecord(value)}
case reflect.Struct:
if v, ok := value.(iInterfaces); ok {
array := v.Interfaces()
list = make(List, len(array))
for i := 0; i < len(array); i++ {
list[i] = anyValueToMapBeforeToRecord(array[i])
}
} else {
list = List{anyValueToMapBeforeToRecord(value)}
}
default:
return result, gerror.NewCodef(
gcode.CodeInvalidParameter,
"unsupported data list type: %v",
reflectInfo.InputValue.Type(),
)
}
}
if len(list) < 1 {

View File

@ -26,7 +26,11 @@ type iUnixNano interface {
}
// New creates and returns a Time object with given parameter.
// The optional parameter can be type of: time.Time/*time.Time, string or integer.
// The optional parameter is the time object which can be type of: time.Time/*time.Time, string or integer.
// Example:
// New("2024-10-29")
// New(1390876568)
// New(t) // The t is type of time.Time.
func New(param ...interface{}) *Time {
if len(param) > 0 {
switch r := param[0].(type) {