diff --git a/contrib/drivers/README.MD b/contrib/drivers/README.MD index ef3bd361c..c10d58d66 100644 --- a/contrib/drivers/README.MD +++ b/contrib/drivers/README.MD @@ -1,4 +1,3 @@ -English | [简体中文](README.zh_CN.MD) # Database drivers @@ -44,7 +43,7 @@ func main() { ## Supported Drivers -### MySQL/MariaDB/TiDB +### MySQL/MariaDB/TiDB/OceanBase ```go import _ "github.com/gogf/gf/contrib/drivers/mysql/v2" @@ -116,10 +115,6 @@ Note: import _ "github.com/gogf/gf/contrib/drivers/dm/v2" ``` -Note: - -- It does not support `Replace` features. - ## Custom Drivers It's quick and easy, please refer to current driver source. diff --git a/contrib/drivers/README.zh_CN.MD b/contrib/drivers/README.zh_CN.MD deleted file mode 100644 index 0d5b1d214..000000000 --- a/contrib/drivers/README.zh_CN.MD +++ /dev/null @@ -1,126 +0,0 @@ -[English](README.MD) | 简体中文 - -# 数据库驱动程序 - -用于gdb包的数据库驱动程序。 - -## 安装 - -以 `mysql` 为例。 - -```shell -go get github.com/gogf/gf/contrib/drivers/mysql/v2@latest -# 方便复制 -go get github.com/gogf/gf/contrib/drivers/clickhouse/v2@latest -go get github.com/gogf/gf/contrib/drivers/dm/v2@latest -go get github.com/gogf/gf/contrib/drivers/mssql/v2@latest -go get github.com/gogf/gf/contrib/drivers/oracle/v2@latest -go get github.com/gogf/gf/contrib/drivers/pgsql/v2@latest -go get github.com/gogf/gf/contrib/drivers/sqlite/v2@latest -go get github.com/gogf/gf/contrib/drivers/sqlitecgo/v2@latest -``` - -选择并将驱动程序导入到您的项目中: - -```go -import _ "github.com/gogf/gf/contrib/drivers/mysql/v2" -``` - -通常在 `main.go` 的顶部导入: - -```go -package main - -import ( - _ "github.com/gogf/gf/contrib/drivers/mysql/v2" - - // 其他导入的包。 -) - -func main() { - // 主要逻辑。 -} -``` - -## 支持的驱动程序 - -### MySQL/MariaDB/TiDB - -```go -import _ "github.com/gogf/gf/contrib/drivers/mysql/v2" -``` - -### SQLite - -```go -import _ "github.com/gogf/gf/contrib/drivers/sqlite/v2" -``` - -#### cgo 版本 - -32位Windows请使用cgo版本 - -```go -import _ "github.com/gogf/gf/contrib/drivers/sqlitecgo/v2" -``` - -### PostgreSQL - -```go -import _ "github.com/gogf/gf/contrib/drivers/pgsql/v2" -``` - -注意: - -- 不支持 `Replace` 功能。 - -### SQL Server - -```go -import _ "github.com/gogf/gf/contrib/drivers/mssql/v2" -``` - -注意: - -- 不支持 `Replace` 功能。 -- 仅支持服务器版本 >= `SQL Server2005` -- 仅支持 datetime2 和 datetimeoffset 类型来自动处理 created_at/updated_at/deleted_at 列,因为 datetime 类型在将列值作为字符串传递时不支持微秒精度。 - -### Oracle - -```go -import _ "github.com/gogf/gf/contrib/drivers/oracle/v2" -``` - -注意: - -- 不支持 `Replace` 功能。 -- 不支持 `LastInsertId`。 - -### ClickHouse - -```go -import _ "github.com/gogf/gf/contrib/drivers/clickhouse/v2" -``` - -注意: - -- 不支持 `InsertIgnore/InsertGetId` 功能。 -- 不支持 `Save/Replace` 功能。 -- 不支持 `Transaction` 功能。 -- 不支持 `RowsAffected` 功能。 - -### DM - -```go -import _ "github.com/gogf/gf/contrib/drivers/dm/v2" -``` - -注意: - -- 不支持 `Replace` 功能。 - -## 自定义驱动程序 - -自定义驱动程序非常快速和简单,您可以参考当前驱动程序的源代码来进行开发。 -如果您有关于支持新驱动程序的PR(Pull Request),我们将非常感激地接受您的提交到当前仓库。 \ No newline at end of file diff --git a/contrib/drivers/dm/dm.go b/contrib/drivers/dm/dm.go index 3bfb01cb9..6b45a51de 100644 --- a/contrib/drivers/dm/dm.go +++ b/contrib/drivers/dm/dm.go @@ -14,6 +14,7 @@ import ( "github.com/gogf/gf/v2/frame/g" ) +// Driver is the driver for dm database. type Driver struct { *gdb.Core } diff --git a/contrib/drivers/dm/dm_do_insert.go b/contrib/drivers/dm/dm_do_insert.go index 1218ab900..2606167f2 100644 --- a/contrib/drivers/dm/dm_do_insert.go +++ b/contrib/drivers/dm/dm_do_insert.go @@ -28,28 +28,70 @@ func (d *Driver) DoInsert( return d.doSave(ctx, link, table, list, option) case gdb.InsertOptionReplace: - // TODO:: Should be Supported - return nil, gerror.NewCode( - gcode.CodeNotSupported, `Replace operation is not supported by dm driver`, - ) - } + // dm does not support REPLACE INTO syntax, use SAVE instead. + return d.doSave(ctx, link, table, list, option) - return d.Core.DoInsert(ctx, link, table, list, option) + case gdb.InsertOptionIgnore: + // dm does not support INSERT IGNORE syntax, use MERGE instead. + return d.doInsertIgnore(ctx, link, table, list, option) + + default: + return d.Core.DoInsert(ctx, link, table, list, option) + } } // doSave support upsert for dm func (d *Driver) doSave(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption, ) (result sql.Result, err error) { - if len(option.OnConflict) == 0 { - return nil, gerror.NewCode( - gcode.CodeMissingParameter, `Please specify conflict columns`, - ) + return d.doMergeInsert(ctx, link, table, list, option, true) +} + +// doInsertIgnore implements INSERT IGNORE operation using MERGE statement for DM database. +// It only inserts records when there's no conflict on primary/unique keys. +func (d *Driver) doInsertIgnore(ctx context.Context, + link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption, +) (result sql.Result, err error) { + return d.doMergeInsert(ctx, link, table, list, option, false) +} + +// doMergeInsert implements MERGE-based insert operations for DM database. +// When withUpdate is true, it performs upsert (insert or update). +// When withUpdate is false, it performs insert ignore (insert only when no conflict). +func (d *Driver) doMergeInsert( + ctx context.Context, + link gdb.Link, + table string, + list gdb.List, + option gdb.DoInsertOption, + withUpdate bool, +) (result sql.Result, err error) { + // If OnConflict is not specified, automatically get the primary key of the table + conflictKeys := option.OnConflict + if len(conflictKeys) == 0 { + conflictKeys, err = d.getPrimaryKeys(ctx, table) + if err != nil { + return nil, gerror.WrapCode( + gcode.CodeInternalError, + err, + `failed to get primary keys for table`, + ) + } + if len(conflictKeys) == 0 { + return nil, gerror.NewCode( + gcode.CodeMissingParameter, + `Please specify conflict columns or ensure the table has a primary key`, + ) + } } if len(list) == 0 { - return nil, gerror.NewCode( - gcode.CodeInvalidRequest, `Save operation list is empty by oracle driver`, + opName := "Save" + if !withUpdate { + opName = "InsertIgnore" + } + return nil, gerror.NewCodef( + gcode.CodeInvalidRequest, `%s operation list is empty by dm driver`, opName, ) } @@ -58,14 +100,13 @@ func (d *Driver) doSave(ctx context.Context, oneLen = len(one) charL, charR = d.GetChars() - conflictKeys = option.OnConflict conflictKeySet = gset.New(false) - // queryHolders: Handle data with Holder that need to be upsert - // queryValues: Handle data that need to be upsert + // queryHolders: Handle data with Holder that need to be merged + // queryValues: Handle data that need to be merged // insertKeys: Handle valid keys that need to be inserted // insertValues: Handle values that need to be inserted - // updateValues: Handle values that need to be updated + // updateValues: Handle values that need to be updated (only when withUpdate=true) queryHolders = make([]string, oneLen) queryValues = make([]any, oneLen) insertKeys = make([]string, oneLen) @@ -86,9 +127,9 @@ func (d *Driver) doSave(ctx context.Context, insertKeys[index] = keyWithChar insertValues[index] = fmt.Sprintf("T2.%s", keyWithChar) - // filter conflict keys in updateValues. - // And the key is not a soft created field. - if !(conflictKeySet.Contains(key) || d.Core.IsSoftCreatedFieldName(key)) { + // Build updateValues only when withUpdate is true + // Filter conflict keys and soft created fields from updateValues + if withUpdate && !(conflictKeySet.Contains(key) || d.Core.IsSoftCreatedFieldName(key)) { updateValues = append( updateValues, fmt.Sprintf(`T1.%s = T2.%s`, keyWithChar, keyWithChar), @@ -97,8 +138,10 @@ func (d *Driver) doSave(ctx context.Context, index++ } - batchResult := new(gdb.SqlResult) - sqlStr := parseSqlForUpsert(table, queryHolders, insertKeys, insertValues, updateValues, conflictKeys) + var ( + batchResult = new(gdb.SqlResult) + sqlStr = parseSqlForMerge(table, queryHolders, insertKeys, insertValues, updateValues, conflictKeys) + ) r, err := d.DoExec(ctx, link, sqlStr, queryValues...) if err != nil { return r, err @@ -112,40 +155,59 @@ func (d *Driver) doSave(ctx context.Context, return batchResult, nil } -// parseSqlForUpsert -// MERGE INTO {{table}} T1 -// USING ( SELECT {{queryHolders}} FROM DUAL T2 -// ON (T1.{{duplicateKey}} = T2.{{duplicateKey}} AND ...) -// WHEN NOT MATCHED THEN -// INSERT {{insertKeys}} VALUES {{insertValues}} -// WHEN MATCHED THEN -// UPDATE SET {{updateValues}} -func parseSqlForUpsert(table string, +// getPrimaryKeys retrieves the primary key field list of the table. +// This method extracts primary key information from TableFields. +func (d *Driver) getPrimaryKeys(ctx context.Context, table string) ([]string, error) { + tableFields, err := d.TableFields(ctx, table) + if err != nil { + return nil, err + } + + var primaryKeys []string + for _, field := range tableFields { + if field.Key == "PRI" { + primaryKeys = append(primaryKeys, field.Name) + } + } + + return primaryKeys, nil +} + +// parseSqlForMerge generates MERGE statement for DM database. +// When updateValues is empty, it only inserts (INSERT IGNORE behavior). +// When updateValues is provided, it performs upsert (INSERT or UPDATE). +// Examples: +// - INSERT IGNORE: MERGE INTO table T1 USING (...) T2 ON (...) WHEN NOT MATCHED THEN INSERT(...) VALUES (...) +// - UPSERT: MERGE INTO table T1 USING (...) T2 ON (...) WHEN NOT MATCHED THEN INSERT(...) VALUES (...) WHEN MATCHED THEN UPDATE SET ... +func parseSqlForMerge(table string, queryHolders, insertKeys, insertValues, updateValues, duplicateKey []string, ) (sqlStr string) { var ( queryHolderStr = strings.Join(queryHolders, ",") insertKeyStr = strings.Join(insertKeys, ",") insertValueStr = strings.Join(insertValues, ",") - updateValueStr = strings.Join(updateValues, ",") duplicateKeyStr string - pattern = gstr.Trim(`MERGE INTO %s T1 USING (SELECT %s FROM DUAL) T2 ON (%s) WHEN NOT MATCHED THEN INSERT(%s) VALUES (%s) WHEN MATCHED THEN UPDATE SET %s;`) ) + // Build ON condition for index, keys := range duplicateKey { if index != 0 { duplicateKeyStr += " AND " } - duplicateTmp := fmt.Sprintf("T1.%s = T2.%s", keys, keys) - duplicateKeyStr += duplicateTmp + duplicateKeyStr += fmt.Sprintf("T1.%s = T2.%s", keys, keys) } - return fmt.Sprintf(pattern, - table, - queryHolderStr, - duplicateKeyStr, - insertKeyStr, - insertValueStr, - updateValueStr, - ) + // Build SQL based on whether UPDATE is needed + pattern := gstr.Trim(`MERGE INTO %s T1 USING (SELECT %s FROM DUAL) T2 ON (%s) WHEN NOT MATCHED THEN INSERT(%s) VALUES (%s)`) + if len(updateValues) > 0 { + // Upsert: INSERT or UPDATE + pattern += gstr.Trim(`WHEN MATCHED THEN UPDATE SET %s`) + return fmt.Sprintf( + pattern, table, queryHolderStr, duplicateKeyStr, insertKeyStr, insertValueStr, + strings.Join(updateValues, ","), + ) + } else { + // Insert Ignore: INSERT only + return fmt.Sprintf(pattern, table, queryHolderStr, duplicateKeyStr, insertKeyStr, insertValueStr) + } } diff --git a/contrib/drivers/dm/dm_table_fields.go b/contrib/drivers/dm/dm_table_fields.go index 9dc3c6c8c..8999616ae 100644 --- a/contrib/drivers/dm/dm_table_fields.go +++ b/contrib/drivers/dm/dm_table_fields.go @@ -23,7 +23,7 @@ func escapeSingleQuote(s string) string { } const ( - tableFieldsSqlTmp = `SELECT c.COLUMN_NAME, c.DATA_TYPE, c.DATA_DEFAULT, c.NULLABLE, cc.COMMENTS FROM ALL_TAB_COLUMNS c LEFT JOIN ALL_COL_COMMENTS cc ON c.COLUMN_NAME = cc.COLUMN_NAME AND c.TABLE_NAME = cc.TABLE_NAME AND c.OWNER = cc.OWNER WHERE c.TABLE_NAME = '%s' AND c.OWNER = '%s'` + tableFieldsSqlTmp = `SELECT c.COLUMN_NAME, c.DATA_TYPE, c.DATA_LENGTH, c.DATA_PRECISION, c.DATA_SCALE, c.DATA_DEFAULT, c.NULLABLE, cc.COMMENTS FROM ALL_TAB_COLUMNS c LEFT JOIN ALL_COL_COMMENTS cc ON c.COLUMN_NAME = cc.COLUMN_NAME AND c.TABLE_NAME = cc.TABLE_NAME AND c.OWNER = cc.OWNER WHERE c.TABLE_NAME = '%s' AND c.OWNER = '%s'` tableFieldsPkSqlSchemaTmp = `SELECT COLS.COLUMN_NAME AS PRIMARY_KEY_COLUMN FROM USER_CONSTRAINTS CONS JOIN USER_CONS_COLUMNS COLS ON CONS.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME WHERE CONS.TABLE_NAME = '%s' AND CONS.CONSTRAINT_TYPE = 'P'` tableFieldsPkSqlDBATmp = `SELECT COLS.COLUMN_NAME AS PRIMARY_KEY_COLUMN FROM DBA_CONSTRAINTS CONS JOIN DBA_CONS_COLUMNS COLS ON CONS.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME WHERE CONS.TABLE_NAME = '%s' AND CONS.OWNER = '%s' AND CONS.CONSTRAINT_TYPE = 'P'` ) @@ -85,10 +85,24 @@ func (d *Driver) TableFields( if m["NULLABLE"].String() != "N" { nullable = true } + + // Build field type with length/precision + // For NUMBER(p,s): use DATA_PRECISION and DATA_SCALE + // For VARCHAR2/CHAR: use DATA_LENGTH + var ( + fieldType string + dataType = m["DATA_TYPE"].String() + dataLength = m["DATA_LENGTH"].Int() + ) + if dataLength > 0 { + fieldType = fmt.Sprintf("%s(%d)", dataType, dataLength) + } else { + fieldType = dataType + } fields[m["COLUMN_NAME"].String()] = &gdb.TableField{ Index: i, Name: m["COLUMN_NAME"].String(), - Type: m["DATA_TYPE"].String(), + Type: fieldType, Null: nullable, Default: m["DATA_DEFAULT"].Val(), Key: pkFields.Get(m["COLUMN_NAME"].String()), diff --git a/contrib/drivers/dm/dm_z_unit_basic_test.go b/contrib/drivers/dm/dm_z_unit_basic_test.go index aeb83a70a..69a6005ba 100644 --- a/contrib/drivers/dm/dm_z_unit_basic_test.go +++ b/contrib/drivers/dm/dm_z_unit_basic_test.go @@ -80,12 +80,12 @@ func TestTableFields(t *testing.T) { createInitTable(tables) gtest.C(t, func(t *gtest.T) { var expect = map[string][]any{ - "ID": {"BIGINT", false}, - "ACCOUNT_NAME": {"VARCHAR", false}, - "PWD_RESET": {"TINYINT", false}, - "ATTR_INDEX": {"INT", true}, - "DELETED": {"INT", false}, - "CREATED_TIME": {"TIMESTAMP", false}, + "ID": {"BIGINT(8)", false}, + "ACCOUNT_NAME": {"VARCHAR(128)", false}, + "PWD_RESET": {"TINYINT(1)", false}, + "ATTR_INDEX": {"INT(4)", true}, + "DELETED": {"INT(4)", false}, + "CREATED_TIME": {"TIMESTAMP(8)", false}, } res, err := db.TableFields(ctx, tables) @@ -140,109 +140,6 @@ func Test_DB_Query(t *testing.T) { }) } -func TestModelSave(t *testing.T) { - table := createTable() - defer dropTable(table) - gtest.C(t, func(t *gtest.T) { - type User struct { - Id int - AccountName string - AttrIndex int - } - var ( - user User - count int - result sql.Result - err error - ) - - result, err = db.Model(table).Data(g.Map{ - "id": 1, - "accountName": "ac1", - "attrIndex": 100, - }).OnConflict("id").Save() - - t.AssertNil(err) - n, _ := result.RowsAffected() - t.Assert(n, 1) - - err = db.Model(table).Scan(&user) - t.AssertNil(err) - t.Assert(user.Id, 1) - t.Assert(user.AccountName, "ac1") - t.Assert(user.AttrIndex, 100) - - _, err = db.Model(table).Data(g.Map{ - "id": 1, - "accountName": "ac2", - "attrIndex": 200, - }).OnConflict("id").Save() - t.AssertNil(err) - - err = db.Model(table).Scan(&user) - t.AssertNil(err) - t.Assert(user.AccountName, "ac2") - t.Assert(user.AttrIndex, 200) - - count, err = db.Model(table).Count() - t.AssertNil(err) - t.Assert(count, 1) - }) -} - -func TestModelInsert(t *testing.T) { - // g.Model.insert not lost default not null coloumn - table := "A_tables" - createInitTable(table) - gtest.C(t, func(t *gtest.T) { - i := 200 - data := User{ - ID: int64(i), - AccountName: fmt.Sprintf(`A%dtwo`, i), - PwdReset: 0, - AttrIndex: 99, - CreatedTime: time.Now(), - UpdatedTime: time.Now(), - } - // _, err := db.Schema(TestDBName).Model(table).Data(data).Insert() - _, err := db.Model(table).Insert(&data) - gtest.AssertNil(err) - }) - - gtest.C(t, func(t *gtest.T) { - i := 201 - data := User{ - ID: int64(i), - AccountName: fmt.Sprintf(`A%dtwoONE`, i), - PwdReset: 1, - CreatedTime: time.Now(), - AttrIndex: 98, - UpdatedTime: time.Now(), - } - // _, err := db.Schema(TestDBName).Model(table).Data(data).Insert() - _, err := db.Model(table).Data(&data).Insert() - gtest.AssertNil(err) - }) -} - -func TestDBInsert(t *testing.T) { - table := "A_tables" - createInitTable("A_tables") - gtest.C(t, func(t *gtest.T) { - i := 300 - data := g.Map{ - "ID": i, - "ACCOUNT_NAME": fmt.Sprintf(`A%dthress`, i), - "PWD_RESET": 3, - "ATTR_INDEX": 98, - "CREATED_TIME": gtime.Now(), - "UPDATED_TIME": gtime.Now(), - } - _, err := db.Insert(ctx, table, &data) - gtest.AssertNil(err) - }) -} - func Test_DB_Exec(t *testing.T) { createInitTable("A_tables") gtest.C(t, func(t *gtest.T) { @@ -612,3 +509,124 @@ func Test_Empty_Slice_Argument(t *testing.T) { t.Assert(len(result), 0) }) } + +func TestModelSave(t *testing.T) { + table := createTable() + defer dropTable(table) + gtest.C(t, func(t *gtest.T) { + type User struct { + Id int + AccountName string + AttrIndex int + } + var ( + user User + count int + result sql.Result + err error + ) + + result, err = db.Model(table).Data(g.Map{ + "id": 1, + "accountName": "ac1", + "attrIndex": 100, + }).OnConflict("id").Save() + + t.AssertNil(err) + n, _ := result.RowsAffected() + t.Assert(n, 1) + + err = db.Model(table).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 1) + t.Assert(user.AccountName, "ac1") + t.Assert(user.AttrIndex, 100) + + _, err = db.Model(table).Data(g.Map{ + "id": 1, + "accountName": "ac2", + "attrIndex": 200, + }).OnConflict("id").Save() + t.AssertNil(err) + + err = db.Model(table).Scan(&user) + t.AssertNil(err) + t.Assert(user.AccountName, "ac2") + t.Assert(user.AttrIndex, 200) + + count, err = db.Model(table).Count() + t.AssertNil(err) + t.Assert(count, 1) + }) +} + +func TestModelInsert(t *testing.T) { + // g.Model.insert not lost default not null coloumn + table := "A_tables" + createInitTable(table) + gtest.C(t, func(t *gtest.T) { + i := 200 + data := User{ + ID: int64(i), + AccountName: fmt.Sprintf(`A%dtwo`, i), + PwdReset: 0, + AttrIndex: 99, + CreatedTime: time.Now(), + UpdatedTime: time.Now(), + } + // _, err := db.Schema(TestDBName).Model(table).Data(data).Insert() + _, err := db.Model(table).Insert(&data) + gtest.AssertNil(err) + }) + + gtest.C(t, func(t *gtest.T) { + i := 201 + data := User{ + ID: int64(i), + AccountName: fmt.Sprintf(`A%dtwoONE`, i), + PwdReset: 1, + CreatedTime: time.Now(), + AttrIndex: 98, + UpdatedTime: time.Now(), + } + // _, err := db.Schema(TestDBName).Model(table).Data(data).Insert() + _, err := db.Model(table).Data(&data).Insert() + gtest.AssertNil(err) + }) +} + +func Test_Model_InsertIgnore(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + // db.SetDebug(true) + + gtest.C(t, func(t *gtest.T) { + data := User{ + ID: int64(666), + AccountName: fmt.Sprintf(`name_%d`, 666), + PwdReset: 0, + AttrIndex: 99, + CreatedTime: time.Now(), + UpdatedTime: time.Now(), + } + _, err := db.Model(table).Data(data).Insert() + t.AssertNil(err) + }) + gtest.C(t, func(t *gtest.T) { + data := User{ + ID: int64(666), + AccountName: fmt.Sprintf(`name_%d`, 777), + PwdReset: 0, + AttrIndex: 99, + CreatedTime: time.Now(), + UpdatedTime: time.Now(), + } + _, err := db.Model(table).Data(data).InsertIgnore() + t.AssertNil(err) + + one, err := db.Model(table).Where("id", 666).One() + t.AssertNil(err) + t.Assert(one["ACCOUNT_NAME"].String(), "name_666") + }) +} diff --git a/contrib/drivers/dm/dm_z_unit_feature_soft_time_test.go b/contrib/drivers/dm/dm_z_unit_feature_soft_time_test.go new file mode 100644 index 000000000..d66630f7f --- /dev/null +++ b/contrib/drivers/dm/dm_z_unit_feature_soft_time_test.go @@ -0,0 +1,1400 @@ +// Copyright GoFrame Author(https://goframe.org). 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 ( + "fmt" + "testing" + "time" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/test/gtest" +) + +// CreateAt/UpdateAt/DeleteAt. +func Test_SoftTime_CreateUpdateDelete1(t *testing.T) { + table := "soft_time_test_table_" + gtime.TimestampNanoStr() + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id INT NOT NULL, + name VARCHAR(45) DEFAULT NULL, + create_at TIMESTAMP(6) DEFAULT NULL, + update_at TIMESTAMP(6) DEFAULT NULL, + delete_at TIMESTAMP(6) DEFAULT NULL, + PRIMARY KEY (id) +); + `, 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", + } + 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.AssertGE(oneInsert["CREATE_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + t.AssertGE(oneInsert["UPDATE_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Save + dataSave := g.Map{ + "id": 1, + "name": "name_10", + } + r, err = db.Model(table).Data(dataSave).Save() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + 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"].GTime().Timestamp(), oneInsert["CREATE_AT"].GTime().Timestamp()) + t.AssertNE(oneSave["UPDATE_AT"].GTime().Timestamp(), oneInsert["UPDATE_AT"].GTime().Timestamp()) + t.AssertGE(oneSave["UPDATE_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Update + dataUpdate := g.Map{ + "name": "name_1000", + } + 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"].GTime().Timestamp(), oneInsert["CREATE_AT"].GTime().Timestamp()) + t.AssertGE(oneUpdate["UPDATE_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + + // Replace + dataReplace := g.Map{ + "id": 1, + "name": "name_100", + } + r, err = db.Model(table).Data(dataReplace).Replace() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + 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.AssertGE(oneReplace["CREATE_AT"].GTime().Timestamp(), oneInsert["CREATE_AT"].GTime().Timestamp()) + t.AssertGE(oneReplace["UPDATE_AT"].GTime().Timestamp(), oneInsert["UPDATE_AT"].GTime().Timestamp()) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Delete + r, err = db.Model(table).Delete("id", 1) + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + // Delete Select + one4, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(len(one4), 0) + one5, err := db.Model(table).Unscoped().WherePri(1).One() + t.AssertNil(err) + t.Assert(one5["ID"].Int(), 1) + t.AssertGE(one5["DELETE_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + // Delete Count + i, err := db.Model(table).Count() + t.AssertNil(err) + t.Assert(i, 0) + i, err = db.Model(table).Unscoped().Count() + t.AssertNil(err) + t.Assert(i, 1) + + // Delete Unscoped + r, err = db.Model(table).Unscoped().Delete("id", 1) + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + one6, err := db.Model(table).Unscoped().WherePri(1).One() + t.AssertNil(err) + t.Assert(len(one6), 0) + i, err = db.Model(table).Unscoped().Count() + t.AssertNil(err) + t.Assert(i, 0) + }) +} + +// CreateAt/UpdateAt/DeleteAt. +func Test_SoftTime_CreateUpdateDelete2(t *testing.T) { + table := "soft_time_test_table_" + gtime.TimestampNanoStr() + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id INT NOT NULL, + name VARCHAR(45) DEFAULT NULL, + create_at TIMESTAMP(0) DEFAULT NULL, + update_at TIMESTAMP(0) DEFAULT NULL, + delete_at TIMESTAMP(0) DEFAULT NULL, + PRIMARY KEY (id) +); + `, 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", + } + 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.AssertGE(oneInsert["CREATE_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + t.AssertGE(oneInsert["UPDATE_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Save + dataSave := g.Map{ + "id": 1, + "name": "name_10", + } + r, err = db.Model(table).Data(dataSave).Save() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + 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"].GTime().Timestamp(), oneInsert["CREATE_AT"].GTime().Timestamp()) + t.AssertNE(oneSave["UPDATE_AT"].GTime().Timestamp(), oneInsert["UPDATE_AT"].GTime().Timestamp()) + t.AssertGE(oneSave["UPDATE_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Update + dataUpdate := g.Map{ + "name": "name_1000", + } + 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"].GTime().Timestamp(), oneInsert["CREATE_AT"].GTime().Timestamp()) + t.AssertGE(oneUpdate["UPDATE_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + + // Replace + dataReplace := g.Map{ + "id": 1, + "name": "name_100", + } + r, err = db.Model(table).Data(dataReplace).Replace() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + 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.AssertGE(oneReplace["CREATE_AT"].GTime().Timestamp(), oneInsert["CREATE_AT"].GTime().Timestamp()) + t.AssertGE(oneReplace["UPDATE_AT"].GTime().Timestamp(), oneInsert["UPDATE_AT"].GTime().Timestamp()) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Delete + r, err = db.Model(table).Delete("id", 1) + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + // Delete Select + one4, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(len(one4), 0) + one5, err := db.Model(table).Unscoped().WherePri(1).One() + t.AssertNil(err) + t.Assert(one5["ID"].Int(), 1) + t.AssertGE(one5["DELETE_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + // Delete Count + i, err := db.Model(table).Count() + t.AssertNil(err) + t.Assert(i, 0) + i, err = db.Model(table).Unscoped().Count() + t.AssertNil(err) + t.Assert(i, 1) + + // Delete Unscoped + r, err = db.Model(table).Unscoped().Delete("id", 1) + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + one6, err := db.Model(table).Unscoped().WherePri(1).One() + t.AssertNil(err) + t.Assert(len(one6), 0) + i, err = db.Model(table).Unscoped().Count() + t.AssertNil(err) + t.Assert(i, 0) + }) +} + +// CreatedAt/UpdatedAt/DeletedAt. +func Test_SoftTime_CreatedUpdatedDeleted_Map(t *testing.T) { + table := "soft_time_test_table_" + gtime.TimestampNanoStr() + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id INT NOT NULL, + name VARCHAR(45) DEFAULT NULL, + created_at TIMESTAMP(6) DEFAULT NULL, + updated_at TIMESTAMP(6) DEFAULT NULL, + deleted_at TIMESTAMP(6) DEFAULT NULL, + PRIMARY KEY (id) +); + `, 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", + } + 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["DELETED_AT"].String(), "") + t.AssertGE(oneInsert["CREATED_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + t.AssertGE(oneInsert["UPDATED_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Save + dataSave := g.Map{ + "id": 1, + "name": "name_10", + } + r, err = db.Model(table).Data(dataSave).Save() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + 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["DELETED_AT"].String(), "") + t.Assert(oneSave["CREATED_AT"].GTime().Timestamp(), oneInsert["CREATED_AT"].GTime().Timestamp()) + t.AssertNE(oneSave["UPDATED_AT"].GTime().Timestamp(), oneInsert["UPDATED_AT"].GTime().Timestamp()) + t.AssertGE(oneSave["UPDATED_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Update + dataUpdate := g.Map{ + "name": "name_1000", + } + 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["DELETED_AT"].String(), "") + t.Assert(oneUpdate["CREATED_AT"].GTime().Timestamp(), oneInsert["CREATED_AT"].GTime().Timestamp()) + t.AssertGE(oneUpdate["UPDATED_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + + // Replace + dataReplace := g.Map{ + "id": 1, + "name": "name_100", + } + r, err = db.Model(table).Data(dataReplace).Replace() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + 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["DELETED_AT"].String(), "") + t.AssertGE(oneReplace["CREATED_AT"].GTime().Timestamp(), oneInsert["CREATED_AT"].GTime().Timestamp()) + t.AssertGE(oneReplace["UPDATED_AT"].GTime().Timestamp(), oneInsert["UPDATED_AT"].GTime().Timestamp()) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Delete + r, err = db.Model(table).Delete("id", 1) + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + // Delete Select + one4, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(len(one4), 0) + one5, err := db.Model(table).Unscoped().WherePri(1).One() + t.AssertNil(err) + t.Assert(one5["ID"].Int(), 1) + t.AssertGE(one5["DELETED_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + // Delete Count + i, err := db.Model(table).Count() + t.AssertNil(err) + t.Assert(i, 0) + i, err = db.Model(table).Unscoped().Count() + t.AssertNil(err) + t.Assert(i, 1) + + // Delete Unscoped + r, err = db.Model(table).Unscoped().Delete("id", 1) + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + one6, err := db.Model(table).Unscoped().WherePri(1).One() + t.AssertNil(err) + t.Assert(len(one6), 0) + i, err = db.Model(table).Unscoped().Count() + t.AssertNil(err) + t.Assert(i, 0) + }) +} + +// CreatedAt/UpdatedAt/DeletedAt. +func Test_SoftTime_CreatedUpdatedDeleted_Struct(t *testing.T) { + table := "soft_time_test_table_" + gtime.TimestampNanoStr() + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id INT NOT NULL, + name VARCHAR(45) DEFAULT NULL, + created_at TIMESTAMP(6) DEFAULT NULL, + updated_at TIMESTAMP(6) DEFAULT NULL, + deleted_at TIMESTAMP(6) DEFAULT NULL, + PRIMARY KEY (id) +); + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + + type User struct { + Id int + Name string + CreatedAT *gtime.Time + UpdatedAT *gtime.Time + DeletedAT *gtime.Time + } + gtest.C(t, func(t *gtest.T) { + // Insert + dataInsert := User{ + Id: 1, + Name: "name_1", + } + 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["DELETED_AT"].String(), "") + t.AssertGE(oneInsert["CREATED_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + t.AssertGE(oneInsert["UPDATED_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Save + dataSave := User{ + Id: 1, + Name: "name_10", + } + r, err = db.Model(table).Data(dataSave).OmitEmpty().Save() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + 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["DELETED_AT"].String(), "") + t.Assert(oneSave["CREATED_AT"].GTime().Timestamp(), oneInsert["CREATED_AT"].GTime().Timestamp()) + t.AssertNE(oneSave["UPDATED_AT"].GTime().Timestamp(), oneInsert["UPDATED_AT"].GTime().Timestamp()) + t.AssertGE(oneSave["UPDATED_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Update + dataUpdate := User{ + Name: "name_1000", + } + r, err = db.Model(table).Data(dataUpdate).OmitEmpty().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["DELETED_AT"].String(), "") + t.Assert(oneUpdate["CREATED_AT"].GTime().Timestamp(), oneInsert["CREATED_AT"].GTime().Timestamp()) + t.AssertGE(oneUpdate["UPDATED_AT"].GTime().Timestamp(), gtime.Timestamp()-4) + + // Replace + dataReplace := User{ + Id: 1, + Name: "name_100", + } + r, err = db.Model(table).Data(dataReplace).OmitEmpty().Replace() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + 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["DELETED_AT"].String(), "") + t.AssertGE(oneReplace["CREATED_AT"].GTime().Timestamp(), oneInsert["CREATED_AT"].GTime().Timestamp()) + t.AssertGE(oneReplace["UPDATED_AT"].GTime().Timestamp(), oneInsert["UPDATED_AT"].GTime().Timestamp()) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Delete + r, err = db.Model(table).Delete("id", 1) + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + // Delete Select + one4, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(len(one4), 0) + one5, err := db.Model(table).Unscoped().WherePri(1).One() + t.AssertNil(err) + t.Assert(one5["ID"].Int(), 1) + t.AssertGE(one5["DELETED_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + // Delete Count + i, err := db.Model(table).Count() + t.AssertNil(err) + t.Assert(i, 0) + i, err = db.Model(table).Unscoped().Count() + t.AssertNil(err) + t.Assert(i, 1) + + // Delete Unscoped + r, err = db.Model(table).Unscoped().Delete("id", 1) + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + one6, err := db.Model(table).Unscoped().WherePri(1).One() + t.AssertNil(err) + t.Assert(len(one6), 0) + i, err = db.Model(table).Unscoped().Count() + t.AssertNil(err) + t.Assert(i, 0) + }) +} + +func Test_SoftUpdateTime(t *testing.T) { + table := "soft_time_test_table_" + gtime.TimestampNanoStr() + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id INT NOT NULL, + num INT DEFAULT NULL, + create_at TIMESTAMP(6) DEFAULT NULL, + update_at TIMESTAMP(6) DEFAULT NULL, + delete_at TIMESTAMP(6) DEFAULT NULL, + PRIMARY KEY (id) +); + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + // Insert + dataInsert := g.Map{ + "id": 1, + "num": 10, + } + 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["NUM"].Int(), 10) + + // Update. + r, err = db.Model(table).Data("num=num+1").Where("id=?", 1).Update() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + }) +} + +func Test_SoftUpdateTime_WithDO(t *testing.T) { + table := "soft_time_test_table_" + gtime.TimestampNanoStr() + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id INT NOT NULL, + num INT DEFAULT NULL, + created_at TIMESTAMP(6) DEFAULT NULL, + updated_at TIMESTAMP(6) DEFAULT NULL, + deleted_at TIMESTAMP(6) DEFAULT NULL, + PRIMARY KEY (id) +); + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + // Insert + dataInsert := g.Map{ + "id": 1, + "num": 10, + } + r, err := db.Model(table).Data(dataInsert).Insert() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + + oneInserted, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(oneInserted["ID"].Int(), 1) + t.Assert(oneInserted["NUM"].Int(), 10) + + // Update. + time.Sleep(2 * time.Second) + type User struct { + g.Meta `orm:"do:true"` + Id any + Num any + CreatedAt any + UpdatedAt any + DeletedAt any + } + r, err = db.Model(table).Data(User{ + Num: 100, + }).Where("id=?", 1).Update() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + oneUpdated, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(oneUpdated["NUM"].Int(), 100) + t.Assert(oneUpdated["CREATED_AT"].String(), oneInserted["CREATED_AT"].String()) + t.AssertNE(oneUpdated["UPDATED_AT"].String(), oneInserted["UPDATED_AT"].String()) + }) +} + +func Test_SoftDelete(t *testing.T) { + table := "soft_time_test_table_" + gtime.TimestampNanoStr() + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id INT NOT NULL, + name VARCHAR(45) DEFAULT NULL, + create_at TIMESTAMP(6) DEFAULT NULL, + update_at TIMESTAMP(6) DEFAULT NULL, + delete_at TIMESTAMP(6) DEFAULT NULL, + PRIMARY KEY (id) +); + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + // db.SetDebug(true) + gtest.C(t, func(t *gtest.T) { + for i := 1; i <= 10; i++ { + data := g.Map{ + "id": i, + "name": fmt.Sprintf("name_%d", i), + } + r, err := db.Model(table).Data(data).Insert() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + } + }) + gtest.C(t, func(t *gtest.T) { + one, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.AssertNE(one["CREATE_AT"].String(), "") + t.AssertNE(one["UPDATE_AT"].String(), "") + t.Assert(one["DELETE_AT"].String(), "") + }) + gtest.C(t, func(t *gtest.T) { + one, err := db.Model(table).WherePri(10).One() + t.AssertNil(err) + t.AssertNE(one["CREATE_AT"].String(), "") + t.AssertNE(one["UPDATE_AT"].String(), "") + t.Assert(one["DELETE_AT"].String(), "") + }) + gtest.C(t, func(t *gtest.T) { + ids := g.SliceInt{1, 3, 5} + r, err := db.Model(table).Where("id", ids).Delete() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 3) + + count, err := db.Model(table).Where("id", ids).Count() + t.AssertNil(err) + t.Assert(count, 0) + + all, err := db.Model(table).Unscoped().Where("id", ids).All() + t.AssertNil(err) + t.Assert(len(all), 3) + t.AssertNE(all[0]["CREATE_AT"].String(), "") + t.AssertNE(all[0]["UPDATE_AT"].String(), "") + t.AssertNE(all[0]["DELETE_AT"].String(), "") + t.AssertNE(all[1]["CREATE_AT"].String(), "") + t.AssertNE(all[1]["UPDATE_AT"].String(), "") + t.AssertNE(all[1]["DELETE_AT"].String(), "") + t.AssertNE(all[2]["CREATE_AT"].String(), "") + t.AssertNE(all[2]["UPDATE_AT"].String(), "") + t.AssertNE(all[2]["DELETE_AT"].String(), "") + }) +} + +func Test_SoftDelete_Join(t *testing.T) { + table1 := "time_test_table1" + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id INT NOT NULL, + name VARCHAR(45) DEFAULT NULL, + create_at TIMESTAMP(6) DEFAULT NULL, + update_at TIMESTAMP(6) DEFAULT NULL, + delete_at TIMESTAMP(6) DEFAULT NULL, + PRIMARY KEY (id) +); + `, table1)); err != nil { + gtest.Error(err) + } + defer dropTable(table1) + + table2 := "time_test_table2" + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id INT NOT NULL, + name VARCHAR(45) DEFAULT NULL, + createat TIMESTAMP(6) DEFAULT NULL, + updateat TIMESTAMP(6) DEFAULT NULL, + deleteat TIMESTAMP(6) DEFAULT NULL, + PRIMARY KEY (id) +); + `, table2)); err != nil { + gtest.Error(err) + } + defer dropTable(table2) + + gtest.C(t, func(t *gtest.T) { + // db.SetDebug(true) + dataInsert1 := g.Map{ + "id": 1, + "name": "name_1", + } + r, err := db.Model(table1).Data(dataInsert1).Insert() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + + dataInsert2 := g.Map{ + "id": 1, + "name": "name_2", + } + r, err = db.Model(table2).Data(dataInsert2).Insert() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + one, err := db.Model(table1, "t1").LeftJoin(table2, "t2", "t2.id=t1.id").Fields("t1.name").One() + t.AssertNil(err) + t.Assert(one["NAME"], "name_1") + + // Soft deleting. + r, err = db.Model(table1).Where(1).Delete() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + one, err = db.Model(table1, "t1").LeftJoin(table2, "t2", "t2.id=t1.id").Fields("t1.name").One() + t.AssertNil(err) + t.Assert(one.IsEmpty(), true) + + one, err = db.Model(table2, "t2").LeftJoin(table1, "t1", "t2.id=t1.id").Fields("t2.name").One() + t.AssertNil(err) + t.Assert(one.IsEmpty(), true) + }) +} + +func Test_SoftDelete_WhereAndOr(t *testing.T) { + table := "soft_time_test_table_" + gtime.TimestampNanoStr() + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id INT NOT NULL, + name VARCHAR(45) DEFAULT NULL, + create_at TIMESTAMP(6) DEFAULT NULL, + update_at TIMESTAMP(6) DEFAULT NULL, + delete_at TIMESTAMP(6) DEFAULT NULL, + PRIMARY KEY (id) +); + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + // db.SetDebug(true) + // Add datas. + gtest.C(t, func(t *gtest.T) { + for i := 1; i <= 10; i++ { + data := g.Map{ + "id": i, + "name": fmt.Sprintf("name_%d", i), + } + r, err := db.Model(table).Data(data).Insert() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + } + }) + gtest.C(t, func(t *gtest.T) { + ids := g.SliceInt{1, 3, 5} + r, err := db.Model(table).Where("id", ids).Delete() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 3) + + count, err := db.Model(table).Where("id", 1).WhereOr("id", 3).Count() + t.AssertNil(err) + t.Assert(count, 0) + }) +} + +func Test_CreateUpdateTime_Struct(t *testing.T) { + table := "soft_time_test_table_" + gtime.TimestampNanoStr() + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id INT NOT NULL, + name VARCHAR(45) DEFAULT NULL, + create_at TIMESTAMP(6) DEFAULT NULL, + update_at TIMESTAMP(6) DEFAULT NULL, + delete_at TIMESTAMP(6) DEFAULT NULL, + PRIMARY KEY (id) +); + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + + // db.SetDebug(true) + // defer db.SetDebug(false) + + type Entity struct { + Id uint64 `orm:"id,primary" json:"id"` + Name string `orm:"name" json:"name"` + CreateAt *gtime.Time `orm:"create_at" json:"create_at"` + UpdateAt *gtime.Time `orm:"update_at" json:"update_at"` + DeleteAt *gtime.Time `orm:"delete_at" json:"delete_at"` + } + gtest.C(t, func(t *gtest.T) { + // Insert + dataInsert := &Entity{ + Id: 1, + Name: "name_1", + CreateAt: nil, + UpdateAt: nil, + DeleteAt: nil, + } + r, err := db.Model(table).Data(dataInsert).OmitEmpty().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.AssertGE(oneInsert["CREATE_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + t.AssertGE(oneInsert["UPDATE_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + + time.Sleep(2 * time.Second) + + // Save + dataSave := &Entity{ + Id: 1, + Name: "name_10", + CreateAt: nil, + UpdateAt: nil, + DeleteAt: nil, + } + r, err = db.Model(table).Data(dataSave).OmitEmpty().Save() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + 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"].GTime().Timestamp(), oneInsert["CREATE_AT"].GTime().Timestamp()) + t.AssertNE(oneSave["UPDATE_AT"].GTime().Timestamp(), oneInsert["UPDATE_AT"].GTime().Timestamp()) + t.AssertGE(oneSave["UPDATE_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + + time.Sleep(2 * time.Second) + + // Update + dataUpdate := &Entity{ + Id: 1, + Name: "name_1000", + CreateAt: nil, + UpdateAt: nil, + DeleteAt: nil, + } + r, err = db.Model(table).Data(dataUpdate).WherePri(1).OmitEmpty().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"].GTime().Timestamp(), oneInsert["CREATE_AT"].GTime().Timestamp()) + t.AssertGE(oneUpdate["UPDATE_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + + // Replace + dataReplace := &Entity{ + Id: 1, + Name: "name_100", + CreateAt: nil, + UpdateAt: nil, + DeleteAt: nil, + } + r, err = db.Model(table).Data(dataReplace).OmitEmpty().Replace() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + 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.AssertGE(oneReplace["CREATE_AT"].GTime().Timestamp(), oneInsert["CREATE_AT"].GTime().Timestamp()) + t.AssertGE(oneReplace["UPDATE_AT"].GTime().Timestamp(), oneInsert["UPDATE_AT"].GTime().Timestamp()) + + time.Sleep(2 * time.Second) + + // Delete + r, err = db.Model(table).Delete("id", 1) + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + // Delete Select + one4, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(len(one4), 0) + one5, err := db.Model(table).Unscoped().WherePri(1).One() + t.AssertNil(err) + t.Assert(one5["ID"].Int(), 1) + t.AssertGE(one5["DELETE_AT"].GTime().Timestamp(), gtime.Timestamp()-2) + // Delete Count + i, err := db.Model(table).Count() + t.AssertNil(err) + t.Assert(i, 0) + i, err = db.Model(table).Unscoped().Count() + t.AssertNil(err) + t.Assert(i, 1) + + // Delete Unscoped + r, err = db.Model(table).Unscoped().Delete("id", 1) + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + one6, err := db.Model(table).Unscoped().WherePri(1).One() + t.AssertNil(err) + t.Assert(len(one6), 0) + i, err = db.Model(table).Unscoped().Count() + t.AssertNil(err) + t.Assert(i, 0) + }) +} + +func Test_SoftTime_CreateUpdateDelete_UnixTimestamp(t *testing.T) { + table := "soft_time_test_table_" + gtime.TimestampNanoStr() + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id INT NOT NULL, + name VARCHAR(45) DEFAULT NULL, + create_at INT DEFAULT NULL, + update_at INT DEFAULT NULL, + delete_at INT DEFAULT NULL, + PRIMARY KEY (id) +); + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + + // insert + gtest.C(t, func(t *gtest.T) { + dataInsert := g.Map{ + "id": 1, + "name": "name_1", + } + r, err := db.Model(table).Data(dataInsert).Insert() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + + one, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(one["NAME"].String(), "name_1") + t.AssertGT(one["CREATE_AT"].Int64(), 0) + t.AssertGT(one["UPDATE_AT"].Int64(), 0) + t.Assert(one["DELETE_AT"].Int64(), 0) + t.Assert(len(one["CREATE_AT"].String()), 10) + t.Assert(len(one["UPDATE_AT"].String()), 10) + }) + + // sleep some seconds to make update time greater than create time. + time.Sleep(2 * time.Second) + + // update + gtest.C(t, func(t *gtest.T) { + // update: map + dataInsert := g.Map{ + "name": "name_11", + } + r, err := db.Model(table).Data(dataInsert).WherePri(1).Update() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + + one, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(one["NAME"].String(), "name_11") + t.AssertGT(one["CREATE_AT"].Int64(), 0) + t.AssertGT(one["UPDATE_AT"].Int64(), 0) + t.Assert(one["DELETE_AT"].Int64(), 0) + t.Assert(len(one["CREATE_AT"].String()), 10) + t.Assert(len(one["UPDATE_AT"].String()), 10) + + var ( + lastCreateTime = one["CREATE_AT"].Int64() + lastUpdateTime = one["UPDATE_AT"].Int64() + ) + + time.Sleep(2 * time.Second) + + // update: string + r, err = db.Model(table).Data("name='name_111'").WherePri(1).Update() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + one, err = db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(one["NAME"].String(), "name_111") + t.Assert(one["CREATE_AT"].Int64(), lastCreateTime) + t.AssertGT(one["UPDATE_AT"].Int64(), lastUpdateTime) + t.Assert(one["DELETE_AT"].Int64(), 0) + }) + + // delete + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(table).WherePri(1).Delete() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + + one, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(len(one), 0) + + one, err = db.Model(table).Unscoped().WherePri(1).One() + t.AssertNil(err) + t.Assert(one["NAME"].String(), "name_111") + t.AssertGT(one["CREATE_AT"].Int64(), 0) + t.AssertGT(one["UPDATE_AT"].Int64(), 0) + t.AssertGT(one["DELETE_AT"].Int64(), 0) + }) +} + +func Test_SoftTime_CreateUpdateDelete_Bool_Deleted(t *testing.T) { + table := "soft_time_test_table_" + gtime.TimestampNanoStr() + // do not use BIT(1) but use BIT in dm database as bool type. + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id INT NOT NULL, + name VARCHAR(45) DEFAULT NULL, + create_at INT DEFAULT NULL, + update_at INT DEFAULT NULL, + delete_at BIT DEFAULT NULL, + PRIMARY KEY (id) +); + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + + // db.SetDebug(true) + // insert + gtest.C(t, func(t *gtest.T) { + dataInsert := g.Map{ + "id": 1, + "name": "name_1", + } + r, err := db.Model(table).Data(dataInsert).Insert() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + + one, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(one["NAME"].String(), "name_1") + t.AssertGT(one["CREATE_AT"].Int64(), 0) + t.AssertGT(one["UPDATE_AT"].Int64(), 0) + t.Assert(one["DELETE_AT"].Int64(), 0) + t.Assert(len(one["CREATE_AT"].String()), 10) + t.Assert(len(one["UPDATE_AT"].String()), 10) + }) + + // delete + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(table).WherePri(1).Delete() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + + one, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(len(one), 0) + + one, err = db.Model(table).Unscoped().WherePri(1).One() + t.AssertNil(err) + t.Assert(one["NAME"].String(), "name_1") + t.AssertGT(one["CREATE_AT"].Int64(), 0) + t.AssertGT(one["UPDATE_AT"].Int64(), 0) + t.Assert(one["DELETE_AT"].Int64(), 1) + }) +} + +func Test_SoftTime_CreateUpdateDelete_Option_SoftTimeTypeTimestampMilli(t *testing.T) { + table := "soft_time_test_table_" + gtime.TimestampNanoStr() + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id INT NOT NULL, + name VARCHAR(45) DEFAULT NULL, + create_at BIGINT DEFAULT NULL, + update_at BIGINT DEFAULT NULL, + delete_at BIT DEFAULT NULL, + PRIMARY KEY (id) +); + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + + var softTimeOption = gdb.SoftTimeOption{ + SoftTimeType: gdb.SoftTimeTypeTimestampMilli, + } + + // insert + gtest.C(t, func(t *gtest.T) { + dataInsert := g.Map{ + "id": 1, + "name": "name_1", + } + r, err := db.Model(table).SoftTime(softTimeOption).Data(dataInsert).Insert() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + + one, err := db.Model(table).SoftTime(softTimeOption).WherePri(1).One() + t.AssertNil(err) + t.Assert(one["NAME"].String(), "name_1") + t.Assert(len(one["CREATE_AT"].String()), 13) + t.Assert(len(one["UPDATE_AT"].String()), 13) + t.Assert(one["DELETE_AT"].Int64(), 0) + }) + + // delete + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(table).SoftTime(softTimeOption).WherePri(1).Delete() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + + one, err := db.Model(table).SoftTime(softTimeOption).WherePri(1).One() + t.AssertNil(err) + t.Assert(len(one), 0) + + one, err = db.Model(table).Unscoped().WherePri(1).One() + t.AssertNil(err) + t.Assert(one["NAME"].String(), "name_1") + t.AssertGT(one["CREATE_AT"].Int64(), 0) + t.AssertGT(one["UPDATE_AT"].Int64(), 0) + t.Assert(one["DELETE_AT"].Int64(), 1) + }) +} + +func Test_SoftTime_CreateUpdateDelete_Option_SoftTimeTypeTimestampNano(t *testing.T) { + table := "soft_time_test_table_" + gtime.TimestampNanoStr() + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id INT NOT NULL, + name VARCHAR(45) DEFAULT NULL, + create_at BIGINT DEFAULT NULL, + update_at BIGINT DEFAULT NULL, + delete_at BIT DEFAULT NULL, + PRIMARY KEY (id) +); + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + + var softTimeOption = gdb.SoftTimeOption{ + SoftTimeType: gdb.SoftTimeTypeTimestampNano, + } + + // insert + gtest.C(t, func(t *gtest.T) { + dataInsert := g.Map{ + "id": 1, + "name": "name_1", + } + r, err := db.Model(table).SoftTime(softTimeOption).Data(dataInsert).Insert() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + + one, err := db.Model(table).SoftTime(softTimeOption).WherePri(1).One() + t.AssertNil(err) + t.Assert(one["NAME"].String(), "name_1") + t.Assert(len(one["CREATE_AT"].String()), 19) + t.Assert(len(one["UPDATE_AT"].String()), 19) + t.Assert(one["DELETE_AT"].Int64(), 0) + }) + + // delete + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(table).SoftTime(softTimeOption).WherePri(1).Delete() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + + one, err := db.Model(table).SoftTime(softTimeOption).WherePri(1).One() + t.AssertNil(err) + t.Assert(len(one), 0) + + one, err = db.Model(table).Unscoped().WherePri(1).One() + t.AssertNil(err) + t.Assert(one["NAME"].String(), "name_1") + t.AssertGT(one["CREATE_AT"].Int64(), 0) + t.AssertGT(one["UPDATE_AT"].Int64(), 0) + 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 NOT NULL, + name VARCHAR(45) DEFAULT NULL, + create_at TIMESTAMP(0) DEFAULT NULL, + update_at TIMESTAMP(0) DEFAULT NULL, + delete_at TIMESTAMP(0) DEFAULT NULL, + PRIMARY KEY (id) +); + `, 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, 1) + + 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, 1) + + 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.AssertGE(oneReplace["CREATE_AT"].GTime().Timestamp(), oneInsert["CREATE_AT"].GTime().Timestamp()) + t.AssertGE(oneReplace["UPDATE_AT"].GTime().Timestamp(), oneInsert["UPDATE_AT"].GTime().Timestamp()) + + // 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") + }) +} diff --git a/contrib/drivers/dm/dm_z_unit_init_test.go b/contrib/drivers/dm/dm_z_unit_init_test.go index 94d056716..100329a70 100644 --- a/contrib/drivers/dm/dm_z_unit_init_test.go +++ b/contrib/drivers/dm/dm_z_unit_init_test.go @@ -63,8 +63,8 @@ func init() { Weight: 1, MaxIdleConnCount: 10, MaxOpenConnCount: 10, - CreatedAt: "created_time", - UpdatedAt: "updated_time", + // CreatedAt: "created_time", + // UpdatedAt: "updated_time", } nodeLink := gdb.ConfigNode{ diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index d34c58ed3..56d10bd35 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -446,8 +446,10 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List, // Group the list by fields. Different fields to different list. // It here uses ListMap to keep sequence for data inserting. // ============================================================================================ - var keyListMap = gmap.NewListMap() - var tmpkeyListMap = make(map[string]List) + var ( + keyListMap = gmap.NewListMap() + tmpKeyListMap = make(map[string]List) + ) for _, item := range list { mapLen := len(item) if mapLen == 0 { @@ -463,13 +465,13 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List, keys = tmpKeys // for fieldsToSequence tmpKeysInSequenceStr := gstr.Join(tmpKeys, ",") - if tmpkeyListMapItem, ok := tmpkeyListMap[tmpKeysInSequenceStr]; ok { - tmpkeyListMap[tmpKeysInSequenceStr] = append(tmpkeyListMapItem, item) + if tmpKeyListMapItem, ok := tmpKeyListMap[tmpKeysInSequenceStr]; ok { + tmpKeyListMap[tmpKeysInSequenceStr] = append(tmpKeyListMapItem, item) } else { - tmpkeyListMap[tmpKeysInSequenceStr] = List{item} + tmpKeyListMap[tmpKeysInSequenceStr] = List{item} } } - for tmpKeysInSequenceStr, itemList := range tmpkeyListMap { + for tmpKeysInSequenceStr, itemList := range tmpKeyListMap { keyListMap.Set(tmpKeysInSequenceStr, itemList) } if keyListMap.Size() > 1 { diff --git a/database/gdb/gdb_model_soft_time.go b/database/gdb/gdb_model_soft_time.go index 3972bf647..51d69efd7 100644 --- a/database/gdb/gdb_model_soft_time.go +++ b/database/gdb/gdb_model_soft_time.go @@ -380,6 +380,7 @@ func (m *softTimeMaintainer) GetValueByFieldTypeForCreateOrUpdate( ctx context.Context, fieldType LocalType, isDeletedField bool, ) any { var value any + // for creat or update procedure, the deleted field is always set to non-deleted value. if isDeletedField { switch fieldType { case LocalTypeDate, LocalTypeTime, LocalTypeDatetime: