mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
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.
350 lines
10 KiB
Go
350 lines
10 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 (
|
|
"database/sql"
|
|
"reflect"
|
|
|
|
"github.com/gogf/gf/v2/errors/gcode"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/internal/utils"
|
|
"github.com/gogf/gf/v2/os/gstructs"
|
|
"github.com/gogf/gf/v2/text/gstr"
|
|
"github.com/gogf/gf/v2/util/gutil"
|
|
)
|
|
|
|
// With creates and returns an ORM model based on metadata of given object.
|
|
// It also enables model association operations feature on given `object`.
|
|
// It can be called multiple times to add one or more objects to model and enable
|
|
// their mode association operations feature.
|
|
// For example, if given struct definition:
|
|
//
|
|
// type User struct {
|
|
// gmeta.Meta `orm:"table:user"`
|
|
// Id int `json:"id"`
|
|
// Name string `json:"name"`
|
|
// UserDetail *UserDetail `orm:"with:uid=id"`
|
|
// UserScores []*UserScores `orm:"with:uid=id"`
|
|
// }
|
|
//
|
|
// We can enable model association operations on attribute `UserDetail` and `UserScores` by:
|
|
//
|
|
// db.With(User{}.UserDetail).With(User{}.UserScores).Scan(xxx)
|
|
//
|
|
// Or:
|
|
//
|
|
// db.With(UserDetail{}).With(UserScores{}).Scan(xxx)
|
|
//
|
|
// Or:
|
|
//
|
|
// db.With(UserDetail{}, UserScores{}).Scan(xxx)
|
|
func (m *Model) With(objects ...any) *Model {
|
|
model := m.getModel()
|
|
for _, object := range objects {
|
|
if m.tables == "" {
|
|
m.tablesInit = m.db.GetCore().QuotePrefixTableName(
|
|
getTableNameFromOrmTag(object),
|
|
)
|
|
m.tables = m.tablesInit
|
|
return model
|
|
}
|
|
model.withArray = append(model.withArray, object)
|
|
}
|
|
return model
|
|
}
|
|
|
|
// WithAll enables model association operations on all objects that have "with" tag in the struct.
|
|
func (m *Model) WithAll() *Model {
|
|
model := m.getModel()
|
|
model.withAll = true
|
|
return model
|
|
}
|
|
|
|
// doWithScanStruct handles model association operations feature for single struct.
|
|
func (m *Model) doWithScanStruct(pointer any) error {
|
|
if len(m.withArray) == 0 && !m.withAll {
|
|
return nil
|
|
}
|
|
var (
|
|
err error
|
|
allowedTypeStrArray = make([]string, 0)
|
|
)
|
|
currentStructFieldMap, err := gstructs.FieldMap(gstructs.FieldMapInput{
|
|
Pointer: pointer,
|
|
PriorityTagArray: nil,
|
|
RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// It checks the with array and automatically calls the ScanList to complete association querying.
|
|
if !m.withAll {
|
|
for _, field := range currentStructFieldMap {
|
|
for _, withItem := range m.withArray {
|
|
withItemReflectValueType, err := gstructs.StructType(withItem)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var (
|
|
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
|
|
withItemReflectValueTypeStr = gstr.TrimAll(withItemReflectValueType.String(), "*[]")
|
|
)
|
|
// It does select operation if the field type is in the specified "with" type array.
|
|
if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 {
|
|
allowedTypeStrArray = append(allowedTypeStrArray, fieldTypeStr)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for _, field := range currentStructFieldMap {
|
|
var (
|
|
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
|
|
parsedTagOutput = m.parseWithTagInFieldStruct(field)
|
|
)
|
|
if parsedTagOutput.With == "" {
|
|
continue
|
|
}
|
|
// It just handlers "with" type attribute struct, so it ignores other struct types.
|
|
if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) {
|
|
continue
|
|
}
|
|
array := gstr.SplitAndTrim(parsedTagOutput.With, "=")
|
|
if len(array) == 1 {
|
|
// It also supports using only one column name
|
|
// if both tables associates using the same column name.
|
|
array = append(array, parsedTagOutput.With)
|
|
}
|
|
var (
|
|
model *Model
|
|
fieldKeys []string
|
|
relatedSourceName = array[0]
|
|
relatedTargetName = array[1]
|
|
relatedTargetValue any
|
|
)
|
|
// Find the value of related attribute from `pointer`.
|
|
for attributeName, attributeValue := range currentStructFieldMap {
|
|
if utils.EqualFoldWithoutChars(attributeName, relatedTargetName) {
|
|
relatedTargetValue = attributeValue.Value.Interface()
|
|
break
|
|
}
|
|
}
|
|
if relatedTargetValue == nil {
|
|
return gerror.NewCodef(
|
|
gcode.CodeInvalidParameter,
|
|
`cannot find the target related value of name "%s" in with tag "%s" for attribute "%s.%s"`,
|
|
relatedTargetName, parsedTagOutput.With, reflect.TypeOf(pointer).Elem(), field.Name(),
|
|
)
|
|
}
|
|
bindToReflectValue := field.Value
|
|
if bindToReflectValue.Kind() != reflect.Pointer && bindToReflectValue.CanAddr() {
|
|
bindToReflectValue = bindToReflectValue.Addr()
|
|
}
|
|
|
|
if structFields, err := gstructs.Fields(gstructs.FieldsInput{
|
|
Pointer: field.Value,
|
|
RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
|
|
}); err != nil {
|
|
return err
|
|
} else {
|
|
fieldKeys = make([]string, len(structFields))
|
|
for i, field := range structFields {
|
|
fieldKeys[i] = field.Name()
|
|
}
|
|
}
|
|
// Recursively with feature checks.
|
|
model = m.db.With(field.Value).Hook(m.hookHandler)
|
|
if m.withAll {
|
|
model = model.WithAll()
|
|
} else {
|
|
model = model.With(m.withArray...)
|
|
}
|
|
if parsedTagOutput.Where != "" {
|
|
model = model.Where(parsedTagOutput.Where)
|
|
}
|
|
if parsedTagOutput.Order != "" {
|
|
model = model.Order(parsedTagOutput.Order)
|
|
}
|
|
if parsedTagOutput.Unscoped == "true" {
|
|
model = model.Unscoped()
|
|
}
|
|
// With cache feature.
|
|
if m.cacheEnabled && m.cacheOption.Name == "" {
|
|
model = model.Cache(m.cacheOption)
|
|
}
|
|
err = model.Fields(fieldKeys).
|
|
Where(relatedSourceName, relatedTargetValue).
|
|
Scan(bindToReflectValue)
|
|
// It ignores sql.ErrNoRows in with feature.
|
|
if err != nil && err != sql.ErrNoRows {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// doWithScanStructs handles model association operations feature for struct slice.
|
|
// Also see doWithScanStruct.
|
|
func (m *Model) doWithScanStructs(pointer any) error {
|
|
if len(m.withArray) == 0 && !m.withAll {
|
|
return nil
|
|
}
|
|
if v, ok := pointer.(reflect.Value); ok {
|
|
pointer = v.Interface()
|
|
}
|
|
|
|
var (
|
|
err error
|
|
allowedTypeStrArray = make([]string, 0)
|
|
)
|
|
currentStructFieldMap, err := gstructs.FieldMap(gstructs.FieldMapInput{
|
|
Pointer: pointer,
|
|
PriorityTagArray: nil,
|
|
RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// It checks the with array and automatically calls the ScanList to complete association querying.
|
|
if !m.withAll {
|
|
for _, field := range currentStructFieldMap {
|
|
for _, withItem := range m.withArray {
|
|
withItemReflectValueType, err := gstructs.StructType(withItem)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var (
|
|
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
|
|
withItemReflectValueTypeStr = gstr.TrimAll(withItemReflectValueType.String(), "*[]")
|
|
)
|
|
// It does select operation if the field type is in the specified with type array.
|
|
if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 {
|
|
allowedTypeStrArray = append(allowedTypeStrArray, fieldTypeStr)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for fieldName, field := range currentStructFieldMap {
|
|
var (
|
|
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
|
|
parsedTagOutput = m.parseWithTagInFieldStruct(field)
|
|
)
|
|
if parsedTagOutput.With == "" {
|
|
continue
|
|
}
|
|
if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) {
|
|
continue
|
|
}
|
|
array := gstr.SplitAndTrim(parsedTagOutput.With, "=")
|
|
if len(array) == 1 {
|
|
// It supports using only one column name
|
|
// if both tables associates using the same column name.
|
|
array = append(array, parsedTagOutput.With)
|
|
}
|
|
var (
|
|
model *Model
|
|
fieldKeys []string
|
|
relatedSourceName = array[0]
|
|
relatedTargetName = array[1]
|
|
relatedTargetValue any
|
|
)
|
|
// Find the value slice of related attribute from `pointer`.
|
|
for attributeName := range currentStructFieldMap {
|
|
if utils.EqualFoldWithoutChars(attributeName, relatedTargetName) {
|
|
relatedTargetValue = ListItemValuesUnique(pointer, attributeName)
|
|
break
|
|
}
|
|
}
|
|
if relatedTargetValue == nil {
|
|
return gerror.NewCodef(
|
|
gcode.CodeInvalidParameter,
|
|
`cannot find the related value for attribute name "%s" of with tag "%s"`,
|
|
relatedTargetName, parsedTagOutput.With,
|
|
)
|
|
}
|
|
// If related value is empty, it does nothing but just returns.
|
|
if gutil.IsEmpty(relatedTargetValue) {
|
|
return nil
|
|
}
|
|
if structFields, err := gstructs.Fields(gstructs.FieldsInput{
|
|
Pointer: field.Value,
|
|
RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
|
|
}); err != nil {
|
|
return err
|
|
} else {
|
|
fieldKeys = make([]string, len(structFields))
|
|
for i, field := range structFields {
|
|
fieldKeys[i] = field.Name()
|
|
}
|
|
}
|
|
// Recursively with feature checks.
|
|
model = m.db.With(field.Value).Hook(m.hookHandler)
|
|
if m.withAll {
|
|
model = model.WithAll()
|
|
} else {
|
|
model = model.With(m.withArray...)
|
|
}
|
|
if parsedTagOutput.Where != "" {
|
|
model = model.Where(parsedTagOutput.Where)
|
|
}
|
|
if parsedTagOutput.Order != "" {
|
|
model = model.Order(parsedTagOutput.Order)
|
|
}
|
|
if parsedTagOutput.Unscoped == "true" {
|
|
model = model.Unscoped()
|
|
}
|
|
// With cache feature.
|
|
if m.cacheEnabled && m.cacheOption.Name == "" {
|
|
model = model.Cache(m.cacheOption)
|
|
}
|
|
err = model.Fields(fieldKeys).
|
|
Where(relatedSourceName, relatedTargetValue).
|
|
ScanList(pointer, fieldName, parsedTagOutput.With)
|
|
// It ignores sql.ErrNoRows in with feature.
|
|
if err != nil && err != sql.ErrNoRows {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type parseWithTagInFieldStructOutput struct {
|
|
With string
|
|
Where string
|
|
Order string
|
|
Unscoped string
|
|
}
|
|
|
|
func (m *Model) parseWithTagInFieldStruct(field gstructs.Field) (output parseWithTagInFieldStructOutput) {
|
|
var (
|
|
ormTag = field.Tag(OrmTagForStruct)
|
|
data = make(map[string]string)
|
|
array []string
|
|
key string
|
|
)
|
|
for _, v := range gstr.SplitAndTrim(ormTag, ",") {
|
|
array = gstr.Split(v, ":")
|
|
if len(array) == 2 {
|
|
key = array[0]
|
|
data[key] = gstr.Trim(array[1])
|
|
} else {
|
|
if key == OrmTagForWithOrder {
|
|
// supporting multiple order fields
|
|
data[key] += "," + gstr.Trim(v)
|
|
} else {
|
|
data[key] += " " + gstr.Trim(v)
|
|
}
|
|
}
|
|
}
|
|
output.With = data[OrmTagForWith]
|
|
output.Where = data[OrmTagForWithWhere]
|
|
output.Order = data[OrmTagForWithOrder]
|
|
output.Unscoped = data[OrmTagForWithUnscoped]
|
|
return
|
|
}
|