From e1e0b71233fc708e380ec210dc555a26a47918b3 Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 5 Feb 2024 16:44:45 +0800 Subject: [PATCH] fix issue #2594 --- .github/workflows/ci-main.yml | 2 +- contrib/drivers/dm/dm_z_unit_issue_test.go | 73 +++++++ .../drivers/dm/testdata/issue/2594/sql.sql | 10 + database/gdb/gdb_core.go | 2 +- database/gdb/gdb_func.go | 203 ++++++++++-------- database/gdb/gdb_model_insert.go | 41 +--- os/gtime/gtime_time.go | 6 +- 7 files changed, 209 insertions(+), 128 deletions(-) create mode 100644 contrib/drivers/dm/dm_z_unit_issue_test.go create mode 100644 contrib/drivers/dm/testdata/issue/2594/sql.sql diff --git a/.github/workflows/ci-main.yml b/.github/workflows/ci-main.yml index 8afc1585a24..5a3f83ec6bf 100644 --- a/.github/workflows/ci-main.yml +++ b/.github/workflows/ci-main.yml @@ -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: diff --git a/contrib/drivers/dm/dm_z_unit_issue_test.go b/contrib/drivers/dm/dm_z_unit_issue_test.go new file mode 100644 index 00000000000..fece6a6183f --- /dev/null +++ b/contrib/drivers/dm/dm_z_unit_issue_test.go @@ -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) + }) +} diff --git a/contrib/drivers/dm/testdata/issue/2594/sql.sql b/contrib/drivers/dm/testdata/issue/2594/sql.sql new file mode 100644 index 00000000000..e76ff15a0eb --- /dev/null +++ b/contrib/drivers/dm/testdata/issue/2594/sql.sql @@ -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) +); \ No newline at end of file diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 556318d6a4e..2b097fb61e4 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -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) } diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index 4d2f9b41f91..a695171b16d 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -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 - } - - 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 diff --git a/database/gdb/gdb_model_insert.go b/database/gdb/gdb_model_insert.go index bc6b8aa82be..80fe9ac6503 100644 --- a/database/gdb/gdb_model_insert.go +++ b/database/gdb/gdb_model_insert.go @@ -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 { diff --git a/os/gtime/gtime_time.go b/os/gtime/gtime_time.go index ed8254d365c..388c4248f42 100644 --- a/os/gtime/gtime_time.go +++ b/os/gtime/gtime_time.go @@ -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) {