mirror of
https://gitee.com/johng/gf
synced 2026-06-07 02:12:11 +08:00
This PR includes the following changes: - **Upgrade `.golangci.yml`**: Updated the configuration file to align with the latest golangci-lint version, ensuring compatibility and leveraging new features. - **Refactor GitHub Action workflow**: Modified `golangci-lint.yml` in the GitHub Actions workflow to reflect the updated configuration and improve CI performance. - **Codebase optimization**: Refactored code to address issues and warnings raised by the updated golangci-lint rules, including: - Improved function length and complexity. - Enhanced error handling and variable naming conventions. - Fixed minor issues such as unused imports and formatting inconsistencies. These changes aim to maintain code quality, ensure compatibility with the latest tools, and improve overall maintainability.
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 ...interface{}) *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 interface{}) 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 interface{}
|
|
)
|
|
// 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.Ptr && 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 interface{}) 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 interface{}
|
|
)
|
|
// 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
|
|
}
|