Files
gf/database/gdb/gdb_model_soft_time.go
hailaz ee24da4e72 refactor: interface{} to any and reflect.Ptr to reflect.Pointer (#4395)
This pull request standardizes the use of the Go 1.18+ `any` type alias
instead of `interface{}` throughout the codebase. The change improves
code readability and aligns with modern Go best practices. The update
touches many files, including core data structures, code generation
templates, logging utilities, and test data, ensuring consistency across
all usages.

**Type alias migration to `any`:**

* Replaced all instances of `interface{}` with `any` in core data
structures such as `garray` and in generated model structs (e.g.,
`TableUser`, `User1`, `User2`) to modernize type usage.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[3]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[4]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[5]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[6]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
* Updated function signatures, method parameters, and return types from
`interface{}` to `any` in various parts of the codebase, including code
generation, service logic, and logging utilities (e.g., `mlog`).
[[1]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[2]](diffhunk://#diff-2b1953fb78cf3593d8c2c7d911e95b65fd0b847c30ed0b4d167d16fe6d781235L54-R74)
[[3]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[4]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)
[[5]](diffhunk://#diff-c5d51d56f487779a2b6207c7ad26c7a20bbadcc846ce094fe60ab4cabff58c51L107-R107)
[[6]](diffhunk://#diff-f96e6a9fdb416eb1804ceaba1fe0ac637bff22c43837f8bb849c2366ce72d4a1L116-R121)
[[7]](diffhunk://#diff-f94c83a1b08ae060d9346f4a6031fc4a7b9a0b894e02d9afaa09018b6598eac0L112-R112)
[[8]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L36-R36)
[[9]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L74-R74)
[[10]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L96-R96)

**Generated code and templates:**

* Adjusted generated files and code generation templates to output `any`
instead of `interface{}` for relevant struct fields and function
signatures, ensuring that new code generation aligns with the updated
convention.
[[1]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[2]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[3]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[4]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[5]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
[[6]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[7]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[8]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)

**Container and utility updates:**

* Refactored the `garray` container implementation and related
constructors/methods to use `[]any` instead of `[]interface{}`, along
with corresponding function signatures.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L52-R52)
[[3]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L62-R62)
[[4]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L73-R86)
[[5]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L96-R97)
[[6]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L107-R114)
[[7]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L124-R124)
[[8]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L135-R143)
[[9]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L167-R167)

These changes collectively modernize the codebase and prepare it for
future Go developments by using the idiomatic `any` type.
2025-08-28 16:53:19 +08:00

450 lines
13 KiB
Go

// 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"
"strings"
"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/internal/utils"
"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"
)
// 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,
)
}
// 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,
) (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
}
)
result, err := innerMemCache.GetOrSetFunc(
ctx, cacheKey, cacheFunc, cacheDuration,
)
if err != nil {
return
}
if result == nil {
return
}
cacheItem := result.Val().(getSoftFieldNameAndTypeCacheItem)
fieldName = cacheItem.FieldName
fieldType = cacheItem.FieldType
return
}
func searchFieldNameFromMap(fieldsMap map[string]*TableField, key string) string {
if len(fieldsMap) == 0 {
return ""
}
_, ok := fieldsMap[key]
if ok {
return key
}
key = utils.RemoveSymbols(key)
for k := range fieldsMap {
if strings.EqualFold(utils.RemoveSymbols(k), key) {
return k
}
}
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, LocalTypeTime, LocalTypeDatetime:
return fmt.Sprintf(`%s IS NULL`, quotedFieldName)
case LocalTypeInt, LocalTypeUint, LocalTypeInt64, LocalTypeUint64, 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, LocalTypeTime, LocalTypeDatetime:
value = nil
default:
value = 0
}
return 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,
)
}
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
}