From bf3dd7a8900ea9a687367a1c38da93cb77c32ff5 Mon Sep 17 00:00:00 2001 From: oninowang Date: Tue, 28 May 2024 20:11:55 +0800 Subject: [PATCH 1/3] fix: Disable automatic handling for creating/updating time if it has been specified. Fixes #3613 Signed-off-by: oninowang --- database/gdb/gdb_model_insert.go | 7 ++++--- database/gdb/gdb_model_update.go | 14 ++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/database/gdb/gdb_model_insert.go b/database/gdb/gdb_model_insert.go index 3f99f23fb19..798b355ae11 100644 --- a/database/gdb/gdb_model_insert.go +++ b/database/gdb/gdb_model_insert.go @@ -14,6 +14,7 @@ import ( "github.com/gogf/gf/v2/container/gset" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/empty" "github.com/gogf/gf/v2/internal/reflection" "github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/util/gconv" @@ -286,19 +287,19 @@ func (m *Model) doInsertWithOption(ctx context.Context, insertOption InsertOptio // Automatic handling for creating/updating time. if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") { for k, v := range list { - if fieldNameCreate != "" { + if fieldNameCreate != "" && empty.IsNil(v[fieldNameCreate]) { fieldCreateValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeCreate, false) if fieldCreateValue != nil { v[fieldNameCreate] = fieldCreateValue } } - if fieldNameUpdate != "" { + if fieldNameUpdate != "" && empty.IsNil(v[fieldNameUpdate]) { fieldUpdateValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeUpdate, false) if fieldUpdateValue != nil { v[fieldNameUpdate] = fieldUpdateValue } } - if fieldNameDelete != "" { + if fieldNameDelete != "" && empty.IsNil(v[fieldNameDelete]) { fieldDeleteValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeDelete, true) if fieldDeleteValue != nil { v[fieldNameDelete] = fieldDeleteValue diff --git a/database/gdb/gdb_model_update.go b/database/gdb/gdb_model_update.go index 4f2f11cb374..4ffd8403ed7 100644 --- a/database/gdb/gdb_model_update.go +++ b/database/gdb/gdb_model_update.go @@ -11,10 +11,10 @@ import ( "fmt" "reflect" - "github.com/gogf/gf/v2/internal/intlog" - "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/intlog" "github.com/gogf/gf/v2/internal/reflection" "github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/util/gconv" @@ -62,7 +62,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro case reflect.Map, reflect.Struct: var dataMap = anyValueToMapBeforeToRecord(m.data) // Automatically update the record updating time. - if fieldNameUpdate != "" { + if fieldNameUpdate != "" && empty.IsNil(dataMap[fieldNameUpdate]) { dataValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeUpdate, false) dataMap[fieldNameUpdate] = dataValue } @@ -71,12 +71,10 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro default: updates := gconv.String(m.data) // Automatically update the record updating time. - if fieldNameUpdate != "" { + if fieldNameUpdate != "" && !gstr.Contains(updates, fieldNameUpdate) { dataValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeUpdate, false) - if fieldNameUpdate != "" && !gstr.Contains(updates, fieldNameUpdate) { - updates += fmt.Sprintf(`,%s=?`, fieldNameUpdate) - conditionArgs = append([]interface{}{dataValue}, conditionArgs...) - } + updates += fmt.Sprintf(`,%s=?`, fieldNameUpdate) + conditionArgs = append([]interface{}{dataValue}, conditionArgs...) } updateData = updates } From d2ce5e7fdcc132eead2a8a9e5106021117f3db9f Mon Sep 17 00:00:00 2001 From: oninowang Date: Fri, 31 May 2024 10:28:59 +0800 Subject: [PATCH 2/3] fix: add tests. Fixes #3613 Signed-off-by: oninowang --- .../mysql_z_unit_feature_soft_time_test.go | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/contrib/drivers/mysql/mysql_z_unit_feature_soft_time_test.go b/contrib/drivers/mysql/mysql_z_unit_feature_soft_time_test.go index 10f12918bcd..cc5ac4f4db8 100644 --- a/contrib/drivers/mysql/mysql_z_unit_feature_soft_time_test.go +++ b/contrib/drivers/mysql/mysql_z_unit_feature_soft_time_test.go @@ -1267,3 +1267,133 @@ CREATE TABLE %s ( t.Assert(one["delete_at"].Int64(), 1) }) } + +func Test_SoftTime_CreateUpdateDelete_Specified(t *testing.T) { + table := "soft_time_test_table_" + gtime.TimestampNanoStr() + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id int(11) NOT NULL, + name varchar(45) DEFAULT NULL, + create_at datetime(0) DEFAULT NULL, + update_at datetime(0) DEFAULT NULL, + delete_at datetime(0) DEFAULT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + // Insert + dataInsert := g.Map{ + "id": 1, + "name": "name_1", + "create_at": gtime.NewFromStr("2024-05-30 20:00:00"), + "update_at": gtime.NewFromStr("2024-05-30 20:00:00"), + } + r, err := db.Model(table).Data(dataInsert).Insert() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + + oneInsert, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(oneInsert["id"].Int(), 1) + t.Assert(oneInsert["name"].String(), "name_1") + t.Assert(oneInsert["delete_at"].String(), "") + t.Assert(oneInsert["create_at"].String(), "2024-05-30 20:00:00") + t.Assert(oneInsert["update_at"].String(), "2024-05-30 20:00:00") + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Save + dataSave := g.Map{ + "id": 1, + "name": "name_10", + "update_at": gtime.NewFromStr("2024-05-30 20:15:00"), + } + r, err = db.Model(table).Data(dataSave).Save() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 2) + + oneSave, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(oneSave["id"].Int(), 1) + t.Assert(oneSave["name"].String(), "name_10") + t.Assert(oneSave["delete_at"].String(), "") + t.Assert(oneSave["create_at"].String(), "2024-05-30 20:00:00") + t.Assert(oneSave["update_at"].String(), "2024-05-30 20:15:00") + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Update + dataUpdate := g.Map{ + "name": "name_1000", + "update_at": gtime.NewFromStr("2024-05-30 20:30:00"), + } + r, err = db.Model(table).Data(dataUpdate).WherePri(1).Update() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + oneUpdate, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(oneUpdate["id"].Int(), 1) + t.Assert(oneUpdate["name"].String(), "name_1000") + t.Assert(oneUpdate["delete_at"].String(), "") + t.Assert(oneUpdate["create_at"].String(), "2024-05-30 20:00:00") + t.Assert(oneUpdate["update_at"].String(), "2024-05-30 20:30:00") + + // Replace + dataReplace := g.Map{ + "id": 1, + "name": "name_100", + "create_at": gtime.NewFromStr("2024-05-30 21:00:00"), + "update_at": gtime.NewFromStr("2024-05-30 21:00:00"), + } + r, err = db.Model(table).Data(dataReplace).Replace() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 2) + + oneReplace, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(oneReplace["id"].Int(), 1) + t.Assert(oneReplace["name"].String(), "name_100") + t.Assert(oneReplace["delete_at"].String(), "") + t.Assert(oneReplace["create_at"].String(), "2024-05-30 21:00:00") + t.Assert(oneReplace["update_at"].String(), "2024-05-30 21:00:00") + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Insert with delete_at + dataInsertDelete := g.Map{ + "id": 2, + "name": "name_2", + "create_at": gtime.NewFromStr("2024-05-30 20:00:00"), + "update_at": gtime.NewFromStr("2024-05-30 20:00:00"), + "delete_at": gtime.NewFromStr("2024-05-30 20:00:00"), + } + r, err = db.Model(table).Data(dataInsertDelete).Insert() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + // Delete Select + oneDelete, err := db.Model(table).WherePri(2).One() + t.AssertNil(err) + t.Assert(len(oneDelete), 0) + oneDeleteUnscoped, err := db.Model(table).Unscoped().WherePri(2).One() + t.AssertNil(err) + t.Assert(oneDeleteUnscoped["id"].Int(), 2) + t.Assert(oneDeleteUnscoped["name"].String(), "name_2") + t.Assert(oneDeleteUnscoped["delete_at"].String(), "2024-05-30 20:00:00") + t.Assert(oneDeleteUnscoped["create_at"].String(), "2024-05-30 20:00:00") + t.Assert(oneDeleteUnscoped["update_at"].String(), "2024-05-30 20:00:00") + }) +} From cd356c5526d5b6f4c15043d4349e47c38a24c319 Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 4 Jun 2024 21:44:31 +0800 Subject: [PATCH 3/3] up --- .../nosql/redis/redis_z_group_generic_test.go | 2 +- database/gdb/gdb_model_update.go | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/contrib/nosql/redis/redis_z_group_generic_test.go b/contrib/nosql/redis/redis_z_group_generic_test.go index 24775372ba1..b4bc4bce15e 100644 --- a/contrib/nosql/redis/redis_z_group_generic_test.go +++ b/contrib/nosql/redis/redis_z_group_generic_test.go @@ -434,7 +434,7 @@ func Test_GroupGeneric_ExpireAt(t *testing.T) { result, err = redis.GroupGeneric().ExpireAt(ctx, TestKey, time.Now().Add(time.Millisecond*100)) t.AssertNil(err) t.AssertEQ(result, int64(1)) - time.Sleep(time.Millisecond * 100) + time.Sleep(time.Millisecond * 200) result, err = redis.GroupGeneric().Exists(ctx, TestKey) t.AssertNil(err) t.AssertEQ(result, int64(0)) diff --git a/database/gdb/gdb_model_update.go b/database/gdb/gdb_model_update.go index 4ffd8403ed7..2d14e558db8 100644 --- a/database/gdb/gdb_model_update.go +++ b/database/gdb/gdb_model_update.go @@ -45,9 +45,9 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro return nil, gerror.NewCode(gcode.CodeMissingParameter, "updating table with empty data") } var ( + newData interface{} stm = m.softTimeMaintainer() - updateData = m.data - reflectInfo = reflection.OriginTypeAndKind(updateData) + reflectInfo = reflection.OriginTypeAndKind(m.data) conditionWhere, conditionExtra, conditionArgs = m.formatCondition(ctx, false, false) conditionStr = conditionWhere + conditionExtra fieldNameUpdate, fieldTypeUpdate = stm.GetFieldNameAndTypeForUpdate( @@ -58,29 +58,30 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro fieldNameUpdate = "" } + newData, err = m.filterDataForInsertOrUpdate(m.data) + if err != nil { + return nil, err + } + switch reflectInfo.OriginKind { case reflect.Map, reflect.Struct: - var dataMap = anyValueToMapBeforeToRecord(m.data) + var dataMap = anyValueToMapBeforeToRecord(newData) // Automatically update the record updating time. if fieldNameUpdate != "" && empty.IsNil(dataMap[fieldNameUpdate]) { dataValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeUpdate, false) dataMap[fieldNameUpdate] = dataValue } - updateData = dataMap + newData = dataMap default: - updates := gconv.String(m.data) + var updateStr = gconv.String(newData) // Automatically update the record updating time. - if fieldNameUpdate != "" && !gstr.Contains(updates, fieldNameUpdate) { + if fieldNameUpdate != "" && !gstr.Contains(updateStr, fieldNameUpdate) { dataValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeUpdate, false) - updates += fmt.Sprintf(`,%s=?`, fieldNameUpdate) + updateStr += fmt.Sprintf(`,%s=?`, fieldNameUpdate) conditionArgs = append([]interface{}{dataValue}, conditionArgs...) } - updateData = updates - } - newData, err := m.filterDataForInsertOrUpdate(updateData) - if err != nil { - return nil, err + newData = updateStr } if !gstr.ContainsI(conditionStr, " WHERE ") {