diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index 98b71493a..dd1a9078c 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -37,7 +37,7 @@ type Model struct { cacheEnabled bool // Enable sql result cache feature. cacheDuration time.Duration // Cache TTL duration. cacheName string // Cache name for custom operation. - force bool // Force select/delete without soft operation features. + unscoped bool // Disables soft deleting features when select/delete operations. safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model. } diff --git a/database/gdb/gdb_model_delete.go b/database/gdb/gdb_model_delete.go index 190a3f00f..7974c7c7d 100644 --- a/database/gdb/gdb_model_delete.go +++ b/database/gdb/gdb_model_delete.go @@ -12,13 +12,13 @@ import ( "github.com/gogf/gf/os/gtime" ) -// Force enables/disables the soft deleting feature. -func (m *Model) Force(force ...bool) *Model { +// Unscoped enables/disables the soft deleting feature. +func (m *Model) Unscoped(unscoped ...bool) *Model { model := m.getModel() - if len(force) > 0 { - model.force = force[0] + if len(unscoped) > 0 { + model.unscoped = unscoped[0] } else { - model.force = true + model.unscoped = true } return model } @@ -40,7 +40,7 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) { conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false) ) // Soft deleting. - if !m.force && fieldNameDelete != "" { + if !m.unscoped && fieldNameDelete != "" { return m.db.DoUpdate( m.getLink(true), m.tables, diff --git a/database/gdb/gdb_model_insert.go b/database/gdb/gdb_model_insert.go index f78708770..590d56863 100644 --- a/database/gdb/gdb_model_insert.go +++ b/database/gdb/gdb_model_insert.go @@ -157,7 +157,7 @@ func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql. batch = m.batch } // Automatic handling for creating/updating time. - if !m.force && (fieldNameCreate != "" || fieldNameUpdate != "") { + if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") { for k, v := range list { if fieldNameCreate != "" && !gutil.MapContainsPossibleKey(v, fieldNameCreate) { v[fieldNameCreate] = nowString @@ -179,7 +179,7 @@ func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql. // Single operation. if data, ok := m.data.(Map); ok { // Automatic handling for creating/updating time. - if !m.force && (fieldNameCreate != "" || fieldNameUpdate != "") { + if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") { if fieldNameCreate != "" && !gutil.MapContainsPossibleKey(data, fieldNameCreate) { data[fieldNameCreate] = nowString } diff --git a/database/gdb/gdb_model_select.go b/database/gdb/gdb_model_select.go index d873bc12c..aefd83197 100644 --- a/database/gdb/gdb_model_select.go +++ b/database/gdb/gdb_model_select.go @@ -34,8 +34,10 @@ func (m *Model) All(where ...interface{}) (Result, error) { fieldNameDelete = m.getSoftFieldNameDelete() conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false) ) - if !m.force && fieldNameDelete != "" { - if conditionWhere != "" { + if !m.unscoped && fieldNameDelete != "" { + if conditionWhere == "" { + conditionWhere = " WHERE" + } else { conditionWhere += " AND" } conditionWhere += fmt.Sprintf(` %s IS NULL`, m.db.QuoteWord(fieldNameDelete)) @@ -244,7 +246,19 @@ func (m *Model) Count(where ...interface{}) (int, error) { if m.fields != "" && m.fields != "*" { countFields = fmt.Sprintf(`COUNT(%s)`, m.fields) } - conditionWhere, conditionExtra, conditionArgs := m.formatCondition(false) + var ( + fieldNameDelete = m.getSoftFieldNameDelete() + conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false) + ) + if !m.unscoped && fieldNameDelete != "" { + if conditionWhere == "" { + conditionWhere = " WHERE" + } else { + conditionWhere += " AND" + } + conditionWhere += fmt.Sprintf(` %s IS NULL`, m.db.QuoteWord(fieldNameDelete)) + } + s := fmt.Sprintf("SELECT %s FROM %s %s", countFields, m.tables, conditionWhere+conditionExtra) if len(m.groupBy) > 0 { s = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", s) diff --git a/database/gdb/gdb_model_update.go b/database/gdb/gdb_model_update.go index 7f9ce5240..1d9118c44 100644 --- a/database/gdb/gdb_model_update.go +++ b/database/gdb/gdb_model_update.go @@ -46,7 +46,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false) ) // Automatically update the record updating time. - if !m.force && fieldNameUpdate != "" { + if !m.unscoped && fieldNameUpdate != "" { var ( refValue = reflect.ValueOf(m.data) refKind = refValue.Kind() diff --git a/database/gdb/gdb_unit_z_mysql_time_test.go b/database/gdb/gdb_unit_z_mysql_time_test.go new file mode 100644 index 000000000..4396beab5 --- /dev/null +++ b/database/gdb/gdb_unit_z_mysql_time_test.go @@ -0,0 +1,149 @@ +// 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 gdb_test + +import ( + "fmt" + "github.com/gogf/gf/os/gtime" + "testing" + "time" + + "github.com/gogf/gf/frame/g" + + "github.com/gogf/gf/test/gtest" +) + +func Test_CreateUpdateDeleteTime(t *testing.T) { + table := "time_test_table" + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + id int(11) NOT NULL, + name varchar(45) DEFAULT NULL, + create_at datetime DEFAULT NULL, + update_at datetime DEFAULT NULL, + delete_at datetime 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", + } + r, err := db.Table(table).Data(dataInsert).Insert() + t.Assert(err, nil) + n, _ := r.RowsAffected() + t.Assert(n, 1) + + oneInsert, err := db.Table(table).FindOne(1) + t.Assert(err, nil) + 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()) + + time.Sleep(2 * time.Second) + + // Save + dataSave := g.Map{ + "id": 1, + "name": "name_10", + } + r, err = db.Table(table).Data(dataSave).Save() + t.Assert(err, nil) + n, _ = r.RowsAffected() + t.Assert(n, 2) + + oneSave, err := db.Table(table).FindOne(1) + t.Assert(err, nil) + 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.Now().Timestamp()-2) + + time.Sleep(2 * time.Second) + + // Update + dataUpdate := g.Map{ + "id": 1, + "name": "name_1000", + } + r, err = db.Table(table).Data(dataUpdate).WherePri(1).Update() + t.Assert(err, nil) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + oneUpdate, err := db.Table(table).FindOne(1) + t.Assert(err, nil) + 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.Now().Timestamp()-2) + + // Replace + dataReplace := g.Map{ + "id": 1, + "name": "name_100", + } + r, err = db.Table(table).Data(dataReplace).Replace() + t.Assert(err, nil) + n, _ = r.RowsAffected() + t.Assert(n, 2) + + oneReplace, err := db.Table(table).FindOne(1) + t.Assert(err, nil) + 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.Table(table).Delete("id", 1) + t.Assert(err, nil) + n, _ = r.RowsAffected() + t.Assert(n, 1) + // Delete Select + one4, err := db.Table(table).FindOne(1) + t.Assert(err, nil) + t.Assert(len(one4), 0) + one5, err := db.Table(table).Unscoped().FindOne(1) + t.Assert(err, nil) + t.Assert(one5["id"].Int(), 1) + t.AssertGE(one5["delete_at"].GTime().Timestamp(), gtime.Now().Timestamp()-2) + // Delete Count + i, err := db.Table(table).FindCount() + t.Assert(err, nil) + t.Assert(i, 0) + i, err = db.Table(table).Unscoped().FindCount() + t.Assert(err, nil) + t.Assert(i, 1) + + // Delete Unscoped + r, err = db.Table(table).Unscoped().Delete("id", 1) + t.Assert(err, nil) + n, _ = r.RowsAffected() + t.Assert(n, 1) + one6, err := db.Table(table).Unscoped().FindOne(1) + t.Assert(err, nil) + t.Assert(len(one6), 0) + i, err = db.Table(table).Unscoped().FindCount() + t.Assert(err, nil) + t.Assert(i, 0) + }) +} diff --git a/test/gtest/gtest_t.go b/test/gtest/gtest_t.go index a95fd46d3..06c677227 100644 --- a/test/gtest/gtest_t.go +++ b/test/gtest/gtest_t.go @@ -30,9 +30,9 @@ func (t *T) AssertNE(value, expect interface{}) { AssertNE(value, expect) } -// AssertNE checks and NOT EQUAL, including their TYPES. -func (t *T) AssertNEQ(value, expect interface{}) { - AssertNEQ(value, expect) +// AssertNQ checks and NOT EQUAL, including their TYPES. +func (t *T) AssertNQ(value, expect interface{}) { + AssertNQ(value, expect) } // AssertGT checks is GREATER THAN . diff --git a/test/gtest/gtest_util.go b/test/gtest/gtest_util.go index f3663f3ce..701d8250c 100644 --- a/test/gtest/gtest_util.go +++ b/test/gtest/gtest_util.go @@ -116,8 +116,8 @@ func AssertNE(value, expect interface{}) { } } -// AssertNEQ checks and NOT EQUAL, including their TYPES. -func AssertNEQ(value, expect interface{}) { +// AssertNQ checks and NOT EQUAL, including their TYPES. +func AssertNQ(value, expect interface{}) { // Type assert. t1 := reflect.TypeOf(value) t2 := reflect.TypeOf(expect)