Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Case insensitive matching of field names #337

Merged
merged 21 commits into from
Aug 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 31 additions & 14 deletions gorm/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ func FieldSelectionStringToGorm(ctx context.Context, fs string, obj interface{})
// FieldSelectionToGorm receives FieldSelection struct and returns a list of associations to preload.
func (converter *DefaultFieldSelectionConverter) FieldSelectionToGorm(ctx context.Context, fs *query.FieldSelection, obj interface{}) ([]string, error) {
objType := indirectType(reflect.TypeOf(obj))
if fs.GetFields() == nil {
selectedFields := fs.GetFields()
if selectedFields == nil {
return preloadEverything(objType, nil)
}
var toPreload []string
fieldNames := getSortedFieldNames(fs.GetFields())
fieldNames := getSortedFieldNames(selectedFields)
for _, fieldName := range fieldNames {
f := fs.GetFields()[fieldName]
f := selectedFields[fieldName]
subPreload, err := handlePreloads(f, objType)
if err != nil {
return nil, err
Expand Down Expand Up @@ -76,35 +77,51 @@ fields:
}

func handlePreloads(f *query.Field, objType reflect.Type) ([]string, error) {
sf, ok := objType.FieldByName(util.Camel(f.GetName()))
queryFieldName := f.GetName()

var sf reflect.StructField
var ok bool
// do default(camel-case) search
sf, ok = objType.FieldByName(util.Camel(queryFieldName))
if !ok {
return nil, nil
// do case-insensitive search
sf, ok = objType.FieldByNameFunc(func(name string) bool {
return strings.EqualFold(name, strings.ToLower(strings.ReplaceAll(queryFieldName, "_", "")))
})
if !ok {
return nil, nil
}
}

fType := indirectType(sf.Type)
if f.GetSubs() == nil {
fName := sf.Name

fieldSubs := f.GetSubs()

if fieldSubs == nil {
if isModel(fType) {
return []string{util.Camel(f.GetName())}, nil
return []string{fName}, nil
} else {
return nil, nil
}
}
if !isModel(fType) {
return nil, fmt.Errorf("%s is expected to be a model, but got %s ", f.GetName(), fType)
return nil, fmt.Errorf("%s is expected to be a model, but got %s ", queryFieldName, fType)
}
var toPreload []string
fieldNames := getSortedFieldNames(f.GetSubs())
fieldNames := getSortedFieldNames(fieldSubs)
for _, fieldName := range fieldNames {
subField := f.GetSubs()[fieldName]
subField := fieldSubs[fieldName]
subPreload, err := handlePreloads(subField, fType)
if err != nil {
return nil, err
}
for i, e := range subPreload {
subPreload[i] = util.Camel(f.GetName()) + "." + e
subPreload[i] = fName + "." + e
}
toPreload = append(toPreload, subPreload...)
}
return append(toPreload, util.Camel(f.GetName())), nil
return append(toPreload, fName), nil
}

func getSortedFieldNames(fields map[string]*query.Field) []string {
Expand All @@ -126,7 +143,7 @@ func preload(db *gorm.DB, obj interface{}, assoc string) (*gorm.DB, error) {
for i, part := range assocPath {
sf, ok := objType.FieldByName(part)
if !ok {
return nil, fmt.Errorf("Cannot find %s in %s", part, objType)
return nil, fmt.Errorf("cannot find %s in %s", part, objType)
}
objType = indirectType(sf.Type)
if !isModel(objType) {
Expand All @@ -143,5 +160,5 @@ func preload(db *gorm.DB, obj interface{}, assoc string) (*gorm.DB, error) {
}
}
}
return nil, fmt.Errorf("Cannot preload empty association")
return nil, fmt.Errorf("cannot preload empty association")
}
119 changes: 111 additions & 8 deletions gorm/fields_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,54 @@ package gorm

import (
"context"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

type Model struct {
Property string
SubModel SubModel
SubModels []SubModel
CycleModel *CycleModel
NotPreloadObj SubModel `gorm:"preload:false"`
PreloadObj SubModel `gorm:"preload:true"`
Property string
SubModel SubModel
SubModels []SubModel
CycleModel *CycleModel
NotPreloadObj SubModel `gorm:"preload:false"`
PreloadObj SubModel `gorm:"preload:true"`
NonCamelMODEL NonCamelMODEL
NonCAMEL2Model NonCAMEL2Model
NonCamelSUBMODEL NonCamelSUBMODEL
SubModelMix SubModelMix
SubModelMix2 SubModelMix2
NonCamelModelMIX NonCamelModelMIX
}

type NonCamelModelMIX struct {
NonCamelMixProperty string
SubModel SubModel
}

type SubModelMix struct {
SubModelMixProperty string
NonCamelMODEL NonCamelMODEL
}

type SubModelMix2 struct {
SubModelMixProperty string
NonCamelSUBMODEL NonCamelSUBMODEL
}

type NonCamelMODEL struct {
NonCamelProperty string
NonCamelSUBMODEL NonCamelSUBMODEL
}

type NonCamelSUBMODEL struct {
NonCamelSubProperty string
}

type NonCAMEL2Model struct {
NonCamelProperty string
Model *Model
}

type CycleModel struct {
Expand All @@ -36,6 +72,56 @@ func TestGormFieldSelection(t *testing.T) {
toPreload []string
err bool
}{
{
"sub_model_mix2.non_camel_submodel",
[]string{"SubModelMix2.NonCamelSUBMODEL", "SubModelMix2"},
false,
},
{
"non_camel_model_mix.sub_model.sub_property",
[]string{"NonCamelModelMIX.SubModel", "NonCamelModelMIX"},
false,
},
{
"non_camel_model_mix,sub_model,non_camel_2_model,cycle_model",
[]string{"CycleModel", "NonCAMEL2Model", "NonCamelModelMIX", "SubModel"},
false,
},
{
"sub_model_mix.non_camel_model",
[]string{"SubModelMix.NonCamelMODEL", "SubModelMix"},
false,
},
{
"non_camel_model.non_camel_submodel.noncamelsubproperty",
[]string{"NonCamelMODEL.NonCamelSUBMODEL", "NonCamelMODEL"},
false,
},
{
"non_camel_model.non_camel_submodel.noncamelsubproperty",
[]string{"NonCamelMODEL.NonCamelSUBMODEL", "NonCamelMODEL"},
false,
},
{
"non_camel_model.non_camel_submodel",
[]string{"NonCamelMODEL.NonCamelSUBMODEL", "NonCamelMODEL"},
false,
},
{
"non_camel_model",
[]string{"NonCamelMODEL"},
false,
},
{
"non_camel_model.noncamelproperty",
[]string{"NonCamelMODEL"},
false,
},
{
"non_CAMEL_2_Model,Non_camel_2_model,non_camel2_model,non_camel_2model",
[]string{"NonCAMEL2Model", "NonCAMEL2Model", "NonCAMEL2Model", "NonCAMEL2Model"},
false,
},
{
"property",
nil,
Expand All @@ -62,7 +148,17 @@ func TestGormFieldSelection(t *testing.T) {
false,
},
{
"sub_model,sub_model.sub_sub_model.sub_sub_property",
"sub_model",
[]string{"SubModel"},
false,
},
{
"sub_model.sub_sub_model",
[]string{"SubModel.SubSubModel", "SubModel"},
false,
},
{
"sub_model.sub_sub_model.sub_sub_property",
[]string{"SubModel.SubSubModel", "SubModel"},
false,
},
Expand All @@ -78,16 +174,23 @@ func TestGormFieldSelection(t *testing.T) {
},
{
"",
[]string{"SubModel.SubSubModel", "SubModel", "SubModels.SubSubModel", "SubModels", "CycleModel", "PreloadObj.SubSubModel", "PreloadObj"},
[]string{"SubModel.SubSubModel", "SubModel", "SubModels.SubSubModel", "SubModels",
"CycleModel", "PreloadObj.SubSubModel", "PreloadObj",
"NonCamelMODEL.NonCamelSUBMODEL", "NonCamelMODEL", "NonCAMEL2Model", "NonCamelSUBMODEL",
"SubModelMix.NonCamelMODEL.NonCamelSUBMODEL", "SubModelMix.NonCamelMODEL", "SubModelMix",
"SubModelMix2.NonCamelSUBMODEL", "SubModelMix2",
"NonCamelModelMIX.SubModel.SubSubModel", "NonCamelModelMIX.SubModel", "NonCamelModelMIX"},
false,
},
}

for _, test := range tests {
toPreload, err := FieldSelectionStringToGorm(context.Background(), test.fs, &Model{})
if test.err {
assert.Nil(t, toPreload)
assert.NotNil(t, err)
} else {
fmt.Printf("expected=%v\nactual=%v\n\n", test.toPreload, toPreload)
assert.Equal(t, test.toPreload, toPreload)
assert.Nil(t, err)
}
Expand Down