diff --git a/.circleci/config.yml b/.circleci/config.yml index f39cbdd..ba41989 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,8 +2,8 @@ version: 2 jobs: build: docker: - - image: golang:1.15-buster - working_directory: /go/src/github.com/toguser/paginate + - image: golang:1.16-buster + working_directory: /go/src/github.com/morkid/paginate steps: - checkout - run: go test -v diff --git a/README.md b/README.md index 107dc91..bd7cfcb 100644 --- a/README.md +++ b/README.md @@ -52,11 +52,9 @@ var req *http.Request = ... // or // var req *fasthttp.Request -model := db.Where("id > ?", 1).Model(&Article{}) +stmt := db.Where("id > ?", 1).Model(&Article{}) pg := paginate.New() -page := pg.Response(model, req, &[]Article{}) -// or -page := pg.With(model).Request(req).Response(&[]Article{}) +page := pg.With(stmt).Request(req).Response(&[]Article{}) log.Println(page.Total) log.Println(page.Items) @@ -72,10 +70,6 @@ pg := paginate.New(&paginate.Config{ ``` see more about [customize default configuration](#customize-default-configuration). -> Note that `Response` was marked as a deprecated function. Please use `With` instead. -> Old: `pg.Response(model, req, &[]Article{})`, -> New: `pg.With(model).Request(req).Response(&[]Article{})` - ## Pagination Result ```js @@ -227,9 +221,9 @@ func main() { pg := paginate.New() http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - model := db.Joins("User").Model(&Article{}) - paginated := pg.Response(model, r, &[]Article{}) - j, _ := json.Marshal(paginated) + stmt := db.Joins("User").Model(&Article{}) + page := pg.With(stmt).Request(r).Response(&[]Article{}) + j, _ := json.Marshal(page) w.Header().Set("Content-type", "application/json") w.Write(j) }) @@ -253,9 +247,9 @@ func main() { pg := paginate.New() fasthttp.ListenAndServe(":3000", func(ctx *fasthttp.RequestCtx) { - model := db.Joins("User").Model(&Article{}) - paginated := pg.Response(model, &ctx.Request, &[]Article{}) - j, _ := json.Marshal(paginated) + stmt := db.Joins("User").Model(&Article{}) + page := pg.With(stmt).Request(&ctx.Request).Response(&[]Article{}) + j, _ := json.Marshal(page) ctx.SetContentType("application/json") ctx.SetBody(j) }) @@ -276,9 +270,9 @@ func main() { pg := paginate.New() app := mux.NewRouter() app.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { - model := db.Joins("User").Model(&Article{}) - paginated := pg.Response(model, req, &[]Article{}) - j, _ := json.Marshal(paginated) + stmt := db.Joins("User").Model(&Article{}) + page := pg.With(stmt).Request(req).Response(&[]Article{}) + j, _ := json.Marshal(page) w.Header().Set("Content-type", "application/json") w.Write(j) }).Methods("GET") @@ -302,8 +296,9 @@ func main() { pg := paginate.New() app := fiber.New() app.Get("/", func(c *fiber.Ctx) error { - model := db.Joins("User").Model(&Article{}) - return c.JSON(pg.Response(model, c.Request(), &[]Article{})) + stmt := db.Joins("User").Model(&Article{}) + page := pg.With(stmt).Request(c.Request()).Response(&[]Article{}) + return c.JSON(page) }) app.Listen(":3000") @@ -325,8 +320,9 @@ func main() { pg := paginate.New() app := echo.New() app.GET("/", func(c echo.Context) error { - model := db.Joins("User").Model(&Article{}) - return c.JSON(200, pg.Response(model, c.Request(), &[]Article{})) + stmt := db.Joins("User").Model(&Article{}) + page := pg.With(stmt).Request(c.Request()).Response(&[]Article{}) + return c.JSON(200, page) }) app.Logger.Fatal(app.Start(":3000")) @@ -348,8 +344,9 @@ func main() { pg := paginate.New() app := gin.Default() app.GET("/", func(c *gin.Context) { - model := db.Joins("User").Model(&Article{}) - c.JSON(200, pg.Response(model, c.Request, &[]Article{})) + stmt := db.Joins("User").Model(&Article{}) + page := pg.With(stmt).Request(c.Request).Response(&[]Article{}) + c.JSON(200, page) }) app.Run(":3000") } @@ -372,8 +369,9 @@ func main() { app := martini.Classic() app.Use(render.Renderer()) app.Get("/", func(req *http.Request, r render.Render) { - model := db.Joins("User").Model(&Article{}) - r.JSON(200, pg.Response(model, req, &[]Article{})) + stmt := db.Joins("User").Model(&Article{}) + page := pg.With(stmt).Request(req).Response(&[]Article{}) + r.JSON(200, page) }) app.Run() } @@ -392,9 +390,9 @@ func main() { // var db *gorm.DB pg := paginate.New() web.Get("/", func(c *context.Context) { - model := db.Joins("User").Model(&Article{}) - c.Output.JSON( - pg.Response(model, c.Request, &[]Article{}), false, false) + stmt := db.Joins("User").Model(&Article{}) + page := pg.With(stmt).Request(c.Request).Response(&[]Article{}) + c.Output.JSON(page, false, false) }) web.Run(":3000") } @@ -529,6 +527,7 @@ Single array member is known as **Logical Operator**. // WHERE age = 20 OR age = 25 ``` + You are allowed to send array inside a value. ```js ["age", "between", [20, 30] ] @@ -540,6 +539,19 @@ You are allowed to send array inside a value. // WHERE age NOT IN(20, 21, 22, 23, 24, 25, 26, 26) ``` +Define chain columns with same value separated by comma. +```js +// Example 1 +["price,discount", ">", 10] +// Produces: +// WHERE price > 10 OR discount > 25 + +// Example 2 +["deleted_at,expiration_date", null] +// Produces: +// WHERE deleted_at IS NULL OR expiration_date IS NULL +``` + You can filter nested condition with deep array. ```js [ @@ -592,7 +604,9 @@ Config | Type | Default | Description Operator | `string` | `OR` | Default conditional operator if no operator specified.
For example
`GET /user?filters=[["name","like","jo"],["age",">",20]]`,
produces
`SELECT * FROM user where name like '%jo' OR age > 20` FieldWrapper | `string` | `LOWER(%s)` | FieldWrapper for `LIKE` operator *(for postgres default is: `LOWER((%s)::text)`)* DefaultSize | `int64` | `10` | Default size or limit per page -SmartSearch | `bool` | `false` | Enable smart search *(Experimental feature)* +PageStart | `int64` | `0` | Set start page, default `0` if not set. `total_pages` , `max_page` and `page` variable will be affected if you set `PageStart` greater than `0` +LikeAsIlikeDisabled | `bool` | `false` | By default, paginate using Case Insensitive on `LIKE` operator. Instead of using `ILIKE`, you can use `LIKE` operator to find what you want. You can set `LikeAsIlikeDisabled` to `true` if you need this feature to be disabled. +SmartSearchEnabled | `bool` | `false` | Enable smart search *(Experimental feature)* CustomParamEnabled | `bool` | `false` | Enable custom request parameter FieldSelectorEnabled | `bool` | `false` | Enable partial response with specific fields. Comma separated per field. eg: `?fields=title,user.name` SortParams | `[]string` | `[]string{"sort"}` | if `CustomParamEnabled` is `true`,
you can set the `SortParams` with custom parameter names.
For example: `[]string{"sorting", "ordering", "other_alternative_param"}`.
The following requests will capture same result
`?sorting=-name`
or `?ordering=-name`
or `?other_alternative_param=-name`
or `?sort=-name` @@ -620,15 +634,15 @@ override := func(article *Article) { } var articles []Article -model := db.Joins("User").Model(&Article{}) +stmt := db.Joins("User").Model(&Article{}) pg := paginate.New() -result := pg.Response(model, httpRequest, &articles) +page := pg.With(stmt).Request(httpRequest).Response(&articles) for index := range articles { override(&articles[index]) } -log.Println(result.Items) +log.Println(page.Items) ``` @@ -656,9 +670,9 @@ type UserNullable { ```go // usage nameAndIDOnly := []string{"name","id"} -model := db.Model(&User{}) +stmt := db.Model(&User{}) -page := pg.With(model). +page := pg.With(stmt). Request(req). Fields(nameAndIDOnly). Response([]&UserNullable{}) @@ -706,7 +720,7 @@ func main() { CacheAdapter: gocache.NewInMemoryCache(adapterConfig), }) - page := pg.With(model). + page := pg.With(stmt). Request(req). Cache("article"). // set cache name Response(&[]Article{}) @@ -731,7 +745,7 @@ func main() { CacheAdapter: gocache.NewDiskCache(adapterConfig), }) - page := pg.With(model). + page := pg.With(stmt). Request(req). Cache("article"). // set cache name Response(&[]Article{}) @@ -763,7 +777,7 @@ func main() { CacheAdapter: cache.NewRedisCache(adapterConfig), }) - page := pg.With(model). + page := pg.With(stmt). Request(req). Cache("article"). Response(&[]Article{}) @@ -801,7 +815,7 @@ func main() { CacheAdapter: cache.NewElasticCache(adapterConfig), }) - page := pg.With(model). + page := pg.With(stmt). Request(req). Cache("article"). Response(&[]Article{}) diff --git a/go.mod b/go.mod index 7a5889d..72bc22e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/morkid/paginate -go 1.15 +go 1.16 require ( github.com/iancoleman/strcase v0.1.3 diff --git a/paginate.go b/paginate.go index 382ccab..842f797 100644 --- a/paginate.go +++ b/paginate.go @@ -37,14 +37,6 @@ type Pagination struct { Config *Config } -// Response return page of results -// -// Deprecated: Response must not be used. Use With instead -func (p *Pagination) Response(stmt *gorm.DB, req interface{}, res interface{}) Page { - fmt.Println("paginate.Response(stmt, req, res) is deprecated! please use paginate.With(stmt).Request(req).Response(res) instead") - return p.With(stmt).Request(req).Response(res) -} - // With func func (p *Pagination) With(stmt *gorm.DB) RequestContext { return reqContext{ @@ -116,14 +108,27 @@ func (r resContext) Response(res interface{}) Page { if p.Config.DefaultSize == 0 { p.Config.DefaultSize = 10 } + if p.Config.PageStart < 0 { + p.Config.PageStart = 0 + } - if p.Config.FieldWrapper == "" && p.Config.ValueWrapper == "" { - defaultWrapper := "LOWER(%s)" - wrappers := map[string]string{ + defaultWrapper := "LOWER(%s)" + wrappers := map[string]string{ + "sqlite": defaultWrapper, + "mysql": defaultWrapper, + "postgres": "LOWER((%s)::text)", + } + + if p.Config.LikeAsIlikeDisabled { + defaultWrapper := "%s" + wrappers = map[string]string{ "sqlite": defaultWrapper, "mysql": defaultWrapper, - "postgres": "LOWER((%s)::text)", + "postgres": "(%s)::text", } + } + + if p.Config.FieldWrapper == "" && p.Config.ValueWrapper == "" { p.Config.FieldWrapper = defaultWrapper if wrapper, ok := wrappers[query.Dialector.Name()]; ok { p.Config.FieldWrapper = wrapper @@ -232,24 +237,20 @@ func (r resContext) Response(res interface{}) Page { if math.Mod(f, 1.0) > 0 { f = f + 1 } + f = math.Max(f, 1) + page.TotalPages = int64(f) + page.MaxPage = page.TotalPages - 1 + p.Config.PageStart page.Page = int64(pr.Page) page.Size = int64(pr.Size) - page.MaxPage = 0 page.Visible = rs.RowsAffected - if page.TotalPages > 0 { - page.MaxPage = page.TotalPages - 1 - } - if page.TotalPages < 1 { - page.TotalPages = 1 - } if page.Total < 1 { - page.MaxPage = 0 + page.MaxPage = p.Config.PageStart page.TotalPages = 0 } page.First = causes.Offset < 1 - page.Last = page.MaxPage == page.Page + page.Last = page.Page >= page.MaxPage if hasAdapter && cKey != "" { if cache, err := p.Config.JSONMarshal(page); nil == err { @@ -331,7 +332,7 @@ func createCauses(p pageRequest) requestQuery { } query.Limit = p.Size - query.Offset = p.Page * p.Size + query.Offset = (p.Page - int(p.Config.PageStart)) * p.Size query.Wheres = wheres query.WhereString = strings.Join(wheres, " ") query.Sorts = sorts @@ -363,7 +364,10 @@ func parsingNetHTTPRequest(r *http.Request, p *pageRequest) { var postData map[string]string if err := p.Config.JSONUnmarshal(body, &postData); nil == err { generateParams(param, p.Config, func(key string) string { - value, _ := postData[key] + value, exists := postData[key] + if !exists { + value = "" + } return value }) } else { @@ -405,7 +409,10 @@ func parsingFastHTTPRequest(r *fasthttp.Request, p *pageRequest) { var postData map[string]string if err := p.Config.JSONUnmarshal(b, &postData); nil == err { generateParams(param, p.Config, func(key string) string { - value, _ := postData[key] + value, exists := postData[key] + if !exists { + value = "" + } return value }) } else { @@ -447,7 +454,7 @@ func parsingQueryString(param *parameter, p *pageRequest) { if i, e := strconv.Atoi(param.Page); nil == e { p.Page = i } else { - p.Page = 0 + p.Page = int(p.Config.PageStart) } if param.Sort != "" { @@ -521,6 +528,10 @@ func arrayToFilter(arr []interface{}, config Config) pageFilters { operatorEscape := regexp.MustCompile(`[^A-z=\<\>\-\+\^/\*%&! ]+`) arrayLen := len(arr) + defaultOperator := config.Operator + if defaultOperator == "" { + defaultOperator = "OR" + } if len(arr) > 0 { subFilters := []pageFilters{} @@ -545,9 +556,13 @@ func arrayToFilter(arr []interface{}, config Config) pageFilters { } } else if k == 1 { filters.Value = i - if nil == i { + if nil == i || reflect.TypeOf(i).Name() == "bool" { filters.Operator = "IS" } + if strings.Contains(filters.Column, ",") { + subFilters = filterToSubFilter(&filters, i, config) + continue + } } } else if arrayLen == 3 { if k == 0 { @@ -562,6 +577,10 @@ func arrayToFilter(arr []interface{}, config Config) pageFilters { filters.Single = true } } else if k == 2 { + if strings.Contains(filters.Column, ",") { + subFilters = filterToSubFilter(&filters, i, config) + continue + } switch filters.Operator { case "LIKE", "ILIKE", "NOT LIKE", "NOT ILIKE": escapeString := "" @@ -579,11 +598,10 @@ func arrayToFilter(arr []interface{}, config Config) pageFilters { } value := fmt.Sprintf("%v", i) re := regexp.MustCompile(escapePattern) - value = string(re.ReplaceAll([]byte(value), []byte(escapeString+`$1`))) - if config.SmartSearch { + value = re.ReplaceAllString(value, escapeString+`$1`) + if config.SmartSearchEnabled { re := regexp.MustCompile(`[\s]+`) - byt := re.ReplaceAll([]byte(value), []byte("%")) - value = string(byt) + value = re.ReplaceAllString(value, "%") } filters.Value = fmt.Sprintf("%s%s%s", "%", value, "%") default: @@ -595,10 +613,6 @@ func arrayToFilter(arr []interface{}, config Config) pageFilters { if len(subFilters) > 0 { separatedSubFilters := []pageFilters{} hasOperator := false - defaultOperator := config.Operator - if defaultOperator == "" { - defaultOperator = "OR" - } for k, s := range subFilters { if s.IsOperator && len(subFilters) == (k+1) { break @@ -615,12 +629,32 @@ func arrayToFilter(arr []interface{}, config Config) pageFilters { } filters.Value = separatedSubFilters filters.Single = false + filters.IsOperator = false } } return filters } +func filterToSubFilter(filters *pageFilters, value interface{}, config Config) []pageFilters { + subFilters := []pageFilters{} + re := regexp.MustCompile(`[^A-z0-9\._,]+`) + colString := re.ReplaceAllString(filters.Column, "") + columns := strings.Split(colString, ",") + columnRepeat := []interface{}{} + for _, col := range columns { + columnRepeat = append(columnRepeat, []interface{}{col, filters.Operator, value}) + } + + filters.Column = "" + filters.Single = false + filters.Operator = "" + filters.IsOperator = false + subFilters = append(subFilters, arrayToFilter(columnRepeat, config)) + + return subFilters +} + //gocyclo:ignore func generateWhereCauses(f pageFilters, config Config) ([]string, []interface{}) { wheres := []string{} @@ -703,7 +737,7 @@ func generateWhereCauses(f pageFilters, config Config) ([]string, []interface{}) if isStrValue { if config.ValueWrapper != "" { value = fmt.Sprintf(config.ValueWrapper, value) - } else { + } else if !config.LikeAsIlikeDisabled { value = strings.ToLower(value) } params = append(params, value) @@ -781,7 +815,9 @@ type Config struct { FieldWrapper string ValueWrapper string DefaultSize int64 - SmartSearch bool + PageStart int64 + LikeAsIlikeDisabled bool + SmartSearchEnabled bool Statement *gorm.Statement `json:"-"` CustomParamEnabled bool SortParams []string diff --git a/paginate_test.go b/paginate_test.go index e623601..e49e294 100644 --- a/paginate_test.go +++ b/paginate_test.go @@ -8,7 +8,10 @@ import ( "io" "net/http" "net/url" + "reflect" + "strings" "testing" + "time" "github.com/morkid/gocache" "github.com/valyala/fasthttp" @@ -286,12 +289,25 @@ func TestPaginate(t *testing.T) { db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{ Logger: logger.Discard, }) + db.Exec("PRAGMA case_sensitive_like=ON;") db.AutoMigrate(&User{}, &Article{}) users := []User{{Name: "John doe", AveragePoint: "Seventy %"}, {Name: "Jane doe", AveragePoint: "one hundred %"}} articles := []Article{} - articles = append(articles, Article{Title: "Written by john", Content: "Example by john", User: users[0]}) - articles = append(articles, Article{Title: "Written by jane", Content: "Example by jane", User: users[1]}) + + // add massive data + for i := 0; i < 50; i++ { + articles = append(articles, Article{ + Title: fmt.Sprintf("Written by john %d", i), + Content: fmt.Sprintf("Example by john %d", i), + UserID: 1, + }) + articles = append(articles, Article{ + Title: fmt.Sprintf("Written by jane %d", i), + Content: fmt.Sprintf("Example by jane %d", i), + UserID: 2, + }) + } if nil != err { t.Error(err.Error()) @@ -314,13 +330,16 @@ func TestPaginate(t *testing.T) { return } + // wait for transaction to finish + time.Sleep(1 * time.Second) + size := 1 page := 0 sort := "user.name,-id" avg := "y %" - data := "page=%d&size=%d&sort=%s&filters=%s" + data := "page=%v&size=%d&sort=%s&filters=%s" - queryFilter := fmt.Sprintf(`[["user.average_point","like","%s"],["AND"],["user.name","IS NOT",null],["id","like","1"]]`, avg) + queryFilter := fmt.Sprintf(`[["user.average_point","like","%s"],["AND"],["user.name","IS NOT",null]]`, avg) query := fmt.Sprintf(data, page, size, sort, url.QueryEscape(queryFilter)) request := &http.Request{ @@ -331,13 +350,17 @@ func TestPaginate(t *testing.T) { } response := []Article{} - model := db.Joins("User").Model(&Article{}) - result := New().With(model).Request(request).Response(&response) + stmt := db.Joins("User").Model(&Article{}) + result := New(&Config{LikeAsIlikeDisabled: true}).With(stmt).Request(request).Response(&response) _, err = json.MarshalIndent(result, "", " ") - if nil != err { - t.Error(err) - } + expectNil(t, err) + expect(t, result.Page, int64(0), "Invalid page") + expect(t, result.Total, int64(50), "Invalid total result") + expect(t, result.TotalPages, int64(50), "Invalid total pages") + expect(t, result.MaxPage, int64(49), "Invalid max page") + expectTrue(t, result.First, "Invalid first page") + expectFalse(t, result.Last, "Invalid last page") queryFilter = fmt.Sprintf(`[["users.average_point","like","%s"],["AND"],["user.name","IS NOT",null],["id","like","1"]]`, avg) query = fmt.Sprintf(data, page, size, sort, url.QueryEscape(queryFilter)) @@ -350,11 +373,51 @@ func TestPaginate(t *testing.T) { } response = []Article{} - model = db.Joins("User").Model(&Article{}) - result = New(&Config{ErrorEnabled: true}).With(model).Request(request).Response(&response) - if !result.Error { - t.Error("Failed to get error message") + stmt = db.Joins("User").Model(&Article{}) + result = New(&Config{ErrorEnabled: true}).With(stmt).Request(request).Response(&response) + expectTrue(t, result.Error, "Failed to get error message") + + page = 1 + size = 100 + pageStart := int64(1) + query = fmt.Sprintf(data, page, size, sort, "") + + request = &http.Request{ + Method: "GET", + URL: &url.URL{ + RawQuery: query, + }, + } + response = []Article{} + + stmt = db.Joins("User").Model(&Article{}) + result = New(&Config{PageStart: pageStart}).With(stmt).Request(request).Response(&response) + expect(t, result.Page, int64(1), "Invalid page start") + expect(t, result.MaxPage, int64(1), "Invalid max page") + expect(t, len(response), 100, "Invalid total items") + expect(t, result.Total, int64(100), "Invalid total result") + expect(t, result.TotalPages, int64(1), "Invalid total pages") + expectTrue(t, result.First, "Invalid value first") + expectTrue(t, result.Last, "Invalid value last") + + queryFilter = `[["user.average_point","like","y %"],["AND"],["user.name,title","LIKE","john"]]` + query = fmt.Sprintf(data, page, size, sort, url.QueryEscape(queryFilter)) + + request = &http.Request{ + Method: "GET", + URL: &url.URL{ + RawQuery: query, + }, } + response = []Article{} + + stmt = db.Joins("User").Model(&Article{}) + result = New(&Config{Operator: "AND", PageStart: pageStart, ErrorEnabled: true}). + With(stmt).Request(request).Response(&response) + expectFalse(t, result.Error, "An error occurred") + expect(t, result.Page, int64(1), "Invalid page start") + expect(t, result.MaxPage, int64(1), "Invalid max page") + expect(t, result.Total, int64(50), "Invalid max page") } type noOpAdapter struct { @@ -461,8 +524,8 @@ func TestCache(t *testing.T) { } pg := New(config) // set cache - model1 := db.Joins("User").Model(&Article{}).Preload(`Category`) - page1 := pg.With(model1). + stmt1 := db.Joins("User").Model(&Article{}).Preload(`Category`) + page1 := pg.With(stmt1). Request(request). Fields([]string{"id"}). Cache("cache_prefix"). @@ -470,8 +533,8 @@ func TestCache(t *testing.T) { // get cache var cached []Article - model2 := db.Joins("User").Model(&Article{}) - page2 := pg.With(model2).Request(request).Cache("cache_prefix").Response(&cached) + stmt2 := db.Joins("User").Model(&Article{}) + page2 := pg.With(stmt2).Request(request).Cache("cache_prefix").Response(&cached) if len(cached) < 1 { t.Error("Cache pointer not working perfectly") @@ -486,3 +549,86 @@ func TestCache(t *testing.T) { pg.ClearAllCache() pg.ClearAllCache() } + +func expect(t *testing.T, expected interface{}, actual interface{}, message ...string) { + if expected != actual { + t.Errorf("%s: Expected %s(%v), got %s(%v)", + strings.Join(message, " "), + reflect.TypeOf(expected), expected, + reflect.TypeOf(actual), actual) + t.Fail() + } +} + +func expectFalse(t *testing.T, actual bool, message ...string) { + expect(t, false, actual, message...) +} + +func expectTrue(t *testing.T, actual bool, message ...string) { + expect(t, true, actual, message...) +} + +func expectNil(t *testing.T, actual interface{}, message ...string) { + expect(t, nil, actual, message...) +} + +func expectNotNil(t *testing.T, actual interface{}, message ...string) { + expect(t, false, actual == nil, message...) +} + +func TestArrayFilter(t *testing.T) { + jsonString := `[ + ["name,email,address", "like", "abc"] + ]` + var jsonData []interface{} + json.Unmarshal([]byte(jsonString), &jsonData) + filters := arrayToFilter(jsonData, Config{}) + + expectNotNil(t, filters) + expectNotNil(t, filters.Value) + + subFilters, ok := filters.Value.([]pageFilters) + expectTrue(t, ok) + expect(t, 1, len(subFilters)) + + subFilterValues, ok := subFilters[0].Value.([]pageFilters) + expectTrue(t, ok) + expect(t, 1, len(subFilterValues)) + + contents, ok := subFilterValues[0].Value.([]pageFilters) + expectTrue(t, ok) + expect(t, 5, len(contents)) + + expect(t, "name", contents[0].Column) + expect(t, "LIKE", contents[0].Operator) + expect(t, "%abc%", contents[0].Value) + + expect(t, "OR", contents[1].Operator) + + expect(t, "email", contents[2].Column) + expect(t, "LIKE", contents[2].Operator) + expect(t, "%abc%", contents[2].Value) + + expect(t, "OR", contents[3].Operator) + + expect(t, "address", contents[4].Column) + expect(t, "LIKE", contents[4].Operator) + expect(t, "%abc%", contents[4].Value) +} + +func TestGenerateWhereCauses(t *testing.T) { + jsonString := `[ + ["name,email,address", "like", "abc"], + ["id", ">", 1] + ]` + var jsonData []interface{} + json.Unmarshal([]byte(jsonString), &jsonData) + filters := arrayToFilter(jsonData, Config{}) + wheres, params := generateWhereCauses(filters, Config{}) + + where := strings.Join(wheres, " ") + where = strings.ReplaceAll(where, "( ", "(") + where = strings.ReplaceAll(where, " )", ")") + expect(t, "((((name LIKE ? OR email LIKE ? OR address LIKE ?))) OR (id > ?))", where) + expect(t, 4, len(params)) +}