mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
feat(database/gdb): Optimize SoftTime feature (#4559)
本次PR主要针对GoFrame ORM中的软删除`SoftTime`功能进行了优化 - 新增`SoftTimeFieldType`枚举类型,用于区分创建、更新、删除三种不同的软时间字段 - 替代之前使用的魔数方式,提高类型安全性 - 将原有的6个方法精简为4个方法, 合并了三个几乎相同的`GetFieldNameAndTypeFor*`方法为统一的方法 This pull request refactors and simplifies the "soft time" (created/updated/deleted timestamp) handling logic in the database layer, making the codebase more maintainable and extensible. The changes consolidate multiple similar methods into general-purpose ones, improve cache key generation, and clarify the logic for generating and applying soft time field values and conditions. Key changes include: **Soft Time API Refactoring and Simplification** - Consolidated multiple methods (`GetFieldNameAndTypeForCreate`, `GetFieldNameAndTypeForUpdate`, `GetFieldNameAndTypeForDelete`) into a single, parameterized method `GetFieldInfo`, reducing code duplication and making it easier to support new soft time field types. The interface and implementation for soft time maintenance (`iSoftTimeMaintainer`, `softTimeMaintainer`) have been updated accordingly. [[1]](diffhunk://#diff-6c1d606032d981a7b8aecd3a7167823f76b69407a29eb9a244175a82f59965d8L46-R66) [[2]](diffhunk://#diff-6c1d606032d981a7b8aecd3a7167823f76b69407a29eb9a244175a82f59965d8L105-R180) - Combined and renamed methods for generating soft time field values and delete conditions, such as merging `GetValueByFieldTypeForCreateOrUpdate` into `GetFieldValue`, and `GetWhereConditionForDelete` into `GetDeleteCondition`. Related usages throughout the codebase have been updated to use the new methods. [[1]](diffhunk://#diff-6c1d606032d981a7b8aecd3a7167823f76b69407a29eb9a244175a82f59965d8L255-R206) [[2]](diffhunk://#diff-97beb485550e4381182a04bbb857a25b7f4ecd4a594dff8ac884cfaae38f3046L34-R35) [[3]](diffhunk://#diff-97beb485550e4381182a04bbb857a25b7f4ecd4a594dff8ac884cfaae38f3046L55-R55) [[4]](diffhunk://#diff-88304ddb7791aedbd83dafb68374aecab286d1356a7f2f149a8e57ac1a7f40b4L265-R267) [[5]](diffhunk://#diff-88304ddb7791aedbd83dafb68374aecab286d1356a7f2f149a8e57ac1a7f40b4L298-R311) [[6]](diffhunk://#diff-d4f6e0370e049dea52f3db9a13c64e2cfb2f7ef012433186e21179149b626d0fL944-R944) **Soft Delete Logic Improvements** - Refactored the logic for building soft delete WHERE conditions and generating update data, with clearer and more robust handling of field types and prefixes. Introduced helper methods like `buildDeleteCondition`, `GetDeleteData`, and improved error logging for invalid field types. [[1]](diffhunk://#diff-6c1d606032d981a7b8aecd3a7167823f76b69407a29eb9a244175a82f59965d8L287-R234) [[2]](diffhunk://#diff-6c1d606032d981a7b8aecd3a7167823f76b69407a29eb9a244175a82f59965d8L313-R395) **Cache Key Generation Enhancements** - Added dedicated helper functions for generating cache keys for table names, table fields, select queries, and soft time field/type lookups, improving cache consistency and code readability. [[1]](diffhunk://#diff-d57d57e6f9b342ba6fa30c4bb413e2f4f3514a8cd5ad36949eef126e5f8b7ac9R969) [[2]](diffhunk://#diff-d57d57e6f9b342ba6fa30c4bb413e2f4f3514a8cd5ad36949eef126e5f8b7ac9R980) [[3]](diffhunk://#diff-d57d57e6f9b342ba6fa30c4bb413e2f4f3514a8cd5ad36949eef126e5f8b7ac9R993-R1002) [[4]](diffhunk://#diff-b1bbe5e3995261813e4e0ac6ffee8a37c236eaa2759f2bd82e211711695a70bcL790-R790) **General Code Cleanup** - Removed redundant code, clarified comments, and improved naming throughout the affected files, making the code easier to follow and maintain. [[1]](diffhunk://#diff-6c1d606032d981a7b8aecd3a7167823f76b69407a29eb9a244175a82f59965d8L105-R180) [[2]](diffhunk://#diff-6c1d606032d981a7b8aecd3a7167823f76b69407a29eb9a244175a82f59965d8L255-R206) Let me know if you'd like a walkthrough of any specific part of the refactored soft time logic!
This commit is contained in:
@ -787,7 +787,7 @@ func (c *Core) SetTableFields(ctx context.Context, table string, fields map[stri
|
||||
func (c *Core) GetTablesWithCache() ([]string, error) {
|
||||
var (
|
||||
ctx = c.db.GetCtx()
|
||||
cacheKey = fmt.Sprintf(`Tables:%s`, c.db.GetGroup())
|
||||
cacheKey = genTableNamesCacheKey(c.db.GetGroup())
|
||||
cacheDuration = gcache.DurationNoExpire
|
||||
innerMemCache = c.GetInnerMemCache()
|
||||
)
|
||||
|
||||
@ -966,6 +966,7 @@ func FormatMultiLineSqlToSingle(sql string) (string, error) {
|
||||
return sql, nil
|
||||
}
|
||||
|
||||
// genTableFieldsCacheKey generates cache key for table fields.
|
||||
func genTableFieldsCacheKey(group, schema, table string) string {
|
||||
return fmt.Sprintf(
|
||||
`%s%s@%s#%s`,
|
||||
@ -976,6 +977,7 @@ func genTableFieldsCacheKey(group, schema, table string) string {
|
||||
)
|
||||
}
|
||||
|
||||
// genSelectCacheKey generates cache key for select.
|
||||
func genSelectCacheKey(table, group, schema, name, sql string, args ...any) string {
|
||||
if name == "" {
|
||||
name = fmt.Sprintf(
|
||||
@ -988,3 +990,13 @@ func genSelectCacheKey(table, group, schema, name, sql string, args ...any) stri
|
||||
}
|
||||
return fmt.Sprintf(`%s%s`, cachePrefixSelectCache, name)
|
||||
}
|
||||
|
||||
// genTableNamesCacheKey generates cache key for table names.
|
||||
func genTableNamesCacheKey(group string) string {
|
||||
return fmt.Sprintf(`Tables:%s`, group)
|
||||
}
|
||||
|
||||
// genSoftTimeFieldNameTypeCacheKey generates cache key for soft time field name and type.
|
||||
func genSoftTimeFieldNameTypeCacheKey(schema, table string, candidateFields []string) string {
|
||||
return fmt.Sprintf(`getSoftFieldNameAndType:%s#%s#%s`, schema, table, strings.Join(candidateFields, "_"))
|
||||
}
|
||||
|
||||
@ -31,9 +31,7 @@ func (m *Model) Delete(where ...any) (result sql.Result, err error) {
|
||||
var (
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(ctx, false, false)
|
||||
conditionStr = conditionWhere + conditionExtra
|
||||
fieldNameDelete, fieldTypeDelete = m.softTimeMaintainer().GetFieldNameAndTypeForDelete(
|
||||
ctx, "", m.tablesInit,
|
||||
)
|
||||
fieldNameDelete, fieldTypeDelete = m.softTimeMaintainer().GetFieldInfo(ctx, "", m.tablesInit, SoftTimeFieldDelete)
|
||||
)
|
||||
if m.unscoped {
|
||||
fieldNameDelete = ""
|
||||
@ -52,7 +50,7 @@ func (m *Model) Delete(where ...any) (result sql.Result, err error) {
|
||||
|
||||
// Soft deleting.
|
||||
if fieldNameDelete != "" {
|
||||
dataHolder, dataValue := m.softTimeMaintainer().GetDataByFieldNameAndTypeForDelete(
|
||||
dataHolder, dataValue := m.softTimeMaintainer().GetDeleteData(
|
||||
ctx, "", fieldNameDelete, fieldTypeDelete,
|
||||
)
|
||||
in := &HookUpdateInput{
|
||||
|
||||
@ -262,9 +262,9 @@ func (m *Model) doInsertWithOption(ctx context.Context, insertOption InsertOptio
|
||||
var (
|
||||
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)
|
||||
fieldNameCreate, fieldTypeCreate = stm.GetFieldInfo(ctx, "", m.tablesInit, SoftTimeFieldCreate)
|
||||
fieldNameUpdate, fieldTypeUpdate = stm.GetFieldInfo(ctx, "", m.tablesInit, SoftTimeFieldUpdate)
|
||||
fieldNameDelete, fieldTypeDelete = stm.GetFieldInfo(ctx, "", m.tablesInit, SoftTimeFieldDelete)
|
||||
)
|
||||
// m.data was already converted to type List/Map by function Data
|
||||
newData, err := m.filterDataForInsertOrUpdate(m.data)
|
||||
@ -295,20 +295,20 @@ func (m *Model) doInsertWithOption(ctx context.Context, insertOption InsertOptio
|
||||
if !m.unscoped && isSoftTimeFeatureEnabled {
|
||||
for k, v := range list {
|
||||
if fieldNameCreate != "" && empty.IsNil(v[fieldNameCreate]) {
|
||||
fieldCreateValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeCreate, false)
|
||||
fieldCreateValue := stm.GetFieldValue(ctx, fieldTypeCreate, false)
|
||||
if fieldCreateValue != nil {
|
||||
v[fieldNameCreate] = fieldCreateValue
|
||||
}
|
||||
}
|
||||
if fieldNameUpdate != "" && empty.IsNil(v[fieldNameUpdate]) {
|
||||
fieldUpdateValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeUpdate, false)
|
||||
fieldUpdateValue := stm.GetFieldValue(ctx, fieldTypeUpdate, false)
|
||||
if fieldUpdateValue != nil {
|
||||
v[fieldNameUpdate] = fieldUpdateValue
|
||||
}
|
||||
}
|
||||
// for timestamp field that should initialize the delete_at field with value, for example 0.
|
||||
if fieldNameDelete != "" && empty.IsNil(v[fieldNameDelete]) {
|
||||
fieldDeleteValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeDelete, true)
|
||||
fieldDeleteValue := stm.GetFieldValue(ctx, fieldTypeDelete, true)
|
||||
if fieldDeleteValue != nil {
|
||||
v[fieldNameDelete] = fieldDeleteValue
|
||||
}
|
||||
|
||||
@ -941,7 +941,7 @@ func (m *Model) formatCondition(
|
||||
}
|
||||
// WHERE
|
||||
conditionWhere, conditionArgs = m.whereBuilder.Build()
|
||||
softDeletingCondition := m.softTimeMaintainer().GetWhereConditionForDelete(ctx)
|
||||
softDeletingCondition := m.softTimeMaintainer().GetDeleteCondition(ctx)
|
||||
if m.rawSql != "" && conditionWhere != "" {
|
||||
if gstr.ContainsI(m.rawSql, " WHERE ") {
|
||||
conditionWhere = " AND " + conditionWhere
|
||||
|
||||
@ -43,28 +43,27 @@ type softTimeMaintainer struct {
|
||||
*Model
|
||||
}
|
||||
|
||||
// SoftTimeFieldType represents different soft time field purposes.
|
||||
type SoftTimeFieldType int
|
||||
|
||||
const (
|
||||
SoftTimeFieldCreate SoftTimeFieldType = iota
|
||||
SoftTimeFieldUpdate
|
||||
SoftTimeFieldDelete
|
||||
)
|
||||
|
||||
type iSoftTimeMaintainer interface {
|
||||
GetFieldNameAndTypeForCreate(
|
||||
ctx context.Context, schema string, table string,
|
||||
) (fieldName string, fieldType LocalType)
|
||||
// GetFieldInfo returns field name and type for specified field purpose.
|
||||
GetFieldInfo(ctx context.Context, schema, table string, fieldPurpose SoftTimeFieldType) (fieldName string, localType LocalType)
|
||||
|
||||
GetFieldNameAndTypeForUpdate(
|
||||
ctx context.Context, schema string, table string,
|
||||
) (fieldName string, fieldType LocalType)
|
||||
// GetFieldValue generates value for create/update/delete operations.
|
||||
GetFieldValue(ctx context.Context, localType LocalType, isDeleted bool) any
|
||||
|
||||
GetFieldNameAndTypeForDelete(
|
||||
ctx context.Context, schema string, table string,
|
||||
) (fieldName string, fieldType LocalType)
|
||||
// GetDeleteCondition returns WHERE condition for soft delete query.
|
||||
GetDeleteCondition(ctx context.Context) string
|
||||
|
||||
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
|
||||
// GetDeleteData returns UPDATE statement data for soft delete.
|
||||
GetDeleteData(ctx context.Context, prefix, fieldName string, localType LocalType) (holder string, value any)
|
||||
}
|
||||
|
||||
// getSoftFieldNameAndTypeCacheItem is the internal struct for storing create/update/delete fields.
|
||||
@ -102,137 +101,83 @@ func (m *Model) softTimeMaintainer() iSoftTimeMaintainer {
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// GetFieldInfo returns field name and type for specified field purpose.
|
||||
// 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.
|
||||
func (m *softTimeMaintainer) GetFieldInfo(
|
||||
ctx context.Context, schema, table string, fieldPurpose SoftTimeFieldType,
|
||||
) (fieldName string, localType LocalType) {
|
||||
// Check if feature is 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 {
|
||||
// Determine table name
|
||||
tableName := table
|
||||
if tableName == "" {
|
||||
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
|
||||
}
|
||||
// Get config and field candidates
|
||||
config := m.db.GetConfig()
|
||||
if config.DeletedAt != "" {
|
||||
return m.getSoftFieldNameAndType(
|
||||
ctx, schema, tableName, []string{config.DeletedAt},
|
||||
)
|
||||
}
|
||||
return m.getSoftFieldNameAndType(
|
||||
ctx, schema, tableName, deletedFieldNames,
|
||||
var (
|
||||
configField string
|
||||
defaultFields []string
|
||||
)
|
||||
|
||||
switch fieldPurpose {
|
||||
case SoftTimeFieldCreate:
|
||||
configField = config.CreatedAt
|
||||
defaultFields = createdFieldNames
|
||||
case SoftTimeFieldUpdate:
|
||||
configField = config.UpdatedAt
|
||||
defaultFields = updatedFieldNames
|
||||
case SoftTimeFieldDelete:
|
||||
configField = config.DeletedAt
|
||||
defaultFields = deletedFieldNames
|
||||
}
|
||||
|
||||
// Use config field if specified, otherwise use defaults
|
||||
if configField != "" {
|
||||
return m.getSoftFieldNameAndType(ctx, schema, tableName, []string{configField})
|
||||
}
|
||||
return m.getSoftFieldNameAndType(ctx, schema, tableName, defaultFields)
|
||||
}
|
||||
|
||||
// getSoftFieldNameAndType 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,
|
||||
ctx context.Context, schema, table string, candidateFields []string,
|
||||
) (fieldName string, fieldType LocalType) {
|
||||
var (
|
||||
innerMemCache = m.db.GetCore().GetInnerMemCache()
|
||||
cacheKey = fmt.Sprintf(
|
||||
`getSoftFieldNameAndType:%s#%s#%s`,
|
||||
schema, table, strings.Join(checkFiledNames, "_"),
|
||||
)
|
||||
cacheDuration = gcache.DurationNoExpire
|
||||
cacheFunc = func(ctx context.Context) (value any, err error) {
|
||||
// Ignore the error from TableFields.
|
||||
fieldsMap, err := m.TableFields(table, schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(fieldsMap) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
for _, checkFiledName := range checkFiledNames {
|
||||
fieldName = searchFieldNameFromMap(fieldsMap, checkFiledName)
|
||||
if fieldName != "" {
|
||||
fieldType, _ = m.db.CheckLocalTypeForField(
|
||||
ctx, fieldsMap[fieldName].Type, nil,
|
||||
)
|
||||
var cacheItem = getSoftFieldNameAndTypeCacheItem{
|
||||
FieldName: fieldName,
|
||||
FieldType: fieldType,
|
||||
}
|
||||
return cacheItem, nil
|
||||
}
|
||||
}
|
||||
return
|
||||
// Build cache key
|
||||
cacheKey := genSoftTimeFieldNameTypeCacheKey(schema, table, candidateFields)
|
||||
|
||||
// Try to get from cache
|
||||
cache := m.db.GetCore().GetInnerMemCache()
|
||||
result, err := cache.GetOrSetFunc(ctx, cacheKey, func(ctx context.Context) (any, error) {
|
||||
// Get table fields
|
||||
fieldsMap, err := m.TableFields(table, schema)
|
||||
if err != nil || len(fieldsMap) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
)
|
||||
result, err := innerMemCache.GetOrSetFunc(
|
||||
ctx, cacheKey, cacheFunc, cacheDuration,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
// Search for matching field
|
||||
for _, field := range candidateFields {
|
||||
if name := searchFieldNameFromMap(fieldsMap, field); name != "" {
|
||||
fType, _ := m.db.CheckLocalTypeForField(ctx, fieldsMap[name].Type, nil)
|
||||
return getSoftFieldNameAndTypeCacheItem{
|
||||
FieldName: name,
|
||||
FieldType: fType,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}, gcache.DurationNoExpire)
|
||||
|
||||
if err != nil || result == nil {
|
||||
return "", LocalTypeUndefined
|
||||
}
|
||||
if result == nil {
|
||||
return
|
||||
}
|
||||
cacheItem := result.Val().(getSoftFieldNameAndTypeCacheItem)
|
||||
fieldName = cacheItem.FieldName
|
||||
fieldType = cacheItem.FieldType
|
||||
return
|
||||
|
||||
item := result.Val().(getSoftFieldNameAndTypeCacheItem)
|
||||
return item.FieldName, item.FieldType
|
||||
}
|
||||
|
||||
func searchFieldNameFromMap(fieldsMap map[string]*TableField, key string) string {
|
||||
@ -252,13 +197,13 @@ func searchFieldNameFromMap(fieldsMap map[string]*TableField, key string) string
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetWhereConditionForDelete retrieves and returns the condition string for soft deleting.
|
||||
// GetDeleteCondition returns WHERE condition for soft delete query.
|
||||
// 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 {
|
||||
func (m *softTimeMaintainer) GetDeleteCondition(ctx context.Context) string {
|
||||
if m.unscoped {
|
||||
return ""
|
||||
}
|
||||
@ -284,9 +229,9 @@ func (m *softTimeMaintainer) GetWhereConditionForDelete(ctx context.Context) str
|
||||
return conditionArray.Join(" AND ")
|
||||
}
|
||||
// Only one table.
|
||||
fieldName, fieldType := m.GetFieldNameAndTypeForDelete(ctx, "", m.tablesInit)
|
||||
fieldName, fieldType := m.GetFieldInfo(ctx, "", m.tablesInit, SoftTimeFieldDelete)
|
||||
if fieldName != "" {
|
||||
return m.getConditionByFieldNameAndTypeForSoftDeleting(ctx, "", fieldName, fieldType)
|
||||
return m.buildDeleteCondition(ctx, "", fieldName, fieldType)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@ -310,141 +255,130 @@ func (m *softTimeMaintainer) getConditionOfTableStringForSoftDeleting(ctx contex
|
||||
} else {
|
||||
table = array2[0]
|
||||
}
|
||||
fieldName, fieldType := m.GetFieldNameAndTypeForDelete(ctx, schema, table)
|
||||
fieldName, fieldType := m.GetFieldInfo(ctx, schema, table, SoftTimeFieldDelete)
|
||||
if fieldName == "" {
|
||||
return ""
|
||||
}
|
||||
if len(array1) >= 3 {
|
||||
return m.getConditionByFieldNameAndTypeForSoftDeleting(ctx, array1[2], fieldName, fieldType)
|
||||
return m.buildDeleteCondition(ctx, array1[2], fieldName, fieldType)
|
||||
}
|
||||
if len(array1) >= 2 {
|
||||
return m.getConditionByFieldNameAndTypeForSoftDeleting(ctx, array1[1], fieldName, fieldType)
|
||||
return m.buildDeleteCondition(ctx, array1[1], fieldName, fieldType)
|
||||
}
|
||||
return m.getConditionByFieldNameAndTypeForSoftDeleting(ctx, table, fieldName, fieldType)
|
||||
return m.buildDeleteCondition(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)
|
||||
// GetDeleteData returns UPDATE statement data for soft delete.
|
||||
func (m *softTimeMaintainer) GetDeleteData(
|
||||
ctx context.Context, prefix, fieldName string, fieldType LocalType,
|
||||
) (holder string, value any) {
|
||||
core := m.db.GetCore()
|
||||
quotedName := core.QuoteWord(fieldName)
|
||||
|
||||
if prefix != "" {
|
||||
quotedName = fmt.Sprintf(`%s.%s`, core.QuoteWord(prefix), quotedName)
|
||||
}
|
||||
dataHolder = fmt.Sprintf(`%s=?`, quotedFieldName)
|
||||
dataValue = m.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldType, false)
|
||||
|
||||
holder = fmt.Sprintf(`%s=?`, quotedName)
|
||||
value = m.GetFieldValue(ctx, fieldType, false)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *softTimeMaintainer) getConditionByFieldNameAndTypeForSoftDeleting(
|
||||
ctx context.Context, fieldPrefix, fieldName string, fieldType LocalType,
|
||||
// buildDeleteCondition builds WHERE condition for soft delete filtering.
|
||||
func (m *softTimeMaintainer) buildDeleteCondition(
|
||||
ctx context.Context, prefix, 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)
|
||||
core := m.db.GetCore()
|
||||
quotedName := core.QuoteWord(fieldName)
|
||||
|
||||
if prefix != "" {
|
||||
quotedName = fmt.Sprintf(`%s.%s`, core.QuoteWord(prefix), quotedName)
|
||||
}
|
||||
switch m.softTimeOption.SoftTimeType {
|
||||
case SoftTimeTypeAuto:
|
||||
switch fieldType {
|
||||
case LocalTypeDate, LocalTypeTime, LocalTypeDatetime:
|
||||
return fmt.Sprintf(`%s IS NULL`, quotedFieldName)
|
||||
return fmt.Sprintf(`%s IS NULL`, quotedName)
|
||||
case LocalTypeInt, LocalTypeUint, LocalTypeInt64, LocalTypeUint64, LocalTypeBool:
|
||||
return fmt.Sprintf(`%s=0`, quotedFieldName)
|
||||
return fmt.Sprintf(`%s=0`, quotedName)
|
||||
default:
|
||||
intlog.Errorf(
|
||||
ctx,
|
||||
`invalid field type "%s" of field name "%s" with prefix "%s" for soft deleting condition`,
|
||||
fieldType, fieldName, fieldPrefix,
|
||||
)
|
||||
intlog.Errorf(ctx, `invalid field type "%s" for soft delete condition: prefix=%s, field=%s`, fieldType, prefix, fieldName)
|
||||
return ""
|
||||
}
|
||||
|
||||
case SoftTimeTypeTime:
|
||||
return fmt.Sprintf(`%s IS NULL`, quotedFieldName)
|
||||
return fmt.Sprintf(`%s IS NULL`, quotedName)
|
||||
|
||||
default:
|
||||
return fmt.Sprintf(`%s=0`, quotedFieldName)
|
||||
return fmt.Sprintf(`%s=0`, quotedName)
|
||||
}
|
||||
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,
|
||||
// GetFieldValue generates value for create/update/delete operations.
|
||||
func (m *softTimeMaintainer) GetFieldValue(
|
||||
ctx context.Context, fieldType LocalType, isDeleted bool,
|
||||
) any {
|
||||
var value any
|
||||
// for create or update procedure, the deleted field is always set to non-deleted value.
|
||||
if isDeletedField {
|
||||
switch fieldType {
|
||||
case LocalTypeDate, LocalTypeTime, LocalTypeDatetime:
|
||||
value = nil
|
||||
default:
|
||||
value = 0
|
||||
}
|
||||
return value
|
||||
// For deleted field, return "empty" value
|
||||
if isDeleted {
|
||||
return m.getEmptyValue(fieldType)
|
||||
}
|
||||
|
||||
// For create/update/delete, return current time value
|
||||
switch m.softTimeOption.SoftTimeType {
|
||||
case SoftTimeTypeAuto:
|
||||
switch fieldType {
|
||||
case LocalTypeDate, LocalTypeTime, LocalTypeDatetime:
|
||||
value = gtime.Now()
|
||||
case LocalTypeInt, LocalTypeUint, LocalTypeInt64, LocalTypeUint64:
|
||||
value = gtime.Timestamp()
|
||||
case LocalTypeBool:
|
||||
value = 1
|
||||
default:
|
||||
intlog.Errorf(
|
||||
ctx,
|
||||
`invalid field type "%s" for soft deleting data`,
|
||||
fieldType,
|
||||
)
|
||||
}
|
||||
|
||||
return m.getAutoValue(ctx, fieldType)
|
||||
default:
|
||||
switch fieldType {
|
||||
case LocalTypeBool:
|
||||
value = 1
|
||||
return 1
|
||||
default:
|
||||
value = m.createValueBySoftTimeOption(isDeletedField)
|
||||
return m.getTimestampValue()
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
// getTimestampValue returns timestamp value for soft time.
|
||||
func (m *softTimeMaintainer) getTimestampValue() any {
|
||||
switch m.softTimeOption.SoftTimeType {
|
||||
case SoftTimeTypeTime:
|
||||
value = gtime.Now()
|
||||
return gtime.Now()
|
||||
case SoftTimeTypeTimestamp:
|
||||
value = gtime.Timestamp()
|
||||
return gtime.Timestamp()
|
||||
case SoftTimeTypeTimestampMilli:
|
||||
value = gtime.TimestampMilli()
|
||||
return gtime.TimestampMilli()
|
||||
case SoftTimeTypeTimestampMicro:
|
||||
value = gtime.TimestampMicro()
|
||||
return gtime.TimestampMicro()
|
||||
case SoftTimeTypeTimestampNano:
|
||||
value = gtime.TimestampNano()
|
||||
return gtime.TimestampNano()
|
||||
default:
|
||||
panic(gerror.NewCodef(
|
||||
gcode.CodeInternalPanic,
|
||||
`unrecognized SoftTimeType "%d"`, m.softTimeOption.SoftTimeType,
|
||||
))
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// getEmptyValue returns "empty" value for deleted field.
|
||||
func (m *softTimeMaintainer) getEmptyValue(fieldType LocalType) any {
|
||||
switch fieldType {
|
||||
case LocalTypeDate, LocalTypeTime, LocalTypeDatetime:
|
||||
return nil
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// getAutoValue returns auto-detected value based on field type.
|
||||
func (m *softTimeMaintainer) getAutoValue(ctx context.Context, fieldType LocalType) any {
|
||||
switch fieldType {
|
||||
case LocalTypeDate, LocalTypeTime, LocalTypeDatetime:
|
||||
return gtime.Now()
|
||||
case LocalTypeInt, LocalTypeUint, LocalTypeInt64, LocalTypeUint64:
|
||||
return gtime.Timestamp()
|
||||
case LocalTypeBool:
|
||||
return 1
|
||||
default:
|
||||
intlog.Errorf(ctx, `invalid field type "%s" for soft time auto value`, fieldType)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,9 +50,7 @@ func (m *Model) Update(dataAndWhere ...any) (result sql.Result, err error) {
|
||||
reflectInfo = reflection.OriginTypeAndKind(m.data)
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(ctx, false, false)
|
||||
conditionStr = conditionWhere + conditionExtra
|
||||
fieldNameUpdate, fieldTypeUpdate = stm.GetFieldNameAndTypeForUpdate(
|
||||
ctx, "", m.tablesInit,
|
||||
)
|
||||
fieldNameUpdate, fieldTypeUpdate = stm.GetFieldInfo(ctx, "", m.tablesInit, SoftTimeFieldUpdate)
|
||||
)
|
||||
if fieldNameUpdate != "" && (m.unscoped || m.isFieldInFieldsEx(fieldNameUpdate)) {
|
||||
fieldNameUpdate = ""
|
||||
@ -68,7 +66,7 @@ func (m *Model) Update(dataAndWhere ...any) (result sql.Result, err error) {
|
||||
var dataMap = anyValueToMapBeforeToRecord(newData)
|
||||
// Automatically update the record updating time.
|
||||
if fieldNameUpdate != "" && empty.IsNil(dataMap[fieldNameUpdate]) {
|
||||
dataValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeUpdate, false)
|
||||
dataValue := stm.GetFieldValue(ctx, fieldTypeUpdate, false)
|
||||
dataMap[fieldNameUpdate] = dataValue
|
||||
}
|
||||
newData = dataMap
|
||||
@ -77,7 +75,7 @@ func (m *Model) Update(dataAndWhere ...any) (result sql.Result, err error) {
|
||||
var updateStr = gconv.String(newData)
|
||||
// Automatically update the record updating time.
|
||||
if fieldNameUpdate != "" && !gstr.Contains(updateStr, fieldNameUpdate) {
|
||||
dataValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeUpdate, false)
|
||||
dataValue := stm.GetFieldValue(ctx, fieldTypeUpdate, false)
|
||||
updateStr += fmt.Sprintf(`,%s=?`, fieldNameUpdate)
|
||||
conditionArgs = append([]any{dataValue}, conditionArgs...)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user