mirror of
https://gitee.com/johng/gf
synced 2026-06-06 16:21:40 +08:00
2
.github/workflows/ci-main.yml
vendored
2
.github/workflows/ci-main.yml
vendored
@ -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:
|
||||
|
||||
73
contrib/drivers/dm/dm_z_unit_issue_test.go
Normal file
73
contrib/drivers/dm/dm_z_unit_issue_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
10
contrib/drivers/dm/testdata/issue/2594/sql.sql
vendored
Normal file
10
contrib/drivers/dm/testdata/issue/2594/sql.sql
vendored
Normal 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)
|
||||
);
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user