mirror of
https://gitee.com/johng/gf.git
synced 2024-12-01 19:57:40 +08:00
add field type detection for soft time field like created_at/updated_at/deleted_at
to support unix timestamp or bool deleting table field (#3293)
This commit is contained in:
parent
85c5b7f19e
commit
2acdf4bb47
@ -11,14 +11,15 @@ import (
|
||||
"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_SoftCreateUpdateDeleteTimeMicroSecond(t *testing.T) {
|
||||
table := "time_test_table_" + gtime.TimestampNanoStr()
|
||||
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(11) NOT NULL,
|
||||
@ -151,8 +152,8 @@ CREATE TABLE %s (
|
||||
}
|
||||
|
||||
// CreateAt/UpdateAt/DeleteAt.
|
||||
func Test_SoftCreateUpdateDeleteTimeSecond(t *testing.T) {
|
||||
table := "time_test_table_" + gtime.TimestampNanoStr()
|
||||
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(11) NOT NULL,
|
||||
@ -285,8 +286,8 @@ CREATE TABLE %s (
|
||||
}
|
||||
|
||||
// CreatedAt/UpdatedAt/DeletedAt.
|
||||
func Test_SoftCreatedUpdatedDeletedTime_Map(t *testing.T) {
|
||||
table := "time_test_table_" + gtime.TimestampNanoStr()
|
||||
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(11) NOT NULL,
|
||||
@ -419,8 +420,8 @@ CREATE TABLE %s (
|
||||
}
|
||||
|
||||
// CreatedAt/UpdatedAt/DeletedAt.
|
||||
func Test_SoftCreatedUpdatedDeletedTime_Struct(t *testing.T) {
|
||||
table := "time_test_table_" + gtime.TimestampNanoStr()
|
||||
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(11) NOT NULL,
|
||||
@ -560,7 +561,7 @@ CREATE TABLE %s (
|
||||
}
|
||||
|
||||
func Test_SoftUpdateTime(t *testing.T) {
|
||||
table := "time_test_table_" + gtime.TimestampNanoStr()
|
||||
table := "soft_time_test_table_" + gtime.TimestampNanoStr()
|
||||
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(11) NOT NULL,
|
||||
@ -600,7 +601,7 @@ CREATE TABLE %s (
|
||||
}
|
||||
|
||||
func Test_SoftUpdateTime_WithDO(t *testing.T) {
|
||||
table := "time_test_table_" + gtime.TimestampNanoStr()
|
||||
table := "soft_time_test_table_" + gtime.TimestampNanoStr()
|
||||
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(11) NOT NULL,
|
||||
@ -657,7 +658,7 @@ CREATE TABLE %s (
|
||||
}
|
||||
|
||||
func Test_SoftDelete(t *testing.T) {
|
||||
table := "time_test_table_" + gtime.TimestampNanoStr()
|
||||
table := "soft_time_test_table_" + gtime.TimestampNanoStr()
|
||||
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(11) NOT NULL,
|
||||
@ -796,7 +797,7 @@ CREATE TABLE %s (
|
||||
}
|
||||
|
||||
func Test_SoftDelete_WhereAndOr(t *testing.T) {
|
||||
table := "time_test_table_" + gtime.TimestampNanoStr()
|
||||
table := "soft_time_test_table_" + gtime.TimestampNanoStr()
|
||||
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(11) NOT NULL,
|
||||
@ -838,7 +839,7 @@ CREATE TABLE %s (
|
||||
}
|
||||
|
||||
func Test_CreateUpdateTime_Struct(t *testing.T) {
|
||||
table := "time_test_table_" + gtime.TimestampNanoStr()
|
||||
table := "soft_time_test_table_" + gtime.TimestampNanoStr()
|
||||
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(11) NOT NULL,
|
||||
@ -989,3 +990,280 @@ CREATE TABLE %s (
|
||||
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(11) NOT NULL,
|
||||
name varchar(45) DEFAULT NULL,
|
||||
create_at int(11) DEFAULT NULL,
|
||||
update_at int(11) DEFAULT NULL,
|
||||
delete_at int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, 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()
|
||||
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(11) NOT NULL,
|
||||
name varchar(45) DEFAULT NULL,
|
||||
create_at int(11) DEFAULT NULL,
|
||||
update_at int(11) DEFAULT NULL,
|
||||
delete_at bit(1) DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, 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(11) NOT NULL,
|
||||
name varchar(45) DEFAULT NULL,
|
||||
create_at bigint(19) unsigned DEFAULT NULL,
|
||||
update_at bigint(19) unsigned DEFAULT NULL,
|
||||
delete_at bit(1) DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, 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(11) NOT NULL,
|
||||
name varchar(45) DEFAULT NULL,
|
||||
create_at bigint(19) unsigned DEFAULT NULL,
|
||||
update_at bigint(19) unsigned DEFAULT NULL,
|
||||
delete_at bit(1) DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, 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)
|
||||
})
|
||||
}
|
@ -425,6 +425,7 @@ const (
|
||||
type LocalType string
|
||||
|
||||
const (
|
||||
LocalTypeUndefined LocalType = ""
|
||||
LocalTypeString LocalType = "string"
|
||||
LocalTypeDate LocalType = "date"
|
||||
LocalTypeDatetime LocalType = "datetime"
|
||||
|
@ -299,7 +299,9 @@ func (c *Core) CheckLocalTypeForField(ctx context.Context, fieldType string, fie
|
||||
// ConvertValueForLocal converts value to local Golang type of value according field type name from database.
|
||||
// The parameter `fieldType` is in lower case, like:
|
||||
// `float(5,2)`, `unsigned double(5,2)`, `decimal(10,2)`, `char(45)`, `varchar(100)`, etc.
|
||||
func (c *Core) ConvertValueForLocal(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) {
|
||||
func (c *Core) ConvertValueForLocal(
|
||||
ctx context.Context, fieldType string, fieldValue interface{},
|
||||
) (interface{}, error) {
|
||||
// If there's no type retrieved, it returns the `fieldValue` directly
|
||||
// to use its original data type, as `fieldValue` is type of interface{}.
|
||||
if fieldType == "" {
|
||||
|
@ -17,40 +17,41 @@ import (
|
||||
|
||||
// Model is core struct implementing the DAO for ORM.
|
||||
type Model struct {
|
||||
db DB // Underlying DB interface.
|
||||
tx TX // Underlying TX interface.
|
||||
rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model.
|
||||
schema string // Custom database schema.
|
||||
linkType int // Mark for operation on master or slave.
|
||||
tablesInit string // Table names when model initialization.
|
||||
tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud".
|
||||
fields string // Operation fields, multiple fields joined using char ','.
|
||||
fieldsEx string // Excluded operation fields, multiple fields joined using char ','.
|
||||
withArray []interface{} // Arguments for With feature.
|
||||
withAll bool // Enable model association operations on all objects that have "with" tag in the struct.
|
||||
extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver.
|
||||
whereBuilder *WhereBuilder // Condition builder for where operation.
|
||||
groupBy string // Used for "group by" statement.
|
||||
orderBy string // Used for "order by" statement.
|
||||
having []interface{} // Used for "having..." statement.
|
||||
start int // Used for "select ... start, limit ..." statement.
|
||||
limit int // Used for "select ... start, limit ..." statement.
|
||||
option int // Option for extra operation features.
|
||||
offset int // Offset statement for some databases grammar.
|
||||
partition string // Partition table partition name.
|
||||
data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc.
|
||||
batch int // Batch number for batch Insert/Replace/Save operations.
|
||||
filter bool // Filter data and where key-value pairs according to the fields of the table.
|
||||
distinct string // Force the query to only return distinct results.
|
||||
lockInfo string // Lock for update or in shared lock.
|
||||
cacheEnabled bool // Enable sql result cache feature, which is mainly for indicating cache duration(especially 0) usage.
|
||||
cacheOption CacheOption // Cache option for query statement.
|
||||
hookHandler HookHandler // Hook functions for model hook feature.
|
||||
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.
|
||||
onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement.
|
||||
onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement.
|
||||
tableAliasMap map[string]string // Table alias to true table name, usually used in join statements.
|
||||
db DB // Underlying DB interface.
|
||||
tx TX // Underlying TX interface.
|
||||
rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model.
|
||||
schema string // Custom database schema.
|
||||
linkType int // Mark for operation on master or slave.
|
||||
tablesInit string // Table names when model initialization.
|
||||
tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud".
|
||||
fields string // Operation fields, multiple fields joined using char ','.
|
||||
fieldsEx string // Excluded operation fields, multiple fields joined using char ','.
|
||||
withArray []interface{} // Arguments for With feature.
|
||||
withAll bool // Enable model association operations on all objects that have "with" tag in the struct.
|
||||
extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver.
|
||||
whereBuilder *WhereBuilder // Condition builder for where operation.
|
||||
groupBy string // Used for "group by" statement.
|
||||
orderBy string // Used for "order by" statement.
|
||||
having []interface{} // Used for "having..." statement.
|
||||
start int // Used for "select ... start, limit ..." statement.
|
||||
limit int // Used for "select ... start, limit ..." statement.
|
||||
option int // Option for extra operation features.
|
||||
offset int // Offset statement for some databases grammar.
|
||||
partition string // Partition table partition name.
|
||||
data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc.
|
||||
batch int // Batch number for batch Insert/Replace/Save operations.
|
||||
filter bool // Filter data and where key-value pairs according to the fields of the table.
|
||||
distinct string // Force the query to only return distinct results.
|
||||
lockInfo string // Lock for update or in shared lock.
|
||||
cacheEnabled bool // Enable sql result cache feature, which is mainly for indicating cache duration(especially 0) usage.
|
||||
cacheOption CacheOption // Cache option for query statement.
|
||||
hookHandler HookHandler // Hook functions for model hook feature.
|
||||
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.
|
||||
onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement.
|
||||
onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement.
|
||||
tableAliasMap map[string]string // Table alias to true table name, usually used in join statements.
|
||||
softTimeOption SoftTimeOption // SoftTimeOption is the option to customize soft time feature for Model.
|
||||
}
|
||||
|
||||
// ModelHandler is a function that handles given Model and returns a new Model that is custom modified.
|
||||
|
@ -8,13 +8,11 @@ package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// Delete does "DELETE FROM ... " statement for the model.
|
||||
@ -31,9 +29,11 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
}
|
||||
}()
|
||||
var (
|
||||
fieldNameDelete = m.getSoftFieldNameDeleted("", m.tablesInit)
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(ctx, false, false)
|
||||
conditionStr = conditionWhere + conditionExtra
|
||||
fieldNameDelete, fieldTypeDelete = m.softTimeMaintainer().GetFieldNameAndTypeForDelete(
|
||||
ctx, "", m.tablesInit,
|
||||
)
|
||||
)
|
||||
if m.unscoped {
|
||||
fieldNameDelete = ""
|
||||
@ -52,6 +52,9 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
|
||||
// Soft deleting.
|
||||
if fieldNameDelete != "" {
|
||||
dataHolder, dataValue := m.softTimeMaintainer().GetDataByFieldNameAndTypeForDelete(
|
||||
ctx, "", fieldNameDelete, fieldTypeDelete,
|
||||
)
|
||||
in := &HookUpdateInput{
|
||||
internalParamHookUpdate: internalParamHookUpdate{
|
||||
internalParamHook: internalParamHook{
|
||||
@ -61,9 +64,9 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
},
|
||||
Model: m,
|
||||
Table: m.tables,
|
||||
Data: fmt.Sprintf(`%s=?`, m.db.GetCore().QuoteString(fieldNameDelete)),
|
||||
Data: dataHolder,
|
||||
Condition: conditionStr,
|
||||
Args: append([]interface{}{gtime.Now()}, conditionArgs...),
|
||||
Args: append([]interface{}{dataValue}, conditionArgs...),
|
||||
}
|
||||
return in.Next(ctx)
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/reflection"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
@ -243,10 +242,11 @@ func (m *Model) doInsertWithOption(ctx context.Context, insertOption InsertOptio
|
||||
return nil, gerror.NewCode(gcode.CodeMissingParameter, "inserting into table with empty data")
|
||||
}
|
||||
var (
|
||||
list List
|
||||
now = gtime.Now()
|
||||
fieldNameCreate = m.getSoftFieldNameCreated("", m.tablesInit)
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdated("", m.tablesInit)
|
||||
list List
|
||||
stm = m.softTimeMaintainer()
|
||||
fieldNameCreate, fieldTypeCreate = stm.GetFieldNameAndTypeForCreate(ctx, "", m.tablesInit)
|
||||
fieldNameUpdate, fieldTypeUpdate = stm.GetFieldNameAndTypeForUpdate(ctx, "", m.tablesInit)
|
||||
fieldNameDelete, fieldTypeDelete = stm.GetFieldNameAndTypeForDelete(ctx, "", m.tablesInit)
|
||||
)
|
||||
// m.data was already converted to type List/Map by function Data
|
||||
newData, err := m.filterDataForInsertOrUpdate(m.data)
|
||||
@ -270,10 +270,22 @@ func (m *Model) doInsertWithOption(ctx context.Context, insertOption InsertOptio
|
||||
if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") {
|
||||
for k, v := range list {
|
||||
if fieldNameCreate != "" {
|
||||
v[fieldNameCreate] = now
|
||||
fieldCreateValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeCreate, false)
|
||||
if fieldCreateValue != nil {
|
||||
v[fieldNameCreate] = fieldCreateValue
|
||||
}
|
||||
}
|
||||
if fieldNameUpdate != "" {
|
||||
v[fieldNameUpdate] = now
|
||||
fieldUpdateValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeUpdate, false)
|
||||
if fieldUpdateValue != nil {
|
||||
v[fieldNameUpdate] = fieldUpdateValue
|
||||
}
|
||||
}
|
||||
if fieldNameDelete != "" {
|
||||
fieldDeleteValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeDelete, true)
|
||||
if fieldDeleteValue != nil {
|
||||
v[fieldNameDelete] = fieldDeleteValue
|
||||
}
|
||||
}
|
||||
list[k] = v
|
||||
}
|
||||
|
@ -730,7 +730,7 @@ func (m *Model) formatCondition(
|
||||
}
|
||||
// WHERE
|
||||
conditionWhere, conditionArgs = m.whereBuilder.Build()
|
||||
softDeletingCondition := m.getConditionForSoftDeleting()
|
||||
softDeletingCondition := m.softTimeMaintainer().GetWhereConditionForDelete(ctx)
|
||||
if m.rawSql != "" && conditionWhere != "" {
|
||||
if gstr.ContainsI(m.rawSql, " WHERE ") {
|
||||
conditionWhere = " AND " + conditionWhere
|
||||
|
423
database/gdb/gdb_model_soft_time.go
Normal file
423
database/gdb/gdb_model_soft_time.go
Normal file
@ -0,0 +1,423 @@
|
||||
// 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 gdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/container/garray"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/os/gcache"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// SoftTimeType custom defines the soft time field type.
|
||||
type SoftTimeType int
|
||||
|
||||
const (
|
||||
SoftTimeTypeAuto SoftTimeType = 0 // (Default)Auto detect the field type by table field type.
|
||||
SoftTimeTypeTime SoftTimeType = 1 // Using datetime as the field value.
|
||||
SoftTimeTypeTimestamp SoftTimeType = 2 // In unix seconds.
|
||||
SoftTimeTypeTimestampMilli SoftTimeType = 3 // In unix milliseconds.
|
||||
SoftTimeTypeTimestampMicro SoftTimeType = 4 // In unix microseconds.
|
||||
SoftTimeTypeTimestampNano SoftTimeType = 5 // In unix nanoseconds.
|
||||
)
|
||||
|
||||
// SoftTimeOption is the option to customize soft time feature for Model.
|
||||
type SoftTimeOption struct {
|
||||
SoftTimeType SoftTimeType // The value type for soft time field.
|
||||
}
|
||||
|
||||
type softTimeMaintainer struct {
|
||||
*Model
|
||||
}
|
||||
|
||||
type iSoftTimeMaintainer interface {
|
||||
GetFieldNameAndTypeForCreate(
|
||||
ctx context.Context, schema string, table string,
|
||||
) (fieldName string, fieldType LocalType)
|
||||
|
||||
GetFieldNameAndTypeForUpdate(
|
||||
ctx context.Context, schema string, table string,
|
||||
) (fieldName string, fieldType LocalType)
|
||||
|
||||
GetFieldNameAndTypeForDelete(
|
||||
ctx context.Context, schema string, table string,
|
||||
) (fieldName string, fieldType LocalType)
|
||||
|
||||
GetValueByFieldTypeForCreateOrUpdate(
|
||||
ctx context.Context, fieldType LocalType, isDeletedField bool,
|
||||
) (dataValue any)
|
||||
|
||||
GetDataByFieldNameAndTypeForDelete(
|
||||
ctx context.Context, fieldPrefix, fieldName string, fieldType LocalType,
|
||||
) (dataHolder string, dataValue any)
|
||||
|
||||
GetWhereConditionForDelete(ctx context.Context) string
|
||||
}
|
||||
|
||||
// getSoftFieldNameAndTypeCacheItem is the internal struct for storing create/update/delete fields.
|
||||
type getSoftFieldNameAndTypeCacheItem struct {
|
||||
FieldName string
|
||||
FieldType LocalType
|
||||
}
|
||||
|
||||
var (
|
||||
// Default field names of table for automatic-filled for record creating.
|
||||
createdFieldNames = []string{"created_at", "create_at"}
|
||||
// Default field names of table for automatic-filled for record updating.
|
||||
updatedFieldNames = []string{"updated_at", "update_at"}
|
||||
// Default field names of table for automatic-filled for record deleting.
|
||||
deletedFieldNames = []string{"deleted_at", "delete_at"}
|
||||
)
|
||||
|
||||
// SoftTime sets the SoftTimeOption to customize soft time feature for Model.
|
||||
func (m *Model) SoftTime(option SoftTimeOption) *Model {
|
||||
model := m.getModel()
|
||||
model.softTimeOption = option
|
||||
return model
|
||||
}
|
||||
|
||||
// Unscoped disables the soft time feature for insert, update and delete operations.
|
||||
func (m *Model) Unscoped() *Model {
|
||||
model := m.getModel()
|
||||
model.unscoped = true
|
||||
return model
|
||||
}
|
||||
|
||||
func (m *Model) softTimeMaintainer() iSoftTimeMaintainer {
|
||||
return &softTimeMaintainer{
|
||||
m,
|
||||
}
|
||||
}
|
||||
|
||||
// GetFieldNameAndTypeForCreate checks and returns the field name for record creating time.
|
||||
// If there's no field name for storing creating time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *softTimeMaintainer) GetFieldNameAndTypeForCreate(
|
||||
ctx context.Context, schema string, table string,
|
||||
) (fieldName string, fieldType LocalType) {
|
||||
// It checks whether this feature disabled.
|
||||
if m.db.GetConfig().TimeMaintainDisabled {
|
||||
return "", LocalTypeUndefined
|
||||
}
|
||||
tableName := ""
|
||||
if table != "" {
|
||||
tableName = table
|
||||
} else {
|
||||
tableName = m.tablesInit
|
||||
}
|
||||
config := m.db.GetConfig()
|
||||
if config.CreatedAt != "" {
|
||||
return m.getSoftFieldNameAndType(
|
||||
ctx, schema, tableName, []string{config.CreatedAt},
|
||||
)
|
||||
}
|
||||
return m.getSoftFieldNameAndType(
|
||||
ctx, schema, tableName, createdFieldNames,
|
||||
)
|
||||
}
|
||||
|
||||
// GetFieldNameAndTypeForUpdate checks and returns the field name for record updating time.
|
||||
// If there's no field name for storing updating time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *softTimeMaintainer) GetFieldNameAndTypeForUpdate(
|
||||
ctx context.Context, schema string, table string,
|
||||
) (fieldName string, fieldType LocalType) {
|
||||
// It checks whether this feature disabled.
|
||||
if m.db.GetConfig().TimeMaintainDisabled {
|
||||
return "", LocalTypeUndefined
|
||||
}
|
||||
tableName := ""
|
||||
if table != "" {
|
||||
tableName = table
|
||||
} else {
|
||||
tableName = m.tablesInit
|
||||
}
|
||||
config := m.db.GetConfig()
|
||||
if config.UpdatedAt != "" {
|
||||
return m.getSoftFieldNameAndType(
|
||||
ctx, schema, tableName, []string{config.UpdatedAt},
|
||||
)
|
||||
}
|
||||
return m.getSoftFieldNameAndType(
|
||||
ctx, schema, tableName, updatedFieldNames,
|
||||
)
|
||||
}
|
||||
|
||||
// GetFieldNameAndTypeForDelete checks and returns the field name for record deleting time.
|
||||
// If there's no field name for storing deleting time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *softTimeMaintainer) GetFieldNameAndTypeForDelete(
|
||||
ctx context.Context, schema string, table string,
|
||||
) (fieldName string, fieldType LocalType) {
|
||||
// It checks whether this feature disabled.
|
||||
if m.db.GetConfig().TimeMaintainDisabled {
|
||||
return "", LocalTypeUndefined
|
||||
}
|
||||
tableName := ""
|
||||
if table != "" {
|
||||
tableName = table
|
||||
} else {
|
||||
tableName = m.tablesInit
|
||||
}
|
||||
config := m.db.GetConfig()
|
||||
if config.DeletedAt != "" {
|
||||
return m.getSoftFieldNameAndType(
|
||||
ctx, schema, tableName, []string{config.DeletedAt},
|
||||
)
|
||||
}
|
||||
return m.getSoftFieldNameAndType(
|
||||
ctx, schema, tableName, deletedFieldNames,
|
||||
)
|
||||
}
|
||||
|
||||
// getSoftFieldName retrieves and returns the field name of the table for possible key.
|
||||
func (m *softTimeMaintainer) getSoftFieldNameAndType(
|
||||
ctx context.Context,
|
||||
schema string, table string, checkFiledNames []string,
|
||||
) (fieldName string, fieldType LocalType) {
|
||||
var (
|
||||
cacheKey = fmt.Sprintf(`getSoftFieldNameAndType:%s#%s#%p`, schema, table, checkFiledNames)
|
||||
cacheDuration = gcache.DurationNoExpire
|
||||
cacheFunc = func(ctx context.Context) (value interface{}, err error) {
|
||||
// Ignore the error from TableFields.
|
||||
fieldsMap, _ := m.TableFields(table, schema)
|
||||
if len(fieldsMap) > 0 {
|
||||
for _, checkFiledName := range checkFiledNames {
|
||||
fieldName, _ = gutil.MapPossibleItemByKey(
|
||||
gconv.Map(fieldsMap), checkFiledName,
|
||||
)
|
||||
if fieldName != "" {
|
||||
fieldType, _ = m.db.CheckLocalTypeForField(
|
||||
ctx, fieldsMap[fieldName].Type, nil,
|
||||
)
|
||||
var cacheItem = getSoftFieldNameAndTypeCacheItem{
|
||||
FieldName: fieldName,
|
||||
FieldType: fieldType,
|
||||
}
|
||||
return cacheItem, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
)
|
||||
result, err := m.db.GetCache().GetOrSetFunc(ctx, cacheKey, cacheFunc, cacheDuration)
|
||||
if err != nil {
|
||||
intlog.Error(ctx, err)
|
||||
}
|
||||
if result != nil {
|
||||
var cacheItem = result.Val().(getSoftFieldNameAndTypeCacheItem)
|
||||
fieldName = cacheItem.FieldName
|
||||
fieldType = cacheItem.FieldType
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetWhereConditionForDelete retrieves and returns the condition string for soft deleting.
|
||||
// It supports multiple tables string like:
|
||||
// "user u, user_detail ud"
|
||||
// "user u LEFT JOIN user_detail ud ON(ud.uid=u.uid)"
|
||||
// "user LEFT JOIN user_detail ON(user_detail.uid=user.uid)"
|
||||
// "user u LEFT JOIN user_detail ud ON(ud.uid=u.uid) LEFT JOIN user_stats us ON(us.uid=u.uid)".
|
||||
func (m *softTimeMaintainer) GetWhereConditionForDelete(ctx context.Context) string {
|
||||
if m.unscoped {
|
||||
return ""
|
||||
}
|
||||
conditionArray := garray.NewStrArray()
|
||||
if gstr.Contains(m.tables, " JOIN ") {
|
||||
// Base table.
|
||||
tableMatch, _ := gregex.MatchString(`(.+?) [A-Z]+ JOIN`, m.tables)
|
||||
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(ctx, tableMatch[1]))
|
||||
// Multiple joined tables, exclude the sub query sql which contains char '(' and ')'.
|
||||
tableMatches, _ := gregex.MatchAllString(`JOIN ([^()]+?) ON`, m.tables)
|
||||
for _, match := range tableMatches {
|
||||
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(ctx, match[1]))
|
||||
}
|
||||
}
|
||||
if conditionArray.Len() == 0 && gstr.Contains(m.tables, ",") {
|
||||
// Multiple base tables.
|
||||
for _, s := range gstr.SplitAndTrim(m.tables, ",") {
|
||||
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(ctx, s))
|
||||
}
|
||||
}
|
||||
conditionArray.FilterEmpty()
|
||||
if conditionArray.Len() > 0 {
|
||||
return conditionArray.Join(" AND ")
|
||||
}
|
||||
// Only one table.
|
||||
fieldName, fieldType := m.GetFieldNameAndTypeForDelete(ctx, "", m.tablesInit)
|
||||
if fieldName != "" {
|
||||
return m.getConditionByFieldNameAndTypeForSoftDeleting(ctx, "", fieldName, fieldType)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getConditionOfTableStringForSoftDeleting does something as its name describes.
|
||||
// Examples for `s`:
|
||||
// - `test`.`demo` as b
|
||||
// - `test`.`demo` b
|
||||
// - `demo`
|
||||
// - demo
|
||||
func (m *softTimeMaintainer) getConditionOfTableStringForSoftDeleting(ctx context.Context, s string) string {
|
||||
var (
|
||||
table string
|
||||
schema string
|
||||
array1 = gstr.SplitAndTrim(s, " ")
|
||||
array2 = gstr.SplitAndTrim(array1[0], ".")
|
||||
)
|
||||
if len(array2) >= 2 {
|
||||
table = array2[1]
|
||||
schema = array2[0]
|
||||
} else {
|
||||
table = array2[0]
|
||||
}
|
||||
fieldName, fieldType := m.GetFieldNameAndTypeForDelete(ctx, schema, table)
|
||||
if fieldName == "" {
|
||||
return ""
|
||||
}
|
||||
if len(array1) >= 3 {
|
||||
return m.getConditionByFieldNameAndTypeForSoftDeleting(ctx, array1[2], fieldName, fieldType)
|
||||
}
|
||||
if len(array1) >= 2 {
|
||||
return m.getConditionByFieldNameAndTypeForSoftDeleting(ctx, array1[1], fieldName, fieldType)
|
||||
}
|
||||
return m.getConditionByFieldNameAndTypeForSoftDeleting(ctx, table, fieldName, fieldType)
|
||||
}
|
||||
|
||||
// GetDataByFieldNameAndTypeForDelete creates and returns the placeholder and value for
|
||||
// specified field name and type in soft-deleting scenario.
|
||||
func (m *softTimeMaintainer) GetDataByFieldNameAndTypeForDelete(
|
||||
ctx context.Context, fieldPrefix, fieldName string, fieldType LocalType,
|
||||
) (dataHolder string, dataValue any) {
|
||||
var (
|
||||
quotedFieldPrefix = m.db.GetCore().QuoteWord(fieldPrefix)
|
||||
quotedFieldName = m.db.GetCore().QuoteWord(fieldName)
|
||||
)
|
||||
if quotedFieldPrefix != "" {
|
||||
quotedFieldName = fmt.Sprintf(`%s.%s`, quotedFieldPrefix, quotedFieldName)
|
||||
}
|
||||
dataHolder = fmt.Sprintf(`%s=?`, quotedFieldName)
|
||||
dataValue = m.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldType, false)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *softTimeMaintainer) getConditionByFieldNameAndTypeForSoftDeleting(
|
||||
ctx context.Context, fieldPrefix, fieldName string, fieldType LocalType,
|
||||
) string {
|
||||
var (
|
||||
quotedFieldPrefix = m.db.GetCore().QuoteWord(fieldPrefix)
|
||||
quotedFieldName = m.db.GetCore().QuoteWord(fieldName)
|
||||
)
|
||||
if quotedFieldPrefix != "" {
|
||||
quotedFieldName = fmt.Sprintf(`%s.%s`, quotedFieldPrefix, quotedFieldName)
|
||||
}
|
||||
switch m.softTimeOption.SoftTimeType {
|
||||
case SoftTimeTypeAuto:
|
||||
switch fieldType {
|
||||
case LocalTypeDate, LocalTypeDatetime:
|
||||
return fmt.Sprintf(`%s IS NULL`, quotedFieldName)
|
||||
case LocalTypeInt, LocalTypeUint, LocalTypeInt64, LocalTypeBool:
|
||||
return fmt.Sprintf(`%s=0`, quotedFieldName)
|
||||
default:
|
||||
intlog.Errorf(
|
||||
ctx,
|
||||
`invalid field type "%s" of field name "%s" with prefix "%s" for soft deleting condition`,
|
||||
fieldType, fieldName, fieldPrefix,
|
||||
)
|
||||
}
|
||||
|
||||
case SoftTimeTypeTime:
|
||||
return fmt.Sprintf(`%s IS NULL`, quotedFieldName)
|
||||
|
||||
default:
|
||||
return fmt.Sprintf(`%s=0`, quotedFieldName)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetValueByFieldTypeForCreateOrUpdate creates and returns the value for specified field type,
|
||||
// usually for creating or updating operations.
|
||||
func (m *softTimeMaintainer) GetValueByFieldTypeForCreateOrUpdate(
|
||||
ctx context.Context, fieldType LocalType, isDeletedField bool,
|
||||
) any {
|
||||
var value any
|
||||
if isDeletedField {
|
||||
switch fieldType {
|
||||
case LocalTypeDate, LocalTypeDatetime:
|
||||
value = nil
|
||||
default:
|
||||
value = 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
switch m.softTimeOption.SoftTimeType {
|
||||
case SoftTimeTypeAuto:
|
||||
switch fieldType {
|
||||
case LocalTypeDate, LocalTypeDatetime:
|
||||
value = gtime.Now()
|
||||
case LocalTypeInt, LocalTypeUint, LocalTypeInt64:
|
||||
value = gtime.Timestamp()
|
||||
case LocalTypeBool:
|
||||
value = 1
|
||||
default:
|
||||
intlog.Errorf(
|
||||
ctx,
|
||||
`invalid field type "%s" for soft deleting data`,
|
||||
fieldType,
|
||||
)
|
||||
}
|
||||
|
||||
default:
|
||||
switch fieldType {
|
||||
case LocalTypeBool:
|
||||
value = 1
|
||||
default:
|
||||
value = m.createValueBySoftTimeOption(isDeletedField)
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (m *softTimeMaintainer) createValueBySoftTimeOption(isDeletedField bool) any {
|
||||
var value any
|
||||
if isDeletedField {
|
||||
switch m.softTimeOption.SoftTimeType {
|
||||
case SoftTimeTypeTime:
|
||||
value = nil
|
||||
default:
|
||||
value = 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
switch m.softTimeOption.SoftTimeType {
|
||||
case SoftTimeTypeTime:
|
||||
value = gtime.Now()
|
||||
case SoftTimeTypeTimestamp:
|
||||
value = gtime.Timestamp()
|
||||
case SoftTimeTypeTimestampMilli:
|
||||
value = gtime.TimestampMilli()
|
||||
case SoftTimeTypeTimestampMicro:
|
||||
value = gtime.TimestampMicro()
|
||||
case SoftTimeTypeTimestampNano:
|
||||
value = gtime.TimestampNano()
|
||||
default:
|
||||
panic(gerror.NewCodef(
|
||||
gcode.CodeInternalPanic,
|
||||
`unrecognized SoftTimeType "%d"`, m.softTimeOption.SoftTimeType,
|
||||
))
|
||||
}
|
||||
return value
|
||||
}
|
@ -1,181 +0,0 @@
|
||||
// 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 gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/container/garray"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
var (
|
||||
createdFieldNames = []string{"created_at", "create_at"} // Default field names of table for automatic-filled created datetime.
|
||||
updatedFieldNames = []string{"updated_at", "update_at"} // Default field names of table for automatic-filled updated datetime.
|
||||
deletedFieldNames = []string{"deleted_at", "delete_at"} // Default field names of table for automatic-filled deleted datetime.
|
||||
)
|
||||
|
||||
// Unscoped disables the auto-update time feature for insert, update and delete options.
|
||||
func (m *Model) Unscoped() *Model {
|
||||
model := m.getModel()
|
||||
model.unscoped = true
|
||||
return model
|
||||
}
|
||||
|
||||
// getSoftFieldNameCreate checks and returns the field name for record creating time.
|
||||
// If there's no field name for storing creating time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *Model) getSoftFieldNameCreated(schema string, table string) string {
|
||||
// It checks whether this feature disabled.
|
||||
if m.db.GetConfig().TimeMaintainDisabled {
|
||||
return ""
|
||||
}
|
||||
tableName := ""
|
||||
if table != "" {
|
||||
tableName = table
|
||||
} else {
|
||||
tableName = m.tablesInit
|
||||
}
|
||||
config := m.db.GetConfig()
|
||||
if config.CreatedAt != "" {
|
||||
return m.getSoftFieldName(schema, tableName, []string{config.CreatedAt})
|
||||
}
|
||||
return m.getSoftFieldName(schema, tableName, createdFieldNames)
|
||||
}
|
||||
|
||||
// getSoftFieldNameUpdate checks and returns the field name for record updating time.
|
||||
// If there's no field name for storing updating time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *Model) getSoftFieldNameUpdated(schema string, table string) (field string) {
|
||||
// It checks whether this feature disabled.
|
||||
if m.db.GetConfig().TimeMaintainDisabled {
|
||||
return ""
|
||||
}
|
||||
tableName := ""
|
||||
if table != "" {
|
||||
tableName = table
|
||||
} else {
|
||||
tableName = m.tablesInit
|
||||
}
|
||||
config := m.db.GetConfig()
|
||||
if config.UpdatedAt != "" {
|
||||
return m.getSoftFieldName(schema, tableName, []string{config.UpdatedAt})
|
||||
}
|
||||
return m.getSoftFieldName(schema, tableName, updatedFieldNames)
|
||||
}
|
||||
|
||||
// getSoftFieldNameDelete checks and returns the field name for record deleting time.
|
||||
// If there's no field name for storing deleting time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *Model) getSoftFieldNameDeleted(schema string, table string) (field string) {
|
||||
// It checks whether this feature disabled.
|
||||
if m.db.GetConfig().TimeMaintainDisabled {
|
||||
return ""
|
||||
}
|
||||
tableName := ""
|
||||
if table != "" {
|
||||
tableName = table
|
||||
} else {
|
||||
tableName = m.tablesInit
|
||||
}
|
||||
config := m.db.GetConfig()
|
||||
if config.DeletedAt != "" {
|
||||
return m.getSoftFieldName(schema, tableName, []string{config.DeletedAt})
|
||||
}
|
||||
return m.getSoftFieldName(schema, tableName, deletedFieldNames)
|
||||
}
|
||||
|
||||
// getSoftFieldName retrieves and returns the field name of the table for possible key.
|
||||
func (m *Model) getSoftFieldName(schema string, table string, keys []string) (field string) {
|
||||
// Ignore the error from TableFields.
|
||||
fieldsMap, _ := m.TableFields(table, schema)
|
||||
if len(fieldsMap) > 0 {
|
||||
for _, key := range keys {
|
||||
field, _ = gutil.MapPossibleItemByKey(
|
||||
gconv.Map(fieldsMap), key,
|
||||
)
|
||||
if field != "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getConditionForSoftDeleting retrieves and returns the condition string for soft deleting.
|
||||
// It supports multiple tables string like:
|
||||
// "user u, user_detail ud"
|
||||
// "user u LEFT JOIN user_detail ud ON(ud.uid=u.uid)"
|
||||
// "user LEFT JOIN user_detail ON(user_detail.uid=user.uid)"
|
||||
// "user u LEFT JOIN user_detail ud ON(ud.uid=u.uid) LEFT JOIN user_stats us ON(us.uid=u.uid)".
|
||||
func (m *Model) getConditionForSoftDeleting() string {
|
||||
if m.unscoped {
|
||||
return ""
|
||||
}
|
||||
conditionArray := garray.NewStrArray()
|
||||
if gstr.Contains(m.tables, " JOIN ") {
|
||||
// Base table.
|
||||
match, _ := gregex.MatchString(`(.+?) [A-Z]+ JOIN`, m.tables)
|
||||
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(match[1]))
|
||||
// Multiple joined tables, exclude the sub query sql which contains char '(' and ')'.
|
||||
matches, _ := gregex.MatchAllString(`JOIN ([^()]+?) ON`, m.tables)
|
||||
for _, match := range matches {
|
||||
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(match[1]))
|
||||
}
|
||||
}
|
||||
if conditionArray.Len() == 0 && gstr.Contains(m.tables, ",") {
|
||||
// Multiple base tables.
|
||||
for _, s := range gstr.SplitAndTrim(m.tables, ",") {
|
||||
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(s))
|
||||
}
|
||||
}
|
||||
conditionArray.FilterEmpty()
|
||||
if conditionArray.Len() > 0 {
|
||||
return conditionArray.Join(" AND ")
|
||||
}
|
||||
// Only one table.
|
||||
if fieldName := m.getSoftFieldNameDeleted("", m.tablesInit); fieldName != "" {
|
||||
return fmt.Sprintf(`%s IS NULL`, m.db.GetCore().QuoteWord(fieldName))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getConditionOfTableStringForSoftDeleting does something as its name describes.
|
||||
// Examples for `s`:
|
||||
// - `test`.`demo` as b
|
||||
// - `test`.`demo` b
|
||||
// - `demo`
|
||||
// - demo
|
||||
func (m *Model) getConditionOfTableStringForSoftDeleting(s string) string {
|
||||
var (
|
||||
field string
|
||||
table string
|
||||
schema string
|
||||
array1 = gstr.SplitAndTrim(s, " ")
|
||||
array2 = gstr.SplitAndTrim(array1[0], ".")
|
||||
)
|
||||
if len(array2) >= 2 {
|
||||
table = array2[1]
|
||||
schema = array2[0]
|
||||
} else {
|
||||
table = array2[0]
|
||||
}
|
||||
field = m.getSoftFieldNameDeleted(schema, table)
|
||||
if field == "" {
|
||||
return ""
|
||||
}
|
||||
if len(array1) >= 3 {
|
||||
return fmt.Sprintf(`%s.%s IS NULL`, m.db.GetCore().QuoteWord(array1[2]), m.db.GetCore().QuoteWord(field))
|
||||
}
|
||||
if len(array1) >= 2 {
|
||||
return fmt.Sprintf(`%s.%s IS NULL`, m.db.GetCore().QuoteWord(array1[1]), m.db.GetCore().QuoteWord(field))
|
||||
}
|
||||
return fmt.Sprintf(`%s.%s IS NULL`, m.db.GetCore().QuoteWord(table), m.db.GetCore().QuoteWord(field))
|
||||
}
|
@ -16,7 +16,6 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/reflection"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
@ -46,11 +45,14 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
return nil, gerror.NewCode(gcode.CodeMissingParameter, "updating table with empty data")
|
||||
}
|
||||
var (
|
||||
stm = m.softTimeMaintainer()
|
||||
updateData = m.data
|
||||
reflectInfo = reflection.OriginTypeAndKind(updateData)
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdated("", m.tablesInit)
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(ctx, false, false)
|
||||
conditionStr = conditionWhere + conditionExtra
|
||||
fieldNameUpdate, fieldTypeUpdate = stm.GetFieldNameAndTypeForUpdate(
|
||||
ctx, "", m.tablesInit,
|
||||
)
|
||||
)
|
||||
if m.unscoped {
|
||||
fieldNameUpdate = ""
|
||||
@ -61,7 +63,8 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
var dataMap = anyValueToMapBeforeToRecord(m.data)
|
||||
// Automatically update the record updating time.
|
||||
if fieldNameUpdate != "" {
|
||||
dataMap[fieldNameUpdate] = gtime.Now()
|
||||
dataValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeUpdate, false)
|
||||
dataMap[fieldNameUpdate] = dataValue
|
||||
}
|
||||
updateData = dataMap
|
||||
|
||||
@ -69,9 +72,10 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
updates := gconv.String(m.data)
|
||||
// Automatically update the record updating time.
|
||||
if fieldNameUpdate != "" {
|
||||
dataValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeUpdate, false)
|
||||
if fieldNameUpdate != "" && !gstr.Contains(updates, fieldNameUpdate) {
|
||||
updates += fmt.Sprintf(`,%s=?`, fieldNameUpdate)
|
||||
conditionArgs = append([]interface{}{gtime.Now()}, conditionArgs...)
|
||||
conditionArgs = append([]interface{}{dataValue}, conditionArgs...)
|
||||
}
|
||||
}
|
||||
updateData = updates
|
||||
|
@ -20,7 +20,7 @@ import (
|
||||
type Func func(ctx context.Context) (value interface{}, err error)
|
||||
|
||||
const (
|
||||
DurationNoExpire = 0 // Expire duration that never expires.
|
||||
DurationNoExpire = time.Duration(0) // Expire duration that never expires.
|
||||
)
|
||||
|
||||
// Default cache object.
|
||||
|
Loading…
Reference in New Issue
Block a user