From c37d726b4cdac624ce47335d9e661fe7e5fbce87 Mon Sep 17 00:00:00 2001 From: tx7do Date: Sat, 4 Nov 2023 21:35:54 +0800 Subject: [PATCH] feat: ent query filter. --- entgo/go.mod | 1 + entgo/go.sum | 12 +- entgo/query/README.md | 17 +- entgo/query/filter.go | 168 ++++++++---- entgo/query/filter_test.go | 541 +++++++++++++++++++++++++++++++++++++ entgo/query/query.go | 3 +- entgo/query/query_test.go | 84 ++++++ tag.bat | 2 +- 8 files changed, 752 insertions(+), 76 deletions(-) create mode 100644 entgo/query/filter_test.go diff --git a/entgo/go.mod b/entgo/go.mod index d321a0e..96f3fe4 100644 --- a/entgo/go.mod +++ b/entgo/go.mod @@ -25,6 +25,7 @@ require ( github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sony/sonyflake v1.2.0 // indirect github.com/zclconf/go-cty v1.14.1 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.13.0 // indirect diff --git a/entgo/go.sum b/entgo/go.sum index 5ae9ccd..92c1eb9 100644 --- a/entgo/go.sum +++ b/entgo/go.sum @@ -7,8 +7,6 @@ entgo.io/ent v0.12.4/go.mod h1:Y3JVAjtlIk8xVZYSn3t3mf8xlZIn5SAOXZQxD6kKI+Q= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= -github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY= @@ -20,15 +18,13 @@ github.com/go-kratos/kratos/v2 v2.7.1/go.mod h1:CPn82O93OLHjtnbuyOKhAG5TkSvw+mFn github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= -github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI= github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= @@ -37,7 +33,6 @@ github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4E github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= @@ -47,13 +42,12 @@ 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.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ= +github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA= github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= -github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= diff --git a/entgo/query/README.md b/entgo/query/README.md index 612028b..8b92b05 100644 --- a/entgo/query/README.md +++ b/entgo/query/README.md @@ -2,14 +2,15 @@ ## 通用列表查询请求 -| 字段名 | 类型 | 格式 | 字段描述 | 示例 | 备注 | -|----------|-----------|-------------------------------------|---------|----------------------------------------------------------------------------------------------------------|------------------------------------------------------------------| -| page | `number` | | 当前页码 | | 默认为`1`,最小值为`1`。 | -| pageSize | `number` | | 每页的行数 | | 默认为`10`,最小值为`1`。 | -| query | `string` | `json object` 或 `json object array` | AND过滤条件 | json字符串: `{"field1":"val1","field2":"val2"}` 或者`[{"field1":"val1"},{"field1":"val2"},{"field2":"val2"}]` | `map`和`array`都支持,当需要同字段名,不同值的情况下,请使用`array`。具体规则请见:[过滤规则](#过滤规则) | -| or | `string` | `json object` 或 `json object array` | OR过滤条件 | 同 AND过滤条件 | | -| orderBy | `string` | `json string array` | 排序条件 | json字符串:`["-create_time", "type"]` | json的`string array`,字段名前加`-`是为降序,不加为升序。具体规则请见:[排序规则](#排序规则) | -| nopaging | `boolean` | | 是否不分页 | | 此字段为`true`时,`page`、`pageSize`字段的传入将无效用。 | +| 字段名 | 类型 | 格式 | 字段描述 | 示例 | 备注 | +|-----------|-----------|-------------------------------------|---------|----------------------------------------------------------------------------------------------------------|------------------------------------------------------------------| +| page | `number` | | 当前页码 | | 默认为`1`,最小值为`1`。 | +| pageSize | `number` | | 每页的行数 | | 默认为`10`,最小值为`1`。 | +| query | `string` | `json object` 或 `json object array` | AND过滤条件 | json字符串: `{"field1":"val1","field2":"val2"}` 或者`[{"field1":"val1"},{"field1":"val2"},{"field2":"val2"}]` | `map`和`array`都支持,当需要同字段名,不同值的情况下,请使用`array`。具体规则请见:[过滤规则](#过滤规则) | +| or | `string` | `json object` 或 `json object array` | OR过滤条件 | 同 AND过滤条件 | | +| orderBy | `string` | `json string array` | 排序条件 | json字符串:`["-create_time", "type"]` | json的`string array`,字段名前加`-`是为降序,不加为升序。具体规则请见:[排序规则](#排序规则) | +| nopaging | `boolean` | | 是否不分页 | | 此字段为`true`时,`page`、`pageSize`字段的传入将无效用。 | +| fieldMask | `string` | `json string array` | 字段掩码 | | 此字段是`SELECT`条件,为空的时候是为`*`。 | ## 排序规则 diff --git a/entgo/query/filter.go b/entgo/query/filter.go index e862504..a301fb5 100644 --- a/entgo/query/filter.go +++ b/entgo/query/filter.go @@ -4,12 +4,16 @@ import ( "encoding/json" "strings" + "entgo.io/ent/dialect" "entgo.io/ent/dialect/sql" + "github.com/go-kratos/kratos/v2/encoding" "github.com/tx7do/go-utils/stringcase" ) +type FilterOp int + const ( FilterNot = "not" // 不等于 FilterIn = "in" // 检查值是否在列表中 @@ -34,23 +38,51 @@ const ( FilterSearch = "search" // 全文搜索 ) +type DatePart int + const ( - FilterDatePartDate = "date" // 日期 - FilterDatePartYear = "year" // 年 - FilterDatePartISOYear = "iso_year" // ISO 8601 一年中的周数 - FilterDatePartQuarter = "quarter" // 季度 - FilterDatePartMonth = "month" // 月 - FilterDatePartWeek = "week" // ISO 8601 周编号 一年中的周数 - FilterDatePartWeekDay = "week_day" // 星期几 - FilterDatePartISOWeekDay = "iso_week_day" // 星期几 - FilterDatePartDay = "day" // 日 - FilterDatePartTime = "time" // 小时:分钟:秒 - FilterDatePartHour = "hour" // 小时 - FilterDatePartMinute = "minute" // 分钟 - FilterDatePartSecond = "second" // 秒 - FilterDatePartMicrosecond = "microsecond" // 微秒 + DatePartDate DatePart = iota // 日期 + DatePartYear // 年 + DatePartISOYear // ISO 8601 一年中的周数 + DatePartQuarter // 季度 + DatePartMonth // 月 + DatePartWeek // ISO 8601 周编号 一年中的周数 + DatePartWeekDay // 星期几 + DatePartISOWeekDay // 星期几 + DatePartDay // 日 + DatePartTime // 小时:分钟:秒 + DatePartHour // 小时 + DatePartMinute // 分钟 + DatePartSecond // 秒 + DatePartMicrosecond // 微秒 ) +var dateParts = [...]string{ + DatePartDate: "date", + DatePartYear: "year", + DatePartISOYear: "iso_year", + DatePartQuarter: "quarter", + DatePartMonth: "month", + DatePartWeek: "week", + DatePartWeekDay: "week_day", + DatePartISOWeekDay: "iso_week_day", + DatePartDay: "day", + DatePartTime: "time", + DatePartHour: "hour", + DatePartMinute: "minute", + DatePartSecond: "second", + DatePartMicrosecond: "microsecond", +} + +func hasDatePart(str string) bool { + for _, item := range dateParts { + if str == item { + return true + } + } + return false +} + // QueryCommandToWhereConditions 查询命令转换为选择条件 func QueryCommandToWhereConditions(strJson string, isOr bool) (error, func(s *sql.Selector)) { if len(strJson) == 0 { @@ -154,7 +186,7 @@ func oneFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate case FilterIsNull: cond = filterIsNull(s, field, value) case FilterNotIsNull: - cond = filterNotIsNull(s, field, value) + cond = filterIsNotNull(s, field, value) case FilterContains: cond = filterContains(s, field, value) case FilterInsensitiveContains: @@ -201,32 +233,20 @@ func filterNot(s *sql.Selector, field, value string) *sql.Predicate { // filterIn IN操作 // SQL: WHERE name IN ("tom", "jimmy") func filterIn(s *sql.Selector, field, value string) *sql.Predicate { - var strs []string - if err := json.Unmarshal([]byte(value), &strs); err == nil { - return sql.In(s.C(field), strs) + var values []any + if err := json.Unmarshal([]byte(value), &values); err == nil { + return sql.In(s.C(field), values...) } - - var float64s []float64 - if err := json.Unmarshal([]byte(value), &float64s); err == nil { - return sql.In(s.C(field), strs) - } - return nil } // filterNotIn NOT IN操作 // SQL: WHERE name NOT IN ("tom", "jimmy")` func filterNotIn(s *sql.Selector, field, value string) *sql.Predicate { - var strs []string - if err := json.Unmarshal([]byte(value), &strs); err == nil { - return sql.NotIn(s.C(field), strs) + var values []any + if err := json.Unmarshal([]byte(value), &values); err == nil { + return sql.NotIn(s.C(field), values...) } - - var float64s []float64 - if err := json.Unmarshal([]byte(value), &float64s); err == nil { - return sql.NotIn(s.C(field), strs) - } - return nil } @@ -258,27 +278,15 @@ func filterLT(s *sql.Selector, field, value string) *sql.Predicate { // SQL: WHERE "create_time" BETWEEN "2023-10-25" AND "2024-10-25" // 或者: WHERE "create_time" >= "2023-10-25" AND "create_time" <= "2024-10-25" func filterRange(s *sql.Selector, field, value string) *sql.Predicate { - var strs []string - if err := json.Unmarshal([]byte(value), &strs); err == nil { - if len(strs) != 2 { + var values []any + if err := json.Unmarshal([]byte(value), &values); err == nil { + if len(values) != 2 { return nil } return sql.And( - sql.GTE(s.C(field), strs[0]), - sql.LTE(s.C(field), strs[1]), - ) - } - - var float64s []float64 - if err := json.Unmarshal([]byte(value), &float64s); err == nil { - if len(float64s) != 2 { - return nil - } - - return sql.And( - sql.GTE(s.C(field), float64s[0]), - sql.LTE(s.C(field), float64s[1]), + sql.GTE(s.C(field), values[0]), + sql.LTE(s.C(field), values[1]), ) } @@ -291,9 +299,9 @@ func filterIsNull(s *sql.Selector, field, _ string) *sql.Predicate { return sql.IsNull(s.C(field)) } -// filterNotIsNull 不为空 IS NOT NULL操作 +// filterIsNotNull 不为空 IS NOT NULL操作 // SQL: WHERE name IS NOT NULL -func filterNotIsNull(s *sql.Selector, field, _ string) *sql.Predicate { +func filterIsNotNull(s *sql.Selector, field, _ string) *sql.Predicate { return sql.Not(sql.IsNull(s.C(field))) } @@ -345,27 +353,75 @@ func filterInsensitiveExact(s *sql.Selector, field, value string) *sql.Predicate return sql.EqualFold(s.C(field), value) } -// filterRegex LIKE 操作 精确比对 +// filterRegex 正则查找 // MySQL: WHERE title REGEXP BINARY '^(An?|The) +' // Oracle: WHERE REGEXP_LIKE(title, '^(An?|The) +', 'c'); // PostgreSQL: WHERE title ~ '^(An?|The) +'; // SQLite: WHERE title REGEXP '^(An?|The) +'; func filterRegex(s *sql.Selector, field, value string) *sql.Predicate { - return nil + p := sql.P() + p.Append(func(b *sql.Builder) { + switch s.Builder.Dialect() { + case dialect.Postgres: + b.Ident(s.C(field)).WriteString(" ~ ") + b.Arg(value) + break + case dialect.MySQL: + b.Ident(s.C(field)).WriteString(" REGEXP BINARY ") + b.Arg(value) + break + case dialect.SQLite: + b.Ident(s.C(field)).WriteString(" REGEXP ") + b.Arg(value) + break + case dialect.Gremlin: + break + } + }) + return p } -// filterInsensitiveRegex ILIKE 操作 不区分大小写,精确比对 +// filterInsensitiveRegex 正则查找 不区分大小写 // MySQL: WHERE title REGEXP '^(an?|the) +' // Oracle: WHERE REGEXP_LIKE(title, '^(an?|the) +', 'i'); // PostgreSQL: WHERE title ~* '^(an?|the) +'; // SQLite: WHERE title REGEXP '(?i)^(an?|the) +'; func filterInsensitiveRegex(s *sql.Selector, field, value string) *sql.Predicate { - return nil + p := sql.P() + p.Append(func(b *sql.Builder) { + switch s.Builder.Dialect() { + case dialect.Postgres: + b.Ident(s.C(field)).WriteString(" ~* ") + b.Arg(strings.ToLower(value)) + break + case dialect.MySQL: + b.Ident(s.C(field)).WriteString(" REGEXP ") + b.Arg(strings.ToLower(value)) + break + case dialect.SQLite: + b.Ident(s.C(field)).WriteString(" REGEXP ") + if !strings.HasPrefix(value, "(?i)") { + value = "(?i)" + value + } + b.Arg(strings.ToLower(value)) + break + case dialect.Gremlin: + break + } + }) + return p } // filterSearch 全文搜索 // SQL: func filterSearch(s *sql.Selector, _, _ string) *sql.Predicate { + p := sql.P() + p.Append(func(b *sql.Builder) { + switch s.Builder.Dialect() { + + } + }) + return nil } diff --git a/entgo/query/filter_test.go b/entgo/query/filter_test.go new file mode 100644 index 0000000..5a00172 --- /dev/null +++ b/entgo/query/filter_test.go @@ -0,0 +1,541 @@ +package entgo + +import ( + "testing" + + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + + "github.com/stretchr/testify/require" +) + +func TestFilter(t *testing.T) { + t.Run("MySQL_FilterEqual", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterEqual(s, "name", "tom") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` = ?", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "tom") + }) + t.Run("PostgreSQL_FilterEqual", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterEqual(s, "name", "tom") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" = $1", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "tom") + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterNot", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterNot(s, "name", "tom") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE NOT (`users`.`name` = ?)", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "tom") + }) + t.Run("PostgreSQL_FilterNot", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterNot(s, "name", "tom") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE NOT (\"users\".\"name\" = $1)", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "tom") + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterIn", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterIn(s, "name", "[\"tom\", \"jimmy\", 123]") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` IN (?, ?, ?)", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "tom") + require.Equal(t, args[1], "jimmy") + require.Equal(t, args[2], float64(123)) + }) + t.Run("PostgreSQL_FilterIn", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterIn(s, "name", "[\"tom\", \"jimmy\", 123]") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" IN ($1, $2, $3)", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "tom") + require.Equal(t, args[1], "jimmy") + require.Equal(t, args[2], float64(123)) + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterNotIn", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterNotIn(s, "name", "[\"tom\", \"jimmy\", 123]") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` NOT IN (?, ?, ?)", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "tom") + require.Equal(t, args[1], "jimmy") + require.Equal(t, args[2], float64(123)) + }) + t.Run("PostgreSQL_FilterNotIn", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterNotIn(s, "name", "[\"tom\", \"jimmy\", 123]") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" NOT IN ($1, $2, $3)", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "tom") + require.Equal(t, args[1], "jimmy") + require.Equal(t, args[2], float64(123)) + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterGTE", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterGTE(s, "create_time", "2023-10-25") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`create_time` >= ?", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "2023-10-25") + }) + t.Run("PostgreSQL_FilterGTE", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterGTE(s, "create_time", "2023-10-25") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"create_time\" >= $1", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "2023-10-25") + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterGT", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterGT(s, "create_time", "2023-10-25") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`create_time` > ?", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "2023-10-25") + }) + t.Run("PostgreSQL_FilterGT", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterGT(s, "create_time", "2023-10-25") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"create_time\" > $1", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "2023-10-25") + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterLTE", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterLTE(s, "create_time", "2023-10-25") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`create_time` <= ?", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "2023-10-25") + }) + t.Run("PostgreSQL_FilterLTE", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterLTE(s, "create_time", "2023-10-25") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"create_time\" <= $1", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "2023-10-25") + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterLT", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterLT(s, "create_time", "2023-10-25") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`create_time` < ?", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "2023-10-25") + }) + t.Run("PostgreSQL_FilterLT", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterLT(s, "create_time", "2023-10-25") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"create_time\" < $1", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "2023-10-25") + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterRange", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterRange(s, "create_time", "[\"2023-10-25\", \"2024-10-25\"]") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`create_time` >= ? AND `users`.`create_time` <= ?", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "2023-10-25") + require.Equal(t, args[1], "2024-10-25") + }) + t.Run("PostgreSQL_FilterRange", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterRange(s, "create_time", "[\"2023-10-25\", \"2024-10-25\"]") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"create_time\" >= $1 AND \"users\".\"create_time\" <= $2", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "2023-10-25") + require.Equal(t, args[1], "2024-10-25") + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterIsNull", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterIsNull(s, "name", "true") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` IS NULL", query) + require.Empty(t, args) + }) + t.Run("PostgreSQL_FilterIsNull", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterIsNull(s, "name", "true") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" IS NULL", query) + require.Empty(t, args) + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterIsNotNull", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterIsNotNull(s, "name", "true") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE NOT (`users`.`name` IS NULL)", query) + require.Empty(t, args) + }) + t.Run("PostgreSQL_FilterIsNotNull", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterIsNotNull(s, "name", "true") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE NOT (\"users\".\"name\" IS NULL)", query) + require.Empty(t, args) + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterContains", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterContains(s, "name", "L") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` LIKE ?", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "%L%") + }) + t.Run("PostgreSQL_FilterContains", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterContains(s, "name", "L") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" LIKE $1", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "%L%") + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterInsensitiveContains", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterInsensitiveContains(s, "name", "L") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` COLLATE utf8mb4_general_ci LIKE ?", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "%l%") + }) + t.Run("PostgreSQL_FilterInsensitiveContains", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterInsensitiveContains(s, "name", "L") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" ILIKE $1", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "%l%") + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterStartsWith", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterStartsWith(s, "name", "La") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` LIKE ?", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "La%") + }) + t.Run("PostgreSQL_FilterStartsWith", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterStartsWith(s, "name", "La") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" LIKE $1", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "La%") + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterInsensitiveStartsWith", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterInsensitiveStartsWith(s, "name", "La") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` COLLATE utf8mb4_general_ci = ?", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "la%") + }) + t.Run("PostgreSQL_FilterInsensitiveStartsWith", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterInsensitiveStartsWith(s, "name", "La") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" ILIKE $1", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "la\\%") + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterEndsWith", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterEndsWith(s, "name", "La") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` LIKE ?", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "%La") + }) + t.Run("PostgreSQL_FilterEndsWith", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterEndsWith(s, "name", "La") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" LIKE $1", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "%La") + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterInsensitiveEndsWith", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterInsensitiveEndsWith(s, "name", "La") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` COLLATE utf8mb4_general_ci = ?", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "%la") + }) + t.Run("PostgreSQL_FilterInsensitiveEndsWith", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterInsensitiveEndsWith(s, "name", "La") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" ILIKE $1", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "\\%la") + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterExact", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterExact(s, "name", "La") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` LIKE ?", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "La") + }) + t.Run("PostgreSQL_FilterExact", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterExact(s, "name", "La") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" LIKE $1", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "La") + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterInsensitiveExact", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterInsensitiveExact(s, "name", "La") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` COLLATE utf8mb4_general_ci = ?", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "la") + }) + t.Run("PostgreSQL_FilterInsensitiveExact", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterInsensitiveExact(s, "name", "La") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" ILIKE $1", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "la") + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterRegex", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterRegex(s, "name", "^(An?|The) +") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` REGEXP BINARY ?", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "^(An?|The) +") + }) + t.Run("PostgreSQL_FilterRegex", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterRegex(s, "name", "^(An?|The) +") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" ~ $1", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "^(An?|The) +") + }) + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + t.Run("MySQL_FilterInsensitiveRegex", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + p := filterInsensitiveRegex(s, "name", "^(An?|The) +") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` REGEXP ?", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "^(an?|the) +") + }) + t.Run("PostgreSQL_FilterInsensitiveRegex", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + p := filterInsensitiveRegex(s, "name", "^(An?|The) +") + s.Where(p) + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" ~* $1", query) + require.NotEmpty(t, args) + require.Equal(t, args[0], "^(an?|the) +") + }) +} diff --git a/entgo/query/query.go b/entgo/query/query.go index 9b250b2..32246af 100644 --- a/entgo/query/query.go +++ b/entgo/query/query.go @@ -5,9 +5,8 @@ import ( _ "github.com/go-kratos/kratos/v2/encoding/json" ) -// BuildQuerySelector 构建分页查询选择器 +// BuildQuerySelector 构建分页过滤查询器 func BuildQuerySelector( - dbDriverName string, andFilterJsonString, orFilterJsonString string, page, pageSize int32, noPaging bool, orderBys []string, defaultOrderField string, diff --git a/entgo/query/query_test.go b/entgo/query/query_test.go index ca623ab..2f129be 100644 --- a/entgo/query/query_test.go +++ b/entgo/query/query_test.go @@ -6,7 +6,11 @@ import ( "strings" "testing" + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/go-kratos/kratos/v2/encoding" _ "github.com/go-kratos/kratos/v2/encoding/json" @@ -92,3 +96,83 @@ func TestSplitQuery(t *testing.T) { assert.Equal(t, keys[0], "id") assert.Equal(t, keys[1], "not") } + +func TestBuildQuerySelectorDefault(t *testing.T) { + t.Run("MySQL_Pagination", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + err, whereSelectors, querySelectors := BuildQuerySelector("", "", 1, 10, false, []string{}, "created_at") + require.Nil(t, err) + require.Nil(t, whereSelectors) + require.NotNil(t, querySelectors) + + for _, fnc := range whereSelectors { + fnc(s) + } + for _, fnc := range querySelectors { + fnc(s) + } + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` ORDER BY `users`.`created_at` DESC LIMIT 10 OFFSET 0", query) + require.Empty(t, args) + }) + t.Run("PostgreSQL_Pagination", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + err, whereSelectors, querySelectors := BuildQuerySelector("", "", 1, 10, false, []string{}, "created_at") + require.Nil(t, err) + require.Nil(t, whereSelectors) + require.NotNil(t, querySelectors) + + for _, fnc := range whereSelectors { + fnc(s) + } + for _, fnc := range querySelectors { + fnc(s) + } + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" ORDER BY \"users\".\"created_at\" DESC LIMIT 10 OFFSET 0", query) + require.Empty(t, args) + }) + + t.Run("MySQL_NoPagination", func(t *testing.T) { + s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) + + err, whereSelectors, querySelectors := BuildQuerySelector("", "", 1, 10, true, []string{}, "created_at") + require.Nil(t, err) + require.Nil(t, whereSelectors) + require.NotNil(t, querySelectors) + + for _, fnc := range whereSelectors { + fnc(s) + } + for _, fnc := range querySelectors { + fnc(s) + } + + query, args := s.Query() + require.Equal(t, "SELECT * FROM `users` ORDER BY `users`.`created_at` DESC", query) + require.Empty(t, args) + }) + t.Run("PostgreSQL_NoPagination", func(t *testing.T) { + s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) + + err, whereSelectors, querySelectors := BuildQuerySelector("", "", 1, 10, true, []string{}, "created_at") + require.Nil(t, err) + require.Nil(t, whereSelectors) + require.NotNil(t, querySelectors) + + for _, fnc := range whereSelectors { + fnc(s) + } + for _, fnc := range querySelectors { + fnc(s) + } + + query, args := s.Query() + require.Equal(t, "SELECT * FROM \"users\" ORDER BY \"users\".\"created_at\" DESC", query) + require.Empty(t, args) + }) +} diff --git a/tag.bat b/tag.bat index 2073bf5..bff08e9 100644 --- a/tag.bat +++ b/tag.bat @@ -1,6 +1,6 @@ git tag v1.1.4 git tag bank_card/v1.1.0 -git tag entgo/v1.1.5 +git tag entgo/v1.1.6 git tag geoip/v1.1.0 git push origin --tags