From 2d6e5b643e5e5561bba6f018b546f9dfcd1a5e4b Mon Sep 17 00:00:00 2001 From: bramca Date: Thu, 13 Mar 2025 16:39:50 +0100 Subject: [PATCH 01/12] update docs --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ccc91c4..6577150 100644 --- a/README.md +++ b/README.md @@ -47,9 +47,11 @@ import ( func main() { db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) - + // Adds deep filtering - db.Use(deepgorm.New()) + if err := db.Use(deepgorm.New()); err != nil { + panic(err.Error()) + } } ``` From 68f9cb1ea13245f20aaeb58d3d760c0446a3c80c Mon Sep 17 00:00:00 2001 From: bramca Date: Tue, 13 May 2025 17:42:21 +0200 Subject: [PATCH 02/12] wip function support --- deep-filtering.go | 80 ++++++++++++++++++- deep-filtering_test.go | 171 +++++++++++++++++++++++++++++++++++++++++ go.mod | 3 + go.sum | 7 ++ 4 files changed, 260 insertions(+), 1 deletion(-) diff --git a/deep-filtering.go b/deep-filtering.go index 24154b7..f8ed098 100644 --- a/deep-filtering.go +++ b/deep-filtering.go @@ -4,9 +4,13 @@ import ( "errors" "fmt" "reflect" + "regexp" + "strings" "sync" + "github.com/sirupsen/logrus" "github.com/survivorbat/go-tsyncmap" + gormqonvert "github.com/survivorbat/gorm-query-convert" "gorm.io/gorm/schema" "gorm.io/gorm" @@ -81,6 +85,23 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go relationalTypesInfo := getDatabaseFieldsOfType(db.NamingStrategy, schemaInfo) simpleFilter := map[string]any{} + filterString := "" + functionRegex := regexp.MustCompile(`.*?\((.*?)\)`) + qonvertMap := map[string]string{} + + if _, ok := db.Plugins[gormqonvert.New(gormqonvert.CharacterConfig{}).Name()]; ok { + qonvertPlugin := db.Plugins[gormqonvert.New(gormqonvert.CharacterConfig{}).Name()] + qonvertPluginConfig := reflect.ValueOf(qonvertPlugin).Elem().FieldByName("config") + qonvertMap[qonvertPluginConfig.FieldByName("GreaterThanPrefix").String()] = ">" + qonvertMap[qonvertPluginConfig.FieldByName("GreaterOrEqualToPrefix").String()] = ">=" + qonvertMap[qonvertPluginConfig.FieldByName("LessThanPrefix").String()] = "<" + qonvertMap[qonvertPluginConfig.FieldByName("LessOrEqualToPrefix").String()] = "<=" + qonvertMap[qonvertPluginConfig.FieldByName("NotEqualToPrefix").String()] = "!=" + qonvertMap[qonvertPluginConfig.FieldByName("LikePrefix").String()] = "%s LIKE '%%%s%%'" + qonvertMap[qonvertPluginConfig.FieldByName("NotLikePrefix").String()] = "%s NOT LIKE '%%%s%%'" + } + + logrus.Infof("qonvertMap: %+v\n", qonvertMap) // Go through the filters for _, filterObject := range filters { @@ -105,15 +126,72 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go // Simple filters (string, int, bool etc.) default: + if functionRegex.MatchString(fieldName) { + checkFieldName := functionRegex.ReplaceAllString(fieldName, "$1") + if _, ok := schemaInfo.FieldsByDBName[checkFieldName]; !ok { + return nil, fmt.Errorf("failed to add filters for '%s.%s': %w", schemaInfo.Table, checkFieldName, ErrFieldDoesNotExist) + } + + if _, ok := givenFilter.([]string); ok { + for _, filter := range givenFilter.([]string) { + for qonvertField, qonvertValue := range qonvertMap { + if !strings.Contains(filter, qonvertField) { + continue + } + + if strings.Contains(qonvertValue, "%") { + if filterString == "" { + filterString = fmt.Sprintf(qonvertValue, fieldName, filter) + } else { + filterString = fmt.Sprintf("%s OR %s", filterString, fmt.Sprintf(qonvertValue, fieldName, filter)) + } + } else { + filter = strings.Replace(filter, qonvertValue, "", 1) + filterString = fmt.Sprintf("%s %s %s", fieldName, qonvertValue, fmt.Sprintf("'%s'", filter)) + } + + break + } + } + } + + for qonvertField, qonvertValue := range qonvertMap { + if !strings.Contains(givenFilter.(string), qonvertField) { + continue + } + + if strings.Contains(qonvertField, "%") { + if filterString == "" { + filterString = fmt.Sprintf(qonvertValue, fieldName, givenFilter) + } else { + filterString = fmt.Sprintf("%s OR %s", filterString, fmt.Sprintf(qonvertValue, fieldName, givenFilter)) + } + } else { + givenFilter = strings.Replace(givenFilter.(string), qonvertValue, "", 1) + filterString = fmt.Sprintf("%s %s %s", fieldName, qonvertValue, fmt.Sprintf("'%s'", givenFilter)) + } + } + + if filterString == "" { + filterString = fmt.Sprintf("%s = '%s'", fieldName, givenFilter) + } + + break + } + if _, ok := schemaInfo.FieldsByDBName[fieldName]; !ok { return nil, fmt.Errorf("failed to add filters for '%s.%s': %w", schemaInfo.Table, fieldName, ErrFieldDoesNotExist) } - simpleFilter[schemaInfo.Table+"."+fieldName] = givenFilter + simpleFilter[fieldName] = givenFilter + logrus.Infof("simpleFilter: %+v", simpleFilter) } } } // Add simple filters + if filterString != "" { + db = db.Where(filterString) + } db = db.Where(simpleFilter) return db, nil diff --git a/deep-filtering_test.go b/deep-filtering_test.go index 7e57494..354de04 100644 --- a/deep-filtering_test.go +++ b/deep-filtering_test.go @@ -7,8 +7,11 @@ import ( "testing" "github.com/ing-bank/gormtestutil" + "gorm.io/gorm" "gorm.io/gorm/schema" + gormqonvert "github.com/survivorbat/gorm-query-convert" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -688,6 +691,159 @@ func TestAddDeepFilters_ReturnsErrorOnUnknownFieldInformation(t *testing.T) { } } +func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { + t.Parallel() + t.Cleanup(cleanupCache) + type SimpleStruct6 struct { + Name string + Occupation string + } + + tests := map[string]struct { + records []*SimpleStruct6 + expected []*SimpleStruct6 + filterMap map[string]any + }{ + "first": { + records: []*SimpleStruct6{ + { + Occupation: "Dev", + Name: "John", + }, + { + Occupation: "Ops", + Name: "Jennifer", + }, + }, + expected: []*SimpleStruct6{ + { + Occupation: "Ops", + Name: "Jennifer", + }, + }, + filterMap: map[string]any{ + "LOWER(occupation)": "ops", + }, + }, + "second": { + records: []*SimpleStruct6{ + { + Occupation: "Dev", + Name: "John", + }, + { + Occupation: "Ops", + Name: "Jennifer", + }, + { + Occupation: "Ops", + Name: "Roy", + }, + }, + expected: []*SimpleStruct6{ + { + Occupation: "Ops", + Name: "Jennifer", + }, + { + Occupation: "Ops", + Name: "Roy", + }, + }, + filterMap: map[string]any{ + "UPPER(occupation)": "OPS", + }, + }, + "third": { + records: []*SimpleStruct6{ + { + Occupation: "Dev", + Name: "John", + }, + { + Occupation: "Ops", + Name: "Jennifer", + }, + }, + expected: []*SimpleStruct6{ + { + Occupation: "Ops", + Name: "Jennifer", + }, + }, + filterMap: map[string]any{ + "occupation": "Ops", + "LENGTH(name)": ">4", + }, + }, + "fourth": { + records: []*SimpleStruct6{ + { + Occupation: "Dev", + Name: "John", + }, + { + Occupation: "Ops", + Name: "Jennifer", + }, + }, + expected: []*SimpleStruct6{ + { + Occupation: "Dev", + Name: "John", + }, + { + Occupation: "Ops", + Name: "Jennifer", + }, + }, + filterMap: map[string]any{ + "occupation": []string{"Ops", "Dev"}, + }, + }, + } + + for name, testData := range tests { + testData := testData + t.Run(name, func(t *testing.T) { + t.Parallel() + // Arrange + database := gormtestutil.NewMemoryDatabase(t, gormtestutil.WithName(t.Name())) + database.Use(gormqonvert.New(gormqonvert.CharacterConfig{ + GreaterThanPrefix: ">", + GreaterOrEqualToPrefix: ">=", + LessThanPrefix: "<", + LessOrEqualToPrefix: "<=", + NotEqualToPrefix: "!=", + LikePrefix: "~", + NotLikePrefix: "!~", + })) + _ = database.AutoMigrate(&SimpleStruct6{}) + + database.CreateInBatches(testData.records, len(testData.records)) + + // Act + // sqlQuery := database.ToSQL(func (tx *gorm.DB) *gorm.DB { + // deepFilterQuery, _ := AddDeepFilters(tx, SimpleStruct6{}, testData.filterMap) + // return deepFilterQuery.Find(&SimpleStruct6{}) + // }) + // fmt.Printf("sqlQuery: %s\n", sqlQuery) + query, err := AddDeepFilters(database, SimpleStruct6{}, testData.filterMap) + + + // Assert + assert.Nil(t, err) + + if assert.NotNil(t, query) { + var result []*SimpleStruct6 + query.Preload(clause.Associations).Find(&result) + + assert.EqualValues(t, testData.expected, result) + } + }) + } +} + func TestAddDeepFilters_AddsSimpleFilters(t *testing.T) { t.Parallel() t.Cleanup(cleanupCache) @@ -806,13 +962,28 @@ func TestAddDeepFilters_AddsSimpleFilters(t *testing.T) { t.Parallel() // Arrange database := gormtestutil.NewMemoryDatabase(t, gormtestutil.WithName(t.Name())) + database.Use(gormqonvert.New(gormqonvert.CharacterConfig{ + GreaterThanPrefix: ">", + GreaterOrEqualToPrefix: ">=", + LessThanPrefix: "<", + LessOrEqualToPrefix: "<=", + NotEqualToPrefix: "!=", + LikePrefix: "~", + NotLikePrefix: "!~", + })) _ = database.AutoMigrate(&SimpleStruct6{}) database.CreateInBatches(testData.records, len(testData.records)) // Act + sqlQuery := database.ToSQL(func (tx *gorm.DB) *gorm.DB { + deepFilterQuery, _ := AddDeepFilters(tx, SimpleStruct6{}, testData.filterMap) + return deepFilterQuery.Find(&SimpleStruct6{}) + }) + fmt.Printf("sqlQuery: %s\n", sqlQuery) query, err := AddDeepFilters(database, SimpleStruct6{}, testData.filterMap) + // Assert assert.Nil(t, err) diff --git a/go.mod b/go.mod index 4632e51..abb73b2 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,9 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/survivorbat/gorm-query-convert v0.0.1 // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect golang.org/x/text v0.23.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4551b6e..4219c64 100644 --- a/go.sum +++ b/go.sum @@ -18,15 +18,22 @@ github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/survivorbat/go-tsyncmap v0.0.0 h1:XTc1+uXyuw//1Hhpg4IxW6tEe3Tvd2d5vM/6IPqmkeg= github.com/survivorbat/go-tsyncmap v0.0.0/go.mod h1:zKe2CuXEo+c1d9DVT5L7AG2jPTdWi7QQN/Gk+26Vecg= +github.com/survivorbat/gorm-query-convert v0.0.1 h1:e/wesF2ajiryuJhH/Vs9WrsTU5Mh6x3VRkdFtm08l0E= +github.com/survivorbat/gorm-query-convert v0.0.1/go.mod h1:3YidTdfqM+KscRLFKhSUNl16tO0CqlEJcG0dFFpVl0g= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= From 9737dd80fcdc58ad4536228e23051763a55611fc Mon Sep 17 00:00:00 2001 From: Bram Cautaerts Date: Tue, 13 May 2025 21:42:45 +0200 Subject: [PATCH 03/12] wip todo fix or statemet and broken qonvert --- deep-filtering.go | 72 +++++++++++++++++++++++++++++++++++++----- deep-filtering_test.go | 64 +++++++++++++++++++++++-------------- go.mod | 5 ++- go.sum | 6 ++-- 4 files changed, 109 insertions(+), 38 deletions(-) diff --git a/deep-filtering.go b/deep-filtering.go index f8ed098..4b788bb 100644 --- a/deep-filtering.go +++ b/deep-filtering.go @@ -5,10 +5,10 @@ import ( "fmt" "reflect" "regexp" + "strconv" "strings" "sync" - "github.com/sirupsen/logrus" "github.com/survivorbat/go-tsyncmap" gormqonvert "github.com/survivorbat/gorm-query-convert" "gorm.io/gorm/schema" @@ -101,8 +101,6 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go qonvertMap[qonvertPluginConfig.FieldByName("NotLikePrefix").String()] = "%s NOT LIKE '%%%s%%'" } - logrus.Infof("qonvertMap: %+v\n", qonvertMap) - // Go through the filters for _, filterObject := range filters { // Go through all the keys of the filters @@ -134,6 +132,7 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go if _, ok := givenFilter.([]string); ok { for _, filter := range givenFilter.([]string) { + containedQonvert := false for qonvertField, qonvertValue := range qonvertMap { if !strings.Contains(filter, qonvertField) { continue @@ -147,16 +146,49 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go } } else { filter = strings.Replace(filter, qonvertValue, "", 1) - filterString = fmt.Sprintf("%s %s %s", fieldName, qonvertValue, fmt.Sprintf("'%s'", filter)) + if filterString == "" { + filterString = prepareFilterValue(fieldName, qonvertValue, filter) + } else { + filterString = fmt.Sprintf("%s OR %s", filterString, prepareFilterValue(fieldName, qonvertValue, filter)) + } } + containedQonvert = true break } + + if !containedQonvert { + if filterString == "" { + filterString = fmt.Sprintf("%s = '%s'", fieldName, filter) + } else { + filterString = fmt.Sprintf("%s OR %s", filterString, fmt.Sprintf("%s = '%s'", fieldName, filter)) + } + } + } + } + + if _, ok := givenFilter.([]int); ok { + for _, filter := range givenFilter.([]int) { + if filterString == "" { + filterString = fmt.Sprintf("%s = %d", fieldName, filter) + } else { + filterString = fmt.Sprintf("%s OR %s", filterString, fmt.Sprintf("%s = %d", fieldName, filter)) + } + } + } + + if _, ok := givenFilter.([]bool); ok { + for _, filter := range givenFilter.([]bool) { + if filterString == "" { + filterString = fmt.Sprintf("%s = %t", fieldName, filter) + } else { + filterString = fmt.Sprintf("%s OR %s", filterString, fmt.Sprintf("%s = %t", fieldName, filter)) + } } } for qonvertField, qonvertValue := range qonvertMap { - if !strings.Contains(givenFilter.(string), qonvertField) { + if filterStrCast, castOk := givenFilter.(string); !castOk || !strings.Contains(filterStrCast, qonvertField) { continue } @@ -168,12 +200,12 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go } } else { givenFilter = strings.Replace(givenFilter.(string), qonvertValue, "", 1) - filterString = fmt.Sprintf("%s %s %s", fieldName, qonvertValue, fmt.Sprintf("'%s'", givenFilter)) + filterString = prepareFilterValue(fieldName, qonvertValue, givenFilter.(string)) } } if filterString == "" { - filterString = fmt.Sprintf("%s = '%s'", fieldName, givenFilter) + filterString = prepareFilterValueCast(fieldName, "=", givenFilter) } break @@ -183,7 +215,6 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go return nil, fmt.Errorf("failed to add filters for '%s.%s': %w", schemaInfo.Table, fieldName, ErrFieldDoesNotExist) } simpleFilter[fieldName] = givenFilter - logrus.Infof("simpleFilter: %+v", simpleFilter) } } } @@ -226,6 +257,31 @@ type iKind[T any] interface { Elem() T } +// prepareFilterValue checks if the given filter can be converted to an int or bool and gives back the correct SQL value for it +func prepareFilterValue(fieldName string, operator string, filterValue string) string { + if value, err := strconv.Atoi(filterValue); err == nil { + return fmt.Sprintf("%s %s %d", fieldName, operator, value) + } + + if value, err := strconv.ParseBool(filterValue); err == nil { + return fmt.Sprintf("%s %s %t", fieldName, operator, value) + } + + return fmt.Sprintf("%s %s '%s'", fieldName, operator, filterValue) +} + +// prepareFilterValue checks if the given filter can be converted to an int or bool and gives back the correct SQL value for it +func prepareFilterValueCast(fieldName string, operator string, filterValue any) string { + if filterIntCast, castOk := filterValue.(int); castOk { + return fmt.Sprintf("%s %s %d", fieldName, operator, filterIntCast) + } + if filterBoolCast, castOk := filterValue.(bool); castOk { + return fmt.Sprintf("%s %s %t", fieldName, operator, filterBoolCast) + } + + return fmt.Sprintf("%s %s '%s'", fieldName, operator, filterValue.(string)) +} + // ensureConcrete ensures that the given value is a value and not a pointer, if it is, convert it to its element type func ensureConcrete[T iKind[T]](value T) T { if value.Kind() == reflect.Ptr { diff --git a/deep-filtering_test.go b/deep-filtering_test.go index 354de04..47377ba 100644 --- a/deep-filtering_test.go +++ b/deep-filtering_test.go @@ -704,7 +704,7 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { expected []*SimpleStruct6 filterMap map[string]any }{ - "first": { + "simple filter": { records: []*SimpleStruct6{ { Occupation: "Dev", @@ -725,7 +725,7 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { "LOWER(occupation)": "ops", }, }, - "second": { + "and filter with qonvert": { records: []*SimpleStruct6{ { Occupation: "Dev", @@ -735,26 +735,44 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { Occupation: "Ops", Name: "Jennifer", }, + }, + expected: []*SimpleStruct6{ { Occupation: "Ops", - Name: "Roy", + Name: "Jennifer", }, }, - expected: []*SimpleStruct6{ + filterMap: map[string]any{ + "occupation": "Ops", + "LENGTH(name)": ">4", + }, + }, + "simple or filter strings": { + records: []*SimpleStruct6{ + { + Occupation: "Dev", + Name: "John", + }, { Occupation: "Ops", Name: "Jennifer", }, + }, + expected: []*SimpleStruct6{ + { + Occupation: "Dev", + Name: "John", + }, { Occupation: "Ops", - Name: "Roy", + Name: "Jennifer", }, }, filterMap: map[string]any{ - "UPPER(occupation)": "OPS", + "UPPER(occupation)": []string{"OPS", "DEV"}, }, }, - "third": { + "simple or filter int": { records: []*SimpleStruct6{ { Occupation: "Dev", @@ -766,17 +784,20 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { }, }, expected: []*SimpleStruct6{ + { + Occupation: "Dev", + Name: "John", + }, { Occupation: "Ops", Name: "Jennifer", }, }, filterMap: map[string]any{ - "occupation": "Ops", - "LENGTH(name)": ">4", + "LENGTH(name)": []int{4, 8}, }, }, - "fourth": { + "or and filter with convert": { records: []*SimpleStruct6{ { Occupation: "Dev", @@ -792,13 +813,10 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { Occupation: "Dev", Name: "John", }, - { - Occupation: "Ops", - Name: "Jennifer", - }, }, filterMap: map[string]any{ - "occupation": []string{"Ops", "Dev"}, + "UPPER(occupation)": []string{"OPS", "DEV"}, + "LENGTH(name)": []string{"<=4", ">8"}, }, }, } @@ -806,7 +824,7 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { for name, testData := range tests { testData := testData t.Run(name, func(t *testing.T) { - t.Parallel() + // t.Parallel() // Arrange database := gormtestutil.NewMemoryDatabase(t, gormtestutil.WithName(t.Name())) database.Use(gormqonvert.New(gormqonvert.CharacterConfig{ @@ -823,14 +841,13 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { database.CreateInBatches(testData.records, len(testData.records)) // Act - // sqlQuery := database.ToSQL(func (tx *gorm.DB) *gorm.DB { - // deepFilterQuery, _ := AddDeepFilters(tx, SimpleStruct6{}, testData.filterMap) - // return deepFilterQuery.Find(&SimpleStruct6{}) - // }) - // fmt.Printf("sqlQuery: %s\n", sqlQuery) + sqlQuery := database.ToSQL(func(tx *gorm.DB) *gorm.DB { + deepFilterQuery, _ := AddDeepFilters(tx, SimpleStruct6{}, testData.filterMap) + return deepFilterQuery.Find(&SimpleStruct6{}) + }) + fmt.Printf("sqlQuery: %s\n", sqlQuery) query, err := AddDeepFilters(database, SimpleStruct6{}, testData.filterMap) - // Assert assert.Nil(t, err) @@ -976,14 +993,13 @@ func TestAddDeepFilters_AddsSimpleFilters(t *testing.T) { database.CreateInBatches(testData.records, len(testData.records)) // Act - sqlQuery := database.ToSQL(func (tx *gorm.DB) *gorm.DB { + sqlQuery := database.ToSQL(func(tx *gorm.DB) *gorm.DB { deepFilterQuery, _ := AddDeepFilters(tx, SimpleStruct6{}, testData.filterMap) return deepFilterQuery.Find(&SimpleStruct6{}) }) fmt.Printf("sqlQuery: %s\n", sqlQuery) query, err := AddDeepFilters(database, SimpleStruct6{}, testData.filterMap) - // Assert assert.Nil(t, err) diff --git a/go.mod b/go.mod index abb73b2..abd01de 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,10 @@ toolchain go1.23.5 require ( github.com/google/uuid v1.3.0 github.com/ing-bank/gormtestutil v0.0.0 + github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.1 github.com/survivorbat/go-tsyncmap v0.0.0 + github.com/survivorbat/gorm-query-convert v0.0.1 gorm.io/driver/sqlite v1.5.2 gorm.io/gorm v1.25.12 ) @@ -17,11 +19,8 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/kr/text v0.2.0 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/survivorbat/gorm-query-convert v0.0.1 // indirect golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect golang.org/x/text v0.23.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 4219c64..6aa9dba 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,3 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -11,6 +10,7 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= @@ -18,6 +18,7 @@ github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -34,12 +35,11 @@ github.com/survivorbat/gorm-query-convert v0.0.1 h1:e/wesF2ajiryuJhH/Vs9WrsTU5Mh github.com/survivorbat/gorm-query-convert v0.0.1/go.mod h1:3YidTdfqM+KscRLFKhSUNl16tO0CqlEJcG0dFFpVl0g= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 646dd705e6f0de24c45a34b9c2129a75863df42e Mon Sep 17 00:00:00 2001 From: Bram Cautaerts Date: Tue, 13 May 2025 21:47:31 +0200 Subject: [PATCH 04/12] go mod tidy --- go.mod | 2 -- go.sum | 5 ----- 2 files changed, 7 deletions(-) diff --git a/go.mod b/go.mod index abd01de..24e68d4 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ toolchain go1.23.5 require ( github.com/google/uuid v1.3.0 github.com/ing-bank/gormtestutil v0.0.0 - github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.1 github.com/survivorbat/go-tsyncmap v0.0.0 github.com/survivorbat/gorm-query-convert v0.0.1 @@ -21,7 +20,6 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect golang.org/x/text v0.23.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 6aa9dba..58851f1 100644 --- a/go.sum +++ b/go.sum @@ -19,12 +19,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= @@ -33,8 +30,6 @@ github.com/survivorbat/go-tsyncmap v0.0.0 h1:XTc1+uXyuw//1Hhpg4IxW6tEe3Tvd2d5vM/ github.com/survivorbat/go-tsyncmap v0.0.0/go.mod h1:zKe2CuXEo+c1d9DVT5L7AG2jPTdWi7QQN/Gk+26Vecg= github.com/survivorbat/gorm-query-convert v0.0.1 h1:e/wesF2ajiryuJhH/Vs9WrsTU5Mh6x3VRkdFtm08l0E= github.com/survivorbat/gorm-query-convert v0.0.1/go.mod h1:3YidTdfqM+KscRLFKhSUNl16tO0CqlEJcG0dFFpVl0g= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From a46f22bbbdbe7c0eafe46788f79676c19617e6f4 Mon Sep 17 00:00:00 2001 From: bramca Date: Sat, 17 May 2025 17:18:44 +0200 Subject: [PATCH 05/12] wip filtering with sql functions --- deep-filtering.go | 38 +++++++++++++++++++++++++++++++++++--- deep-filtering_test.go | 2 +- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/deep-filtering.go b/deep-filtering.go index 4b788bb..4ad2813 100644 --- a/deep-filtering.go +++ b/deep-filtering.go @@ -85,7 +85,7 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go relationalTypesInfo := getDatabaseFieldsOfType(db.NamingStrategy, schemaInfo) simpleFilter := map[string]any{} - filterString := "" + totalFilterString := "" functionRegex := regexp.MustCompile(`.*?\((.*?)\)`) qonvertMap := map[string]string{} @@ -105,6 +105,7 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go for _, filterObject := range filters { // Go through all the keys of the filters for fieldName, givenFilter := range filterObject { + filterString := "" switch givenFilter.(type) { // WithFilters for relational objects case map[string]any: @@ -134,6 +135,8 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go for _, filter := range givenFilter.([]string) { containedQonvert := false for qonvertField, qonvertValue := range qonvertMap { + // TODO: this check should be revisited to start with the longest possible substring matching + // for qonvertFields like < and <= -> should not overlap if !strings.Contains(filter, qonvertField) { continue } @@ -165,6 +168,13 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go } } } + + filterString = fmt.Sprintf("(%s)", filterString) + if totalFilterString != "" { + totalFilterString += " AND " + } + totalFilterString += filterString + break } if _, ok := givenFilter.([]int); ok { @@ -175,6 +185,13 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go filterString = fmt.Sprintf("%s OR %s", filterString, fmt.Sprintf("%s = %d", fieldName, filter)) } } + + filterString = fmt.Sprintf("(%s)", filterString) + if totalFilterString != "" { + totalFilterString += " AND " + } + totalFilterString += filterString + break } if _, ok := givenFilter.([]bool); ok { @@ -185,9 +202,19 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go filterString = fmt.Sprintf("%s OR %s", filterString, fmt.Sprintf("%s = %t", fieldName, filter)) } } + + filterString = fmt.Sprintf("(%s)", filterString) + if totalFilterString != "" { + totalFilterString += " AND " + } + totalFilterString += filterString + break } for qonvertField, qonvertValue := range qonvertMap { + + // TODO: this check should be revisited to start with the longest possible substring matching + // for qonvertFields like < and <= -> should not overlap if filterStrCast, castOk := givenFilter.(string); !castOk || !strings.Contains(filterStrCast, qonvertField) { continue } @@ -208,6 +235,11 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go filterString = prepareFilterValueCast(fieldName, "=", givenFilter) } + filterString = fmt.Sprintf("(%s)", filterString) + if totalFilterString != "" { + totalFilterString += " AND " + } + totalFilterString += filterString break } @@ -220,8 +252,8 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go } // Add simple filters - if filterString != "" { - db = db.Where(filterString) + if totalFilterString != "" { + db = db.Where(totalFilterString) } db = db.Where(simpleFilter) diff --git a/deep-filtering_test.go b/deep-filtering_test.go index 47377ba..2413263 100644 --- a/deep-filtering_test.go +++ b/deep-filtering_test.go @@ -843,7 +843,7 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { // Act sqlQuery := database.ToSQL(func(tx *gorm.DB) *gorm.DB { deepFilterQuery, _ := AddDeepFilters(tx, SimpleStruct6{}, testData.filterMap) - return deepFilterQuery.Find(&SimpleStruct6{}) + return deepFilterQuery.Find([]*SimpleStruct6{}) }) fmt.Printf("sqlQuery: %s\n", sqlQuery) query, err := AddDeepFilters(database, SimpleStruct6{}, testData.filterMap) From f21f21a7d3b311ecc5b6d2c9d83dcd2b6681bac0 Mon Sep 17 00:00:00 2001 From: bramca Date: Mon, 19 May 2025 10:06:21 +0200 Subject: [PATCH 06/12] wip adding tests --- deep-filtering.go | 71 +++++---- deep-filtering_test.go | 327 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 363 insertions(+), 35 deletions(-) diff --git a/deep-filtering.go b/deep-filtering.go index 4ad2813..3558846 100644 --- a/deep-filtering.go +++ b/deep-filtering.go @@ -134,38 +134,40 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go if _, ok := givenFilter.([]string); ok { for _, filter := range givenFilter.([]string) { containedQonvert := false + qonvertOperator := "" + qonvertFilter := "" for qonvertField, qonvertValue := range qonvertMap { - // TODO: this check should be revisited to start with the longest possible substring matching - // for qonvertFields like < and <= -> should not overlap - if !strings.Contains(filter, qonvertField) { - continue + // Find longest possible prefix match in filter + // (e.g. to make sure <= is fully matched and not overwritten by <) + if strings.HasPrefix(filter, qonvertField) && len(qonvertField) > len(qonvertFilter) { + qonvertOperator = qonvertValue + qonvertFilter = qonvertField + containedQonvert = true } + } + - if strings.Contains(qonvertValue, "%") { + if !containedQonvert { + if filterString == "" { + filterString = fmt.Sprintf("%s = '%s'", fieldName, filter) + } else { + filterString = fmt.Sprintf("%s OR %s", filterString, fmt.Sprintf("%s = '%s'", fieldName, filter)) + } + } else { + filter = strings.Replace(filter, qonvertFilter, "", 1) + if strings.Contains(qonvertOperator, "%") { if filterString == "" { - filterString = fmt.Sprintf(qonvertValue, fieldName, filter) + filterString = fmt.Sprintf(qonvertOperator, fieldName, filter) } else { - filterString = fmt.Sprintf("%s OR %s", filterString, fmt.Sprintf(qonvertValue, fieldName, filter)) + filterString = fmt.Sprintf("%s OR %s", filterString, fmt.Sprintf(qonvertOperator, fieldName, filter)) } } else { - filter = strings.Replace(filter, qonvertValue, "", 1) if filterString == "" { - filterString = prepareFilterValue(fieldName, qonvertValue, filter) + filterString = prepareFilterValue(fieldName, qonvertOperator, filter) } else { - filterString = fmt.Sprintf("%s OR %s", filterString, prepareFilterValue(fieldName, qonvertValue, filter)) + filterString = fmt.Sprintf("%s OR %s", filterString, prepareFilterValue(fieldName, qonvertOperator, filter)) } } - - containedQonvert = true - break - } - - if !containedQonvert { - if filterString == "" { - filterString = fmt.Sprintf("%s = '%s'", fieldName, filter) - } else { - filterString = fmt.Sprintf("%s OR %s", filterString, fmt.Sprintf("%s = '%s'", fieldName, filter)) - } } } @@ -211,23 +213,30 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go break } + containedQonvert := false + qonvertOperator := "" + qonvertFilter := "" for qonvertField, qonvertValue := range qonvertMap { - - // TODO: this check should be revisited to start with the longest possible substring matching - // for qonvertFields like < and <= -> should not overlap - if filterStrCast, castOk := givenFilter.(string); !castOk || !strings.Contains(filterStrCast, qonvertField) { - continue + // Find longest possible prefix match in filter + // (e.g. to make sure <= is fully matched and not overwritten by <) + if filterStrCast, castOk := givenFilter.(string); castOk && strings.HasPrefix(filterStrCast, qonvertField) && len(qonvertField) > len(qonvertFilter) { + qonvertOperator = qonvertValue + qonvertFilter = qonvertField + containedQonvert = true } - if strings.Contains(qonvertField, "%") { + } + + if containedQonvert { + givenFilter = strings.Replace(givenFilter.(string), qonvertFilter, "", 1) + if strings.Contains(qonvertOperator, "%") { if filterString == "" { - filterString = fmt.Sprintf(qonvertValue, fieldName, givenFilter) + filterString = fmt.Sprintf(qonvertOperator, fieldName, givenFilter) } else { - filterString = fmt.Sprintf("%s OR %s", filterString, fmt.Sprintf(qonvertValue, fieldName, givenFilter)) + filterString = fmt.Sprintf("%s OR %s", filterString, fmt.Sprintf(qonvertOperator, fieldName, givenFilter)) } } else { - givenFilter = strings.Replace(givenFilter.(string), qonvertValue, "", 1) - filterString = prepareFilterValue(fieldName, qonvertValue, givenFilter.(string)) + filterString = prepareFilterValue(fieldName, qonvertOperator, givenFilter.(string)) } } diff --git a/deep-filtering_test.go b/deep-filtering_test.go index 2413263..e48552c 100644 --- a/deep-filtering_test.go +++ b/deep-filtering_test.go @@ -700,9 +700,10 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { } tests := map[string]struct { - records []*SimpleStruct6 - expected []*SimpleStruct6 - filterMap map[string]any + records []*SimpleStruct6 + expected []*SimpleStruct6 + filterMap map[string]any + expectedQuery string }{ "simple filter": { records: []*SimpleStruct6{ @@ -724,6 +725,7 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { filterMap: map[string]any{ "LOWER(occupation)": "ops", }, + expectedQuery: "SELECT * FROM `simple_struct6` WHERE (LOWER(occupation) = 'ops')", }, "and filter with qonvert": { records: []*SimpleStruct6{ @@ -746,6 +748,7 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { "occupation": "Ops", "LENGTH(name)": ">4", }, + expectedQuery: "SELECT * FROM `simple_struct6` WHERE (LENGTH(name) > 4) AND `occupation` = \"Ops\"", }, "simple or filter strings": { records: []*SimpleStruct6{ @@ -771,6 +774,7 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { filterMap: map[string]any{ "UPPER(occupation)": []string{"OPS", "DEV"}, }, + expectedQuery: "SELECT * FROM `simple_struct6` WHERE (UPPER(occupation) = 'OPS' OR UPPER(occupation) = 'DEV')", }, "simple or filter int": { records: []*SimpleStruct6{ @@ -796,6 +800,7 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { filterMap: map[string]any{ "LENGTH(name)": []int{4, 8}, }, + expectedQuery: "SELECT * FROM `simple_struct6` WHERE (LENGTH(name) = 4 OR LENGTH(name) = 8)", }, "or and filter with convert": { records: []*SimpleStruct6{ @@ -818,6 +823,82 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { "UPPER(occupation)": []string{"OPS", "DEV"}, "LENGTH(name)": []string{"<=4", ">8"}, }, + expectedQuery: "SELECT * FROM `simple_struct6` WHERE (LENGTH(name) <= 4 OR LENGTH(name) > 8) AND (UPPER(occupation) = 'OPS' OR UPPER(occupation) = 'DEV')", + }, + "simple filter with like prefix": { + records: []*SimpleStruct6{ + { + Occupation: "Dev", + Name: "John", + }, + { + Occupation: "Ops", + Name: "Jennifer", + }, + }, + expected: []*SimpleStruct6{ + { + Occupation: "Dev", + Name: "John", + }, + { + Occupation: "Ops", + Name: "Jennifer", + }, + }, + filterMap: map[string]any{ + "UPPER(name)": []string{"~J"}, + }, + expectedQuery: "SELECT * FROM `simple_struct6` WHERE (UPPER(name) LIKE '%J%')", + }, + "simple or filter with like prefix": { + records: []*SimpleStruct6{ + { + Occupation: "Dev", + Name: "John", + }, + { + Occupation: "Ops", + Name: "Jennifer", + }, + }, + expected: []*SimpleStruct6{ + { + Occupation: "Dev", + Name: "John", + }, + { + Occupation: "Ops", + Name: "Jennifer", + }, + }, + filterMap: map[string]any{ + "UPPER(occupation)": []string{"~EV", "~OP"}, + }, + expectedQuery: "SELECT * FROM `simple_struct6` WHERE (UPPER(occupation) LIKE '%EV%' OR UPPER(occupation) LIKE '%OP%')", + }, + "and or filter with like prefix": { + records: []*SimpleStruct6{ + { + Occupation: "Dev", + Name: "John", + }, + { + Occupation: "Ops", + Name: "Jennifer", + }, + }, + expected: []*SimpleStruct6{ + { + Occupation: "Ops", + Name: "Jennifer", + }, + }, + filterMap: map[string]any{ + "UPPER(occupation)": []string{"!=DEV", "!~TEST"}, + "LENGTH(name)": ">=8", + }, + expectedQuery: "SELECT * FROM `simple_struct6` WHERE (UPPER(occupation) != 'DEV' OR UPPER(occupation) NOT LIKE '%TEST%') AND (LENGTH(name) >= 8)", }, } @@ -845,7 +926,6 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { deepFilterQuery, _ := AddDeepFilters(tx, SimpleStruct6{}, testData.filterMap) return deepFilterQuery.Find([]*SimpleStruct6{}) }) - fmt.Printf("sqlQuery: %s\n", sqlQuery) query, err := AddDeepFilters(database, SimpleStruct6{}, testData.filterMap) // Assert @@ -856,6 +936,7 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { query.Preload(clause.Associations).Find(&result) assert.EqualValues(t, testData.expected, result) + assert.Equal(t, testData.expectedQuery, sqlQuery) } }) } @@ -1013,6 +1094,244 @@ func TestAddDeepFilters_AddsSimpleFilters(t *testing.T) { } } +// TODO: Test functions with nested structs +func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) { + t.Parallel() + t.Cleanup(cleanupCache) + tests := map[string]struct { + records []*ComplexStruct1 + expected []ComplexStruct1 + filterMap map[string]any + }{ + "looking for 1 katherina": { + records: []*ComplexStruct1{ + { + ID: uuid.MustParse("59aa5a8f-c5de-44fa-9355-080650481687"), + Value: 1, + NestedRef: uuid.MustParse("71766db4-eb17-4457-a85c-8b89af5a319d"), // A + Nested: &NestedStruct4{ + ID: uuid.MustParse("71766db4-eb17-4457-a85c-8b89af5a319d"), // A + Name: "Johan", + Occupation: "Dev", + }, + }, + { + ID: uuid.MustParse("23292d51-4768-4c41-8475-6d4c9f0c6f69"), + Value: 11, + NestedRef: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c4"), // BObject + Nested: &NestedStruct4{ + + ID: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c4"), // BObject + Name: "Katherina", + Occupation: "Dev", + }, + }, + }, + expected: []ComplexStruct1{ + { + ID: uuid.MustParse("23292d51-4768-4c41-8475-6d4c9f0c6f69"), + Value: 11, + NestedRef: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c4"), // BObject + Nested: &NestedStruct4{ + ID: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c4"), // BObject + Name: "Katherina", + Occupation: "Dev", + }, + }, + }, + filterMap: map[string]any{ + "nested": map[string]any{ + "name": "Katherina", + }, + }, + }, + "looking for 1 katherina and value 11": { + records: []*ComplexStruct1{ + { + ID: uuid.MustParse("59aa5a8f-c5de-44fa-9355-080650481687"), + Value: 1, + NestedRef: uuid.MustParse("71766db4-eb17-4457-a85c-8b89af5a319d"), // A + Nested: &NestedStruct4{ + ID: uuid.MustParse("71766db4-eb17-4457-a85c-8b89af5a319d"), // A + Name: "Johan", + Occupation: "Dev", + }, + }, + { + ID: uuid.MustParse("23292d51-4768-4c41-8475-6d4c9f0c6f69"), + Value: 11, + NestedRef: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c4"), // BObject + Nested: &NestedStruct4{ + + ID: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c4"), // BObject + Name: "Katherina", + Occupation: "Dev", + }, + }, + }, + expected: []ComplexStruct1{ + { + ID: uuid.MustParse("23292d51-4768-4c41-8475-6d4c9f0c6f69"), + Value: 11, + NestedRef: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c4"), // BObject + Nested: &NestedStruct4{ + ID: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c4"), // BObject + Name: "Katherina", + Occupation: "Dev", + }, + }, + }, + filterMap: map[string]any{ + "nested": map[string]any{ + "name": "Katherina", + }, + "value": 11, + }, + }, + "looking for 2 vanessas": { + records: []*ComplexStruct1{ + { + ID: uuid.MustParse("c98dc9f2-bfa5-4ab5-9cbb-76800e09e512"), + Value: 4, + NestedRef: uuid.MustParse("71766db4-eb17-4457-a85c-8b89af5a319d"), // A + Nested: &NestedStruct4{ + ID: uuid.MustParse("71766db4-eb17-4457-a85c-8b89af5a319d"), // A + Name: "Vanessa", + Occupation: "Ops", + }, + }, + { + ID: uuid.MustParse("2ad6a4fe-e0a4-4791-8f10-df6317cdb8b5"), + Value: 193, + NestedRef: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c4"), // BObject + Nested: &NestedStruct4{ + ID: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c4"), // BObject + Name: "Vanessa", + Occupation: "Dev", + }, + }, + { + ID: uuid.MustParse("5cc022ae-43a1-44d8-8ab5-31350e68d0b1"), + Value: 1593, + NestedRef: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c5"), // C + Nested: &NestedStruct4{ + ID: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c5"), // C + Name: "Derek", + Occupation: "Dev", + }, + }, + }, + expected: []ComplexStruct1{ + { + ID: uuid.MustParse("c98dc9f2-bfa5-4ab5-9cbb-76800e09e512"), + Value: 4, + NestedRef: uuid.MustParse("71766db4-eb17-4457-a85c-8b89af5a319d"), // A + Nested: &NestedStruct4{ + ID: uuid.MustParse("71766db4-eb17-4457-a85c-8b89af5a319d"), // A + Name: "Vanessa", + Occupation: "Ops", + }, + }, + { + ID: uuid.MustParse("2ad6a4fe-e0a4-4791-8f10-df6317cdb8b5"), + Value: 193, + NestedRef: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c4"), // BObject + Nested: &NestedStruct4{ + ID: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c4"), // BObject + Name: "Vanessa", + Occupation: "Dev", + }, + }, + }, + filterMap: map[string]any{ + "nested": map[string]any{ + "name": "Vanessa", + }, + }, + }, + "looking for both coat and joke": { + records: []*ComplexStruct1{ + { + ID: uuid.MustParse("59aa5a8f-c5de-44fa-9355-080650481687"), + Value: 1, + NestedRef: uuid.MustParse("71766db4-eb17-4457-a85c-8b89af5a319d"), // A + Nested: &NestedStruct4{ + ID: uuid.MustParse("71766db4-eb17-4457-a85c-8b89af5a319d"), // A + Name: "Coat", + Occupation: "Product Owner", + }, + }, + { + ID: uuid.MustParse("23292d51-4768-4c41-8475-6d4c9f0c6f69"), + Value: 2, + NestedRef: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c4"), // BObject + Nested: &NestedStruct4{ + ID: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c4"), // BObject + Name: "Joke", + Occupation: "Ops", + }, + }, + }, + expected: []ComplexStruct1{ + { + ID: uuid.MustParse("59aa5a8f-c5de-44fa-9355-080650481687"), + Value: 1, + NestedRef: uuid.MustParse("71766db4-eb17-4457-a85c-8b89af5a319d"), // A + Nested: &NestedStruct4{ + ID: uuid.MustParse("71766db4-eb17-4457-a85c-8b89af5a319d"), // A + Name: "Coat", + Occupation: "Product Owner", + }, + }, + { + ID: uuid.MustParse("23292d51-4768-4c41-8475-6d4c9f0c6f69"), + Value: 2, + NestedRef: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c4"), // BObject + Nested: &NestedStruct4{ + ID: uuid.MustParse("4604bb79-ee05-4a09-b874-c3af8964d8c4"), // BObject + Name: "Joke", + Occupation: "Ops", + }, + }, + }, + filterMap: map[string]any{ + "nested": map[string]any{ + "name": []string{"Joke", "Coat"}, + }, + }, + }, + } + + for name, testData := range tests { + testData := testData + t.Run(name, func(t *testing.T) { + t.Parallel() + // Arrange + database := gormtestutil.NewMemoryDatabase(t, gormtestutil.WithName(t.Name())) + _ = database.AutoMigrate(&ComplexStruct1{}, &NestedStruct4{}) + + // Crate records + database.CreateInBatches(testData.records, len(testData.records)) + + // Act + query, err := AddDeepFilters(database, ComplexStruct1{}, testData.filterMap) + + // Assert + assert.Nil(t, err) + + if assert.NotNil(t, query) { + var result []ComplexStruct1 + res := query.Preload(clause.Associations).Find(&result) + + // Handle error + assert.Nil(t, res.Error) + + assert.Equal(t, testData.expected, result) + } + }) + } +} + func TestAddDeepFilters_AddsDeepFiltersWithOneToMany(t *testing.T) { t.Parallel() t.Cleanup(cleanupCache) From a82d16b8b301f50fbac9974690d65d095259e49a Mon Sep 17 00:00:00 2001 From: bramca Date: Mon, 19 May 2025 16:00:30 +0200 Subject: [PATCH 07/12] update tests --- deep-filtering_test.go | 100 +++++++++++++++++++++++++++++++---------- 1 file changed, 76 insertions(+), 24 deletions(-) diff --git a/deep-filtering_test.go b/deep-filtering_test.go index e48552c..4506e4e 100644 --- a/deep-filtering_test.go +++ b/deep-filtering_test.go @@ -1074,11 +1074,6 @@ func TestAddDeepFilters_AddsSimpleFilters(t *testing.T) { database.CreateInBatches(testData.records, len(testData.records)) // Act - sqlQuery := database.ToSQL(func(tx *gorm.DB) *gorm.DB { - deepFilterQuery, _ := AddDeepFilters(tx, SimpleStruct6{}, testData.filterMap) - return deepFilterQuery.Find(&SimpleStruct6{}) - }) - fmt.Printf("sqlQuery: %s\n", sqlQuery) query, err := AddDeepFilters(database, SimpleStruct6{}, testData.filterMap) // Assert @@ -1094,7 +1089,6 @@ func TestAddDeepFilters_AddsSimpleFilters(t *testing.T) { } } -// TODO: Test functions with nested structs func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) { t.Parallel() t.Cleanup(cleanupCache) @@ -1102,8 +1096,9 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) records []*ComplexStruct1 expected []ComplexStruct1 filterMap map[string]any + expectedQuery string }{ - "looking for 1 katherina": { + "simple filter with function": { records: []*ComplexStruct1{ { ID: uuid.MustParse("59aa5a8f-c5de-44fa-9355-080650481687"), @@ -1141,11 +1136,12 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) }, filterMap: map[string]any{ "nested": map[string]any{ - "name": "Katherina", + "LOWER(name)": "katherina", }, }, + expectedQuery: "SELECT * FROM `complex_struct1` WHERE nested_ref IN (SELECT `id` FROM `nested_struct4` WHERE (LOWER(name) = 'katherina'))", }, - "looking for 1 katherina and value 11": { + "more complex query with simple filter function": { records: []*ComplexStruct1{ { ID: uuid.MustParse("59aa5a8f-c5de-44fa-9355-080650481687"), @@ -1183,12 +1179,13 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) }, filterMap: map[string]any{ "nested": map[string]any{ - "name": "Katherina", + "UPPER(name)": "KATHERINA", }, "value": 11, }, + expectedQuery: "SELECT * FROM `complex_struct1` WHERE nested_ref IN (SELECT `id` FROM `nested_struct4` WHERE (UPPER(name) = 'KATHERINA')) AND `value` = 11", }, - "looking for 2 vanessas": { + "or query with function": { records: []*ComplexStruct1{ { ID: uuid.MustParse("c98dc9f2-bfa5-4ab5-9cbb-76800e09e512"), @@ -1245,11 +1242,13 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) }, filterMap: map[string]any{ "nested": map[string]any{ - "name": "Vanessa", + "LOWER(name)": "vanessa", + "UPPER(occupation)": []string{"DEV", "OPS"}, }, }, + expectedQuery: "SELECT * FROM `complex_struct1` WHERE nested_ref IN (SELECT `id` FROM `nested_struct4` WHERE (LOWER(name) = 'vanessa') AND (UPPER(occupation) = 'DEV' OR UPPER(occupation) = 'OPS'))", }, - "looking for both coat and joke": { + "query with qonvert and function": { records: []*ComplexStruct1{ { ID: uuid.MustParse("59aa5a8f-c5de-44fa-9355-080650481687"), @@ -1273,16 +1272,6 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) }, }, expected: []ComplexStruct1{ - { - ID: uuid.MustParse("59aa5a8f-c5de-44fa-9355-080650481687"), - Value: 1, - NestedRef: uuid.MustParse("71766db4-eb17-4457-a85c-8b89af5a319d"), // A - Nested: &NestedStruct4{ - ID: uuid.MustParse("71766db4-eb17-4457-a85c-8b89af5a319d"), // A - Name: "Coat", - Occupation: "Product Owner", - }, - }, { ID: uuid.MustParse("23292d51-4768-4c41-8475-6d4c9f0c6f69"), Value: 2, @@ -1296,9 +1285,12 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) }, filterMap: map[string]any{ "nested": map[string]any{ - "name": []string{"Joke", "Coat"}, + "UPPER(name)": []string{"~OK", "~AT"}, + "LENGTH(occupation)": ">=3", }, + "value": 2, }, + expectedQuery: "SELECT * FROM `complex_struct1` WHERE nested_ref IN (SELECT `id` FROM `nested_struct4` WHERE (UPPER(name) LIKE '%OK%' OR UPPER(name) LIKE '%AT%') AND (LENGTH(occupation) >= 3)) AND `value` = 2", }, } @@ -1308,12 +1300,25 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) t.Parallel() // Arrange database := gormtestutil.NewMemoryDatabase(t, gormtestutil.WithName(t.Name())) + database.Use(gormqonvert.New(gormqonvert.CharacterConfig{ + GreaterThanPrefix: ">", + GreaterOrEqualToPrefix: ">=", + LessThanPrefix: "<", + LessOrEqualToPrefix: "<=", + NotEqualToPrefix: "!=", + LikePrefix: "~", + NotLikePrefix: "!~", + })) _ = database.AutoMigrate(&ComplexStruct1{}, &NestedStruct4{}) // Crate records database.CreateInBatches(testData.records, len(testData.records)) // Act + sqlQuery := database.ToSQL(func(tx *gorm.DB) *gorm.DB { + deepFilterQuery, _ := AddDeepFilters(tx, ComplexStruct1{}, testData.filterMap) + return deepFilterQuery.Find(&ComplexStruct1{}) + }) query, err := AddDeepFilters(database, ComplexStruct1{}, testData.filterMap) // Assert @@ -1327,6 +1332,7 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) assert.Nil(t, res.Error) assert.Equal(t, testData.expected, result) + assert.Equal(t, testData.expectedQuery, sqlQuery) } }) } @@ -2288,6 +2294,52 @@ func TestAddDeepFilters_ReturnsErrorOnNonExistingFields(t *testing.T) { }, expectedErrorMsg: "failed to add filters for 'tag_values.key': field does not exist", }, + "one to many filter with function": { + records: []*ComplexStruct3{ + { + ID: uuid.MustParse("59aa5a8f-c5de-44fa-9355-080650481687"), // A + Name: "Python", + Tags: []*Tag{ + { + ID: uuid.MustParse("1c83a7c9-e95d-4dba-b858-5eb4e34ebcf2"), + ComplexStructRef: uuid.MustParse("59aa5a8f-c5de-44fa-9355-080650481687"), + Key: "type", + Value: "interpreted", + TagValue: &TagValue{ + ID: uuid.MustParse("38769e29-e945-451f-a551-3e5811a5d363"), + Value: "test-python-value", + }, + }, + }, + }, + { + ID: uuid.MustParse("23292d51-4768-4c41-8475-6d4c9f0c6f69"), // BObject + Name: "Go", + Tags: []*Tag{ + { + ID: uuid.MustParse("17983ba8-2d26-4e36-bb6b-6c5a04b6606e"), + ComplexStructRef: uuid.MustParse("23292d51-4768-4c41-8475-6d4c9f0c6f69"), + Key: "type", + Value: "compiled", + TagValue: &TagValue{ + ID: uuid.MustParse("e75a2f7e-0e1c-4f9c-a8ce-af90f1b64baa"), + Value: "test-go-value", + }, + }, + }, + }, + }, + filterMap: []map[string]any{ + { + "tags": map[string]any{ + "tag_value": map[string]any{ + "LOWER(key)": "test-python-value", + }, + }, + }, + }, + expectedErrorMsg: "failed to add filters for 'tag_values.key': field does not exist", + }, "many to one filter": { records: []*ComplexStruct3{ { From c45a4d8c5d32bceb26946689b8144ab1207c181d Mon Sep 17 00:00:00 2001 From: bramca Date: Mon, 19 May 2025 16:09:40 +0200 Subject: [PATCH 08/12] add comments --- deep-filtering.go | 1 + deep-filtering_test.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/deep-filtering.go b/deep-filtering.go index 3558846..c9e8b6f 100644 --- a/deep-filtering.go +++ b/deep-filtering.go @@ -125,6 +125,7 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go // Simple filters (string, int, bool etc.) default: + // If the simple filter contains a function, build the query different if functionRegex.MatchString(fieldName) { checkFieldName := functionRegex.ReplaceAllString(fieldName, "$1") if _, ok := schemaInfo.FieldsByDBName[checkFieldName]; !ok { diff --git a/deep-filtering_test.go b/deep-filtering_test.go index 4506e4e..34711f1 100644 --- a/deep-filtering_test.go +++ b/deep-filtering_test.go @@ -802,6 +802,7 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { }, expectedQuery: "SELECT * FROM `simple_struct6` WHERE (LENGTH(name) = 4 OR LENGTH(name) = 8)", }, + // TODO: this test result is flaky in the expectedQuery check (order of the AND/OR operations is random) "or and filter with convert": { records: []*SimpleStruct6{ { @@ -877,6 +878,7 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { }, expectedQuery: "SELECT * FROM `simple_struct6` WHERE (UPPER(occupation) LIKE '%EV%' OR UPPER(occupation) LIKE '%OP%')", }, + // TODO: this test result is flaky in the expectedQuery check (order of the AND/OR operations is random) "and or filter with like prefix": { records: []*SimpleStruct6{ { From 7b30e90132be05ee873e39f3b5b13ac2214af863 Mon Sep 17 00:00:00 2001 From: bramca Date: Tue, 20 May 2025 16:04:52 +0200 Subject: [PATCH 09/12] fix flaky tests --- deep-filtering.go | 18 ----- deep-filtering_test.go | 180 ++++++++++++++++++++++++++++++----------- 2 files changed, 132 insertions(+), 66 deletions(-) diff --git a/deep-filtering.go b/deep-filtering.go index c9e8b6f..42e5e2f 100644 --- a/deep-filtering.go +++ b/deep-filtering.go @@ -147,7 +147,6 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go } } - if !containedQonvert { if filterString == "" { filterString = fmt.Sprintf("%s = '%s'", fieldName, filter) @@ -197,23 +196,6 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go break } - if _, ok := givenFilter.([]bool); ok { - for _, filter := range givenFilter.([]bool) { - if filterString == "" { - filterString = fmt.Sprintf("%s = %t", fieldName, filter) - } else { - filterString = fmt.Sprintf("%s OR %s", filterString, fmt.Sprintf("%s = %t", fieldName, filter)) - } - } - - filterString = fmt.Sprintf("(%s)", filterString) - if totalFilterString != "" { - totalFilterString += " AND " - } - totalFilterString += filterString - break - } - containedQonvert := false qonvertOperator := "" qonvertFilter := "" diff --git a/deep-filtering_test.go b/deep-filtering_test.go index 34711f1..ddb4f28 100644 --- a/deep-filtering_test.go +++ b/deep-filtering_test.go @@ -702,7 +702,7 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { tests := map[string]struct { records []*SimpleStruct6 expected []*SimpleStruct6 - filterMap map[string]any + filters []map[string]any expectedQuery string }{ "simple filter": { @@ -722,11 +722,65 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { Name: "Jennifer", }, }, - filterMap: map[string]any{ - "LOWER(occupation)": "ops", + filters: []map[string]any{ + { + "LOWER(occupation)": "ops", + }, }, expectedQuery: "SELECT * FROM `simple_struct6` WHERE (LOWER(occupation) = 'ops')", }, + "simple like filter": { + records: []*SimpleStruct6{ + { + Occupation: "Dev", + Name: "John", + }, + { + Occupation: "Ops", + Name: "Jennifer", + }, + }, + expected: []*SimpleStruct6{ + { + Occupation: "Ops", + Name: "Jennifer", + }, + }, + filters: []map[string]any{ + { + "LOWER(occupation)": "~op", + }, + }, + expectedQuery: "SELECT * FROM `simple_struct6` WHERE (LOWER(occupation) LIKE '%op%')", + }, + "simple like filter with or": { + records: []*SimpleStruct6{ + { + Occupation: "Dev", + Name: "John", + }, + { + Occupation: "Ops", + Name: "Jennifer", + }, + }, + expected: []*SimpleStruct6{ + { + Occupation: "Dev", + Name: "John", + }, + { + Occupation: "Ops", + Name: "Jennifer", + }, + }, + filters: []map[string]any{ + { + "LOWER(occupation)": []string{"~op", "~de"}, + }, + }, + expectedQuery: "SELECT * FROM `simple_struct6` WHERE (LOWER(occupation) LIKE '%op%' OR LOWER(occupation) LIKE '%de%')", + }, "and filter with qonvert": { records: []*SimpleStruct6{ { @@ -744,9 +798,13 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { Name: "Jennifer", }, }, - filterMap: map[string]any{ - "occupation": "Ops", - "LENGTH(name)": ">4", + filters: []map[string]any{ + { + "occupation": "Ops", + }, + { + "LENGTH(name)": ">4", + }, }, expectedQuery: "SELECT * FROM `simple_struct6` WHERE (LENGTH(name) > 4) AND `occupation` = \"Ops\"", }, @@ -771,8 +829,10 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { Name: "Jennifer", }, }, - filterMap: map[string]any{ - "UPPER(occupation)": []string{"OPS", "DEV"}, + filters: []map[string]any{ + { + "UPPER(occupation)": []string{"OPS", "DEV"}, + }, }, expectedQuery: "SELECT * FROM `simple_struct6` WHERE (UPPER(occupation) = 'OPS' OR UPPER(occupation) = 'DEV')", }, @@ -797,12 +857,13 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { Name: "Jennifer", }, }, - filterMap: map[string]any{ - "LENGTH(name)": []int{4, 8}, + filters: []map[string]any{ + { + "LENGTH(name)": []int{4, 8}, + }, }, expectedQuery: "SELECT * FROM `simple_struct6` WHERE (LENGTH(name) = 4 OR LENGTH(name) = 8)", }, - // TODO: this test result is flaky in the expectedQuery check (order of the AND/OR operations is random) "or and filter with convert": { records: []*SimpleStruct6{ { @@ -820,11 +881,15 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { Name: "John", }, }, - filterMap: map[string]any{ - "UPPER(occupation)": []string{"OPS", "DEV"}, - "LENGTH(name)": []string{"<=4", ">8"}, + filters: []map[string]any{ + { + "UPPER(occupation)": []string{"OPS", "DEV"}, + }, + { + "LENGTH(name)": []string{"<=4", ">8"}, + }, }, - expectedQuery: "SELECT * FROM `simple_struct6` WHERE (LENGTH(name) <= 4 OR LENGTH(name) > 8) AND (UPPER(occupation) = 'OPS' OR UPPER(occupation) = 'DEV')", + expectedQuery: "SELECT * FROM `simple_struct6` WHERE (UPPER(occupation) = 'OPS' OR UPPER(occupation) = 'DEV') AND (LENGTH(name) <= 4 OR LENGTH(name) > 8)", }, "simple filter with like prefix": { records: []*SimpleStruct6{ @@ -847,8 +912,10 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { Name: "Jennifer", }, }, - filterMap: map[string]any{ - "UPPER(name)": []string{"~J"}, + filters: []map[string]any{ + { + "UPPER(name)": []string{"~J"}, + }, }, expectedQuery: "SELECT * FROM `simple_struct6` WHERE (UPPER(name) LIKE '%J%')", }, @@ -873,12 +940,13 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { Name: "Jennifer", }, }, - filterMap: map[string]any{ - "UPPER(occupation)": []string{"~EV", "~OP"}, + filters: []map[string]any{ + { + "UPPER(occupation)": []string{"~EV", "~OP"}, + }, }, expectedQuery: "SELECT * FROM `simple_struct6` WHERE (UPPER(occupation) LIKE '%EV%' OR UPPER(occupation) LIKE '%OP%')", }, - // TODO: this test result is flaky in the expectedQuery check (order of the AND/OR operations is random) "and or filter with like prefix": { records: []*SimpleStruct6{ { @@ -896,11 +964,15 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { Name: "Jennifer", }, }, - filterMap: map[string]any{ - "UPPER(occupation)": []string{"!=DEV", "!~TEST"}, - "LENGTH(name)": ">=8", + filters: []map[string]any{ + { + "UPPER(occupation)": []string{"!=DEV", "!~TEST"}, + }, + { + "LENGTH(name)": []int{3, 8}, + }, }, - expectedQuery: "SELECT * FROM `simple_struct6` WHERE (UPPER(occupation) != 'DEV' OR UPPER(occupation) NOT LIKE '%TEST%') AND (LENGTH(name) >= 8)", + expectedQuery: "SELECT * FROM `simple_struct6` WHERE (UPPER(occupation) != 'DEV' OR UPPER(occupation) NOT LIKE '%TEST%') AND (LENGTH(name) = 3 OR LENGTH(name) = 8)", }, } @@ -925,10 +997,10 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { // Act sqlQuery := database.ToSQL(func(tx *gorm.DB) *gorm.DB { - deepFilterQuery, _ := AddDeepFilters(tx, SimpleStruct6{}, testData.filterMap) + deepFilterQuery, _ := AddDeepFilters(tx, SimpleStruct6{}, testData.filters...) return deepFilterQuery.Find([]*SimpleStruct6{}) }) - query, err := AddDeepFilters(database, SimpleStruct6{}, testData.filterMap) + query, err := AddDeepFilters(database, SimpleStruct6{}, testData.filters...) // Assert assert.Nil(t, err) @@ -1095,9 +1167,9 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) t.Parallel() t.Cleanup(cleanupCache) tests := map[string]struct { - records []*ComplexStruct1 - expected []ComplexStruct1 - filterMap map[string]any + records []*ComplexStruct1 + expected []ComplexStruct1 + filters []map[string]any expectedQuery string }{ "simple filter with function": { @@ -1136,9 +1208,11 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) }, }, }, - filterMap: map[string]any{ - "nested": map[string]any{ - "LOWER(name)": "katherina", + filters: []map[string]any{ + { + "nested": map[string]any{ + "LOWER(name)": "katherina", + }, }, }, expectedQuery: "SELECT * FROM `complex_struct1` WHERE nested_ref IN (SELECT `id` FROM `nested_struct4` WHERE (LOWER(name) = 'katherina'))", @@ -1179,11 +1253,13 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) }, }, }, - filterMap: map[string]any{ - "nested": map[string]any{ - "UPPER(name)": "KATHERINA", + filters: []map[string]any{ + { + "nested": map[string]any{ + "UPPER(name)": "KATHERINA", + }, + "value": 11, }, - "value": 11, }, expectedQuery: "SELECT * FROM `complex_struct1` WHERE nested_ref IN (SELECT `id` FROM `nested_struct4` WHERE (UPPER(name) = 'KATHERINA')) AND `value` = 11", }, @@ -1242,13 +1318,19 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) }, }, }, - filterMap: map[string]any{ - "nested": map[string]any{ - "LOWER(name)": "vanessa", - "UPPER(occupation)": []string{"DEV", "OPS"}, + filters: []map[string]any{ + { + "nested": map[string]any{ + "LOWER(name)": "vanessa", + }, + }, + { + "nested": map[string]any{ + "UPPER(occupation)": []string{"DEV", "OPS"}, + }, }, }, - expectedQuery: "SELECT * FROM `complex_struct1` WHERE nested_ref IN (SELECT `id` FROM `nested_struct4` WHERE (LOWER(name) = 'vanessa') AND (UPPER(occupation) = 'DEV' OR UPPER(occupation) = 'OPS'))", + expectedQuery: "SELECT * FROM `complex_struct1` WHERE nested_ref IN (SELECT `id` FROM `nested_struct4` WHERE (LOWER(name) = 'vanessa')) AND nested_ref IN (SELECT `id` FROM `nested_struct4` WHERE (UPPER(occupation) = 'DEV' OR UPPER(occupation) = 'OPS'))", }, "query with qonvert and function": { records: []*ComplexStruct1{ @@ -1285,12 +1367,14 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) }, }, }, - filterMap: map[string]any{ - "nested": map[string]any{ - "UPPER(name)": []string{"~OK", "~AT"}, - "LENGTH(occupation)": ">=3", + filters: []map[string]any{ + { + "nested": map[string]any{ + "UPPER(name)": []string{"~OK", "~AT"}, + "LENGTH(occupation)": ">=3", + }, + "value": 2, }, - "value": 2, }, expectedQuery: "SELECT * FROM `complex_struct1` WHERE nested_ref IN (SELECT `id` FROM `nested_struct4` WHERE (UPPER(name) LIKE '%OK%' OR UPPER(name) LIKE '%AT%') AND (LENGTH(occupation) >= 3)) AND `value` = 2", }, @@ -1318,10 +1402,10 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) // Act sqlQuery := database.ToSQL(func(tx *gorm.DB) *gorm.DB { - deepFilterQuery, _ := AddDeepFilters(tx, ComplexStruct1{}, testData.filterMap) + deepFilterQuery, _ := AddDeepFilters(tx, ComplexStruct1{}, testData.filters...) return deepFilterQuery.Find(&ComplexStruct1{}) }) - query, err := AddDeepFilters(database, ComplexStruct1{}, testData.filterMap) + query, err := AddDeepFilters(database, ComplexStruct1{}, testData.filters...) // Assert assert.Nil(t, err) From e0cf62969b47fc5051d8f1da5772ea075be694a8 Mon Sep 17 00:00:00 2001 From: bramca Date: Fri, 23 May 2025 00:12:41 +0200 Subject: [PATCH 10/12] fixes --- deep-filtering.go | 2 +- deep-filtering_test.go | 13 +++++++------ go.mod | 8 +++----- go.sum | 8 ++++---- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/deep-filtering.go b/deep-filtering.go index 42e5e2f..ab99b8f 100644 --- a/deep-filtering.go +++ b/deep-filtering.go @@ -86,7 +86,7 @@ func AddDeepFilters(db *gorm.DB, objectType any, filters ...map[string]any) (*go simpleFilter := map[string]any{} totalFilterString := "" - functionRegex := regexp.MustCompile(`.*?\((.*?)\)`) + functionRegex := regexp.MustCompile(`.*\((.*?)\)+`) qonvertMap := map[string]string{} if _, ok := db.Plugins[gormqonvert.New(gormqonvert.CharacterConfig{}).Name()]; ok { diff --git a/deep-filtering_test.go b/deep-filtering_test.go index ddb4f28..618e74b 100644 --- a/deep-filtering_test.go +++ b/deep-filtering_test.go @@ -692,7 +692,7 @@ func TestAddDeepFilters_ReturnsErrorOnUnknownFieldInformation(t *testing.T) { } func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { - t.Parallel() + // t.Parallel() t.Cleanup(cleanupCache) type SimpleStruct6 struct { Name string @@ -724,10 +724,10 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { }, filters: []map[string]any{ { - "LOWER(occupation)": "ops", + "LOWER(UPPER(occupation))": "ops", }, }, - expectedQuery: "SELECT * FROM `simple_struct6` WHERE (LOWER(occupation) = 'ops')", + expectedQuery: "SELECT * FROM `simple_struct6` WHERE (LOWER(UPPER(occupation)) = 'ops')", }, "simple like filter": { records: []*SimpleStruct6{ @@ -997,7 +997,8 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { // Act sqlQuery := database.ToSQL(func(tx *gorm.DB) *gorm.DB { - deepFilterQuery, _ := AddDeepFilters(tx, SimpleStruct6{}, testData.filters...) + deepFilterQuery, err := AddDeepFilters(tx, SimpleStruct6{}, testData.filters...) + assert.Nil(t, err) return deepFilterQuery.Find([]*SimpleStruct6{}) }) query, err := AddDeepFilters(database, SimpleStruct6{}, testData.filters...) @@ -1211,11 +1212,11 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) filters: []map[string]any{ { "nested": map[string]any{ - "LOWER(name)": "katherina", + "LOWER(UPPER(name))": "katherina", }, }, }, - expectedQuery: "SELECT * FROM `complex_struct1` WHERE nested_ref IN (SELECT `id` FROM `nested_struct4` WHERE (LOWER(name) = 'katherina'))", + expectedQuery: "SELECT * FROM `complex_struct1` WHERE nested_ref IN (SELECT `id` FROM `nested_struct4` WHERE (LOWER(UPPER(name)) = 'katherina'))", }, "more complex query with simple filter function": { records: []*ComplexStruct1{ diff --git a/go.mod b/go.mod index 24e68d4..34549c0 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/survivorbat/gorm-deep-filtering -go 1.23.0 - -toolchain go1.23.5 +go 1.24.0 require ( github.com/google/uuid v1.3.0 @@ -11,7 +9,7 @@ require ( github.com/survivorbat/go-tsyncmap v0.0.0 github.com/survivorbat/gorm-query-convert v0.0.1 gorm.io/driver/sqlite v1.5.2 - gorm.io/gorm v1.25.12 + gorm.io/gorm v1.26.1 ) require ( @@ -20,6 +18,6 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/text v0.25.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 58851f1..df57164 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,8 @@ github.com/survivorbat/go-tsyncmap v0.0.0 h1:XTc1+uXyuw//1Hhpg4IxW6tEe3Tvd2d5vM/ github.com/survivorbat/go-tsyncmap v0.0.0/go.mod h1:zKe2CuXEo+c1d9DVT5L7AG2jPTdWi7QQN/Gk+26Vecg= github.com/survivorbat/gorm-query-convert v0.0.1 h1:e/wesF2ajiryuJhH/Vs9WrsTU5Mh6x3VRkdFtm08l0E= github.com/survivorbat/gorm-query-convert v0.0.1/go.mod h1:3YidTdfqM+KscRLFKhSUNl16tO0CqlEJcG0dFFpVl0g= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -40,5 +40,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/sqlite v1.5.2 h1:TpQ+/dqCY4uCigCFyrfnrJnrW9zjpelWVoEVNy5qJkc= gorm.io/driver/sqlite v1.5.2/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= -gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= -gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw= +gorm.io/gorm v1.26.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= From 9c437072fc152eed15a6b632d8ac340fab81f793 Mon Sep 17 00:00:00 2001 From: bramca Date: Wed, 28 May 2025 10:25:53 +0200 Subject: [PATCH 11/12] update gorm version --- deep-filtering_test.go | 6 +++--- go.mod | 6 +++--- go.sum | 8 ++++---- plugin.go | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/deep-filtering_test.go b/deep-filtering_test.go index 618e74b..ee2f110 100644 --- a/deep-filtering_test.go +++ b/deep-filtering_test.go @@ -806,7 +806,7 @@ func TestAddDeepFilters_AddsSimplelFiltersWithFunctions(t *testing.T) { "LENGTH(name)": ">4", }, }, - expectedQuery: "SELECT * FROM `simple_struct6` WHERE (LENGTH(name) > 4) AND `occupation` = \"Ops\"", + expectedQuery: "SELECT * FROM `simple_struct6` WHERE (LENGTH(name) > 4) AND `simple_struct6`.`occupation` = \"Ops\"", }, "simple or filter strings": { records: []*SimpleStruct6{ @@ -1262,7 +1262,7 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) "value": 11, }, }, - expectedQuery: "SELECT * FROM `complex_struct1` WHERE nested_ref IN (SELECT `id` FROM `nested_struct4` WHERE (UPPER(name) = 'KATHERINA')) AND `value` = 11", + expectedQuery: "SELECT * FROM `complex_struct1` WHERE nested_ref IN (SELECT `id` FROM `nested_struct4` WHERE (UPPER(name) = 'KATHERINA')) AND `complex_struct1`.`value` = 11", }, "or query with function": { records: []*ComplexStruct1{ @@ -1377,7 +1377,7 @@ func TestAddDeepFilters_AddsDeepFiltersWithOneToManyWithFunctions(t *testing.T) "value": 2, }, }, - expectedQuery: "SELECT * FROM `complex_struct1` WHERE nested_ref IN (SELECT `id` FROM `nested_struct4` WHERE (UPPER(name) LIKE '%OK%' OR UPPER(name) LIKE '%AT%') AND (LENGTH(occupation) >= 3)) AND `value` = 2", + expectedQuery: "SELECT * FROM `complex_struct1` WHERE nested_ref IN (SELECT `id` FROM `nested_struct4` WHERE (UPPER(name) LIKE '%OK%' OR UPPER(name) LIKE '%AT%') AND (LENGTH(occupation) >= 3)) AND `complex_struct1`.`value` = 2", }, } diff --git a/go.mod b/go.mod index 34549c0..7ce16b6 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,15 @@ module github.com/survivorbat/gorm-deep-filtering -go 1.24.0 +go 1.24.1 require ( github.com/google/uuid v1.3.0 github.com/ing-bank/gormtestutil v0.0.0 github.com/stretchr/testify v1.8.1 github.com/survivorbat/go-tsyncmap v0.0.0 - github.com/survivorbat/gorm-query-convert v0.0.1 + github.com/survivorbat/gorm-query-convert v0.1.0 gorm.io/driver/sqlite v1.5.2 - gorm.io/gorm v1.26.1 + gorm.io/gorm v1.30.0 ) require ( diff --git a/go.sum b/go.sum index df57164..839f7a0 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/survivorbat/go-tsyncmap v0.0.0 h1:XTc1+uXyuw//1Hhpg4IxW6tEe3Tvd2d5vM/6IPqmkeg= github.com/survivorbat/go-tsyncmap v0.0.0/go.mod h1:zKe2CuXEo+c1d9DVT5L7AG2jPTdWi7QQN/Gk+26Vecg= -github.com/survivorbat/gorm-query-convert v0.0.1 h1:e/wesF2ajiryuJhH/Vs9WrsTU5Mh6x3VRkdFtm08l0E= -github.com/survivorbat/gorm-query-convert v0.0.1/go.mod h1:3YidTdfqM+KscRLFKhSUNl16tO0CqlEJcG0dFFpVl0g= +github.com/survivorbat/gorm-query-convert v0.1.0 h1:ct05m9K79EbYj45sfLpiYRay+7ZGlg+aGZpuGzFeqtU= +github.com/survivorbat/gorm-query-convert v0.1.0/go.mod h1:JbZVdQDRMhGsdzRpkmvYHxp8goY0bKKUrY3dxnq1d9w= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -40,5 +40,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/sqlite v1.5.2 h1:TpQ+/dqCY4uCigCFyrfnrJnrW9zjpelWVoEVNy5qJkc= gorm.io/driver/sqlite v1.5.2/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= -gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw= -gorm.io/gorm v1.26.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= +gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= diff --git a/plugin.go b/plugin.go index 5209b31..df040e9 100644 --- a/plugin.go +++ b/plugin.go @@ -47,7 +47,7 @@ func createDeepFilterRecursively(exprs []clause.Expression, db *gorm.DB) { concreteType := ensureNotASlice(reflect.TypeOf(db.Statement.Model)) inputObject := ensureConcrete(reflect.New(concreteType)).Interface() - applied, err := AddDeepFilters(db.Session(&gorm.Session{NewDB: true}), inputObject, map[string]any{cond.Column.(string): value}) + applied, err := AddDeepFilters(db.Session(&gorm.Session{NewDB: true}), inputObject, map[string]any{cond.Column.(clause.Column).Name: value}) if err != nil { _ = db.AddError(err) From 520f88b0305c31da338531dfc819307cd5758916 Mon Sep 17 00:00:00 2001 From: bramca Date: Wed, 28 May 2025 11:07:25 +0200 Subject: [PATCH 12/12] fix merge conflict --- plugin.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugin.go b/plugin.go index df040e9..1dd54ae 100644 --- a/plugin.go +++ b/plugin.go @@ -47,7 +47,10 @@ func createDeepFilterRecursively(exprs []clause.Expression, db *gorm.DB) { concreteType := ensureNotASlice(reflect.TypeOf(db.Statement.Model)) inputObject := ensureConcrete(reflect.New(concreteType)).Interface() - applied, err := AddDeepFilters(db.Session(&gorm.Session{NewDB: true}), inputObject, map[string]any{cond.Column.(clause.Column).Name: value}) + column := cond.Column.(clause.Column) + columnName := column.Name + + applied, err := AddDeepFilters(db.Session(&gorm.Session{NewDB: true}), inputObject, map[string]any{columnName: value}) if err != nil { _ = db.AddError(err)