Skip to content

Commit

Permalink
Case insensitive matching of field names (#337)
Browse files Browse the repository at this point in the history
* accept upper case in camel style string

* handle non-camel model structs

* add test case

* change print message

* update test cases

* add struct and test case

* update comment

* update comment

* update test case

* make find field name more generic

* revert changes

* amend revert changes

* log query result

* add flag for insensitive field match

* correct lint error

* uncomment test case

* revert import order

* add tests with ignoreCase set to false

* revert function signature; add function to set case-sensitive matching

* update test; remove debug messgess

* add second search if camel search field failed
  • Loading branch information
ychen-bloxer committed Aug 5, 2022
1 parent e4dd86b commit f5fa79b
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 22 deletions.
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

0 comments on commit f5fa79b

Please sign in to comment.