Files
gf/database/gdb/gdb_model_with.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

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
}