Compare commits

...

10 Commits

Author SHA1 Message Date
tx7do
dcbdf4b7a6 feat: ent filter support 3 params and json filed. 2023-11-29 20:36:41 +08:00
tx7do
c04968803a fix: #1 document error. 2023-11-24 13:10:52 +08:00
tx7do
093172ebca feat: ent update fields. 2023-11-08 18:17:47 +08:00
tx7do
d65e7bb928 feat: ent update fields. 2023-11-08 18:15:48 +08:00
tx7do
78452b1abf feat: field mask util. 2023-11-08 15:16:20 +08:00
tx7do
95ce578ddf feat: field mask util. 2023-11-06 20:15:07 +08:00
tx7do
413e14ac78 feat: ent select fields. 2023-11-06 18:40:47 +08:00
tx7do
50a2e139eb feat: ent select fields. 2023-11-06 17:57:40 +08:00
tx7do
18755155ba feat: ent select fields. 2023-11-06 17:02:55 +08:00
tx7do
da82d442cc feat: add. 2023-11-06 14:11:20 +08:00
33 changed files with 1575 additions and 372 deletions

26
.gitignore vendored
View File

@@ -1,6 +1,4 @@
# If you prefer the allow list template instead of the deny list, see community template: # Reference https://github.com/github/gitignore/blob/master/Go.gitignore
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins # Binaries for programs and plugins
*.exe *.exe
*.exe~ *.exe~
@@ -15,7 +13,23 @@
*.out *.out
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ vendor/
# Go workspace file # Compiled Object files, Static and Dynamic libs (Shared Objects)
go.work *.o
*.a
# OS General
Thumbs.db
.DS_Store
# project
*.cert
*.key
*.log
bin/
# Develop tools
.vscode/
.idea/
*.swp

View File

@@ -1,4 +1,4 @@
package dateutils package dateutil
import "time" import "time"

View File

@@ -1,21 +1,21 @@
package dateutils_test package dateutil_test
import ( import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tx7do/go-utils/dateutils" "github.com/tx7do/go-utils/dateutil"
) )
func TestFloor(t *testing.T) { func TestFloor(t *testing.T) {
now := time.Now() now := time.Now()
assert.Equal(t, "00:00:00", dateutils.Floor(now).Format("15:04:05")) assert.Equal(t, "00:00:00", dateutil.Floor(now).Format("15:04:05"))
} }
func TestCeil(t *testing.T) { func TestCeil(t *testing.T) {
now := time.Now() now := time.Now()
assert.Equal(t, "23:59:59", dateutils.Ceil(now).Format("15:04:05")) assert.Equal(t, "23:59:59", dateutil.Ceil(now).Format("15:04:05"))
} }
func TestBeforeOrEqual(t *testing.T) { func TestBeforeOrEqual(t *testing.T) {
@@ -25,9 +25,9 @@ func TestBeforeOrEqual(t *testing.T) {
dEqual, _ := time.Parse("2006-01-02", "2023-01-01") dEqual, _ := time.Parse("2006-01-02", "2023-01-01")
dAfter, _ := time.Parse("2006-01-02", "2023-01-31") dAfter, _ := time.Parse("2006-01-02", "2023-01-31")
assert.Equal(t, true, dateutils.BeforeOrEqual(milestone, dBefore)) assert.Equal(t, true, dateutil.BeforeOrEqual(milestone, dBefore))
assert.Equal(t, true, dateutils.BeforeOrEqual(milestone, dEqual)) assert.Equal(t, true, dateutil.BeforeOrEqual(milestone, dEqual))
assert.Equal(t, false, dateutils.BeforeOrEqual(milestone, dAfter)) assert.Equal(t, false, dateutil.BeforeOrEqual(milestone, dAfter))
} }
func TestAfterOrEqual(t *testing.T) { func TestAfterOrEqual(t *testing.T) {
@@ -37,9 +37,9 @@ func TestAfterOrEqual(t *testing.T) {
dEqual, _ := time.Parse("2006-01-02", "2023-01-01") dEqual, _ := time.Parse("2006-01-02", "2023-01-01")
dAfter, _ := time.Parse("2006-01-02", "2023-01-31") dAfter, _ := time.Parse("2006-01-02", "2023-01-31")
assert.Equal(t, false, dateutils.AfterOrEqual(milestone, dBefore)) assert.Equal(t, false, dateutil.AfterOrEqual(milestone, dBefore))
assert.Equal(t, true, dateutils.AfterOrEqual(milestone, dEqual)) assert.Equal(t, true, dateutil.AfterOrEqual(milestone, dEqual))
assert.Equal(t, true, dateutils.AfterOrEqual(milestone, dAfter)) assert.Equal(t, true, dateutil.AfterOrEqual(milestone, dAfter))
} }
func TestOverlap(t *testing.T) { func TestOverlap(t *testing.T) {
@@ -52,8 +52,8 @@ func TestOverlap(t *testing.T) {
s3, _ := time.Parse("2006-01-02", "2023-01-02") s3, _ := time.Parse("2006-01-02", "2023-01-02")
e3, _ := time.Parse("2006-01-02", "2023-01-04") e3, _ := time.Parse("2006-01-02", "2023-01-04")
assert.Equal(t, true, dateutils.Overlap(s1, e1, s2, e2)) assert.Equal(t, true, dateutil.Overlap(s1, e1, s2, e2))
assert.Equal(t, false, dateutils.Overlap(s1, e1, s3, e3)) assert.Equal(t, false, dateutil.Overlap(s1, e1, s3, e3))
s4, _ := time.Parse("2006-01-02", "2023-07-13") s4, _ := time.Parse("2006-01-02", "2023-07-13")
e4, _ := time.Parse("2006-01-02", "2023-07-14") e4, _ := time.Parse("2006-01-02", "2023-07-14")
@@ -61,6 +61,6 @@ func TestOverlap(t *testing.T) {
s5, _ := time.Parse("2006-01-02", "2023-07-10") s5, _ := time.Parse("2006-01-02", "2023-07-10")
e5, _ := time.Parse("2006-01-02", "2023-07-17") e5, _ := time.Parse("2006-01-02", "2023-07-17")
assert.Equal(t, true, dateutils.Overlap(s4, e4, s5, e5)) assert.Equal(t, true, dateutil.Overlap(s4, e4, s5, e5))
assert.Equal(t, true, dateutils.Overlap(s5, e5, s4, e4)) assert.Equal(t, true, dateutil.Overlap(s5, e5, s4, e4))
} }

View File

@@ -38,29 +38,29 @@
{字段名}__{查找类型} : {值} {字段名}__{查找类型} : {值}
``` ```
| 查找类型 | 示例 | SQL | 备注 | | 查找类型 | 示例 | SQL | 备注 |
|-------------|---------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| |-------------|---------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|
| not | `{"name__not" : "tom"}` | `WHERE NOT ("name" = "tom")` | | | not | `{"name__not" : "tom"}` | `WHERE NOT ("name" = "tom")` | |
| in | `{"name__in" : ["tom", "jimmy"]}` | `WHERE name IN ("tom", "jimmy")` | | | in | `{"name__in" : "[\"tom\", \"jimmy\"]"}` | `WHERE name IN ("tom", "jimmy")` | |
| not_in | `{"name__not_in" : ["tom", "jimmy"]}` | `WHERE name NOT IN ("tom", "jimmy")` | | | not_in | `{"name__not_in" : "[\"tom\", \"jimmy\"]"}` | `WHERE name NOT IN ("tom", "jimmy")` | |
| gte | `{"create_time__gte" : "2023-10-25"}` | `WHERE "create_time" >= "2023-10-25"` | | | gte | `{"create_time__gte" : "2023-10-25"}` | `WHERE "create_time" >= "2023-10-25"` | |
| gt | `{"create_time__gt" : "2023-10-25"}` | `WHERE "create_time" > "2023-10-25"` | | | gt | `{"create_time__gt" : "2023-10-25"}` | `WHERE "create_time" > "2023-10-25"` | |
| lte | `{"create_time__lte" : "2023-10-25"}` | `WHERE "create_time" <= "2023-10-25"` | | | lte | `{"create_time__lte" : "2023-10-25"}` | `WHERE "create_time" <= "2023-10-25"` | |
| lt | `{"create_time__lt" : "2023-10-25"}` | `WHERE "create_time" < "2023-10-25"` | | | lt | `{"create_time__lt" : "2023-10-25"}` | `WHERE "create_time" < "2023-10-25"` | |
| range | `{"create_time__range" : ["2023-10-25", "2024-10-25"]}` | `WHERE "create_time" BETWEEN "2023-10-25" AND "2024-10-25"` <br><br> `WHERE "create_time" >= "2023-10-25" AND "create_time" <= "2024-10-25"` | 需要注意的是: <br>1. 有些数据库的BETWEEN实现的开闭区间可能不一样。<br>2. 日期`2005-01-01`会被隐式转换为:`2005-01-01 00:00:00`,两个日期一致就会导致查询不到数据。 | | range | `{"create_time__range" : "[\"2023-10-25\", \"2024-10-25\"]"}` | `WHERE "create_time" BETWEEN "2023-10-25" AND "2024-10-25"` <br><br> `WHERE "create_time" >= "2023-10-25" AND "create_time" <= "2024-10-25"` | 需要注意的是: <br>1. 有些数据库的BETWEEN实现的开闭区间可能不一样。<br>2. 日期`2005-01-01`会被隐式转换为:`2005-01-01 00:00:00`,两个日期一致就会导致查询不到数据。 |
| isnull | `{"name__isnull" : "True"}` | `WHERE name IS NULL` | | | isnull | `{"name__isnull" : "True"}` | `WHERE name IS NULL` | |
| not_isnull | `{"name__not_isnull" : "False"}` | `WHERE name IS NOT NULL` | | | not_isnull | `{"name__not_isnull" : "False"}` | `WHERE name IS NOT NULL` | |
| contains | `{"name__contains" : "L"}` | `WHERE name LIKE '%L%';` | | | contains | `{"name__contains" : "L"}` | `WHERE name LIKE '%L%';` | |
| icontains | `{"name__icontains" : "L"}` | `WHERE name ILIKE '%L%';` | | | icontains | `{"name__icontains" : "L"}` | `WHERE name ILIKE '%L%';` | |
| startswith | `{"name__startswith" : "La"}` | `WHERE name LIKE 'La%';` | | | startswith | `{"name__startswith" : "La"}` | `WHERE name LIKE 'La%';` | |
| istartswith | `{"name__istartswith" : "La"}` | `WHERE name ILIKE 'La%';` | | | istartswith | `{"name__istartswith" : "La"}` | `WHERE name ILIKE 'La%';` | |
| endswith | `{"name__endswith" : "a"}` | `WHERE name LIKE '%a';` | | | endswith | `{"name__endswith" : "a"}` | `WHERE name LIKE '%a';` | |
| iendswith | `{"name__iendswith" : "a"}` | `WHERE name ILIKE '%a';` | | | iendswith | `{"name__iendswith" : "a"}` | `WHERE name ILIKE '%a';` | |
| exact | `{"name__exact" : "a"}` | `WHERE name LIKE 'a';` | | | exact | `{"name__exact" : "a"}` | `WHERE name LIKE 'a';` | |
| iexact | `{"name__iexact" : "a"}` | `WHERE name ILIKE 'a';` | | | iexact | `{"name__iexact" : "a"}` | `WHERE name ILIKE 'a';` | |
| regex | `{"title__regex" : "^(An?\|The) +"}` | MySQL: `WHERE title REGEXP BINARY '^(An?\|The) +'` <br> Oracle: `WHERE REGEXP_LIKE(title, '^(An?\|The) +', 'c');` <br> PostgreSQL: `WHERE title ~ '^(An?\|The) +';` <br> SQLite: `WHERE title REGEXP '^(An?\|The) +';` | | | regex | `{"title__regex" : "^(An?\|The) +"}` | MySQL: `WHERE title REGEXP BINARY '^(An?\|The) +'` <br> Oracle: `WHERE REGEXP_LIKE(title, '^(An?\|The) +', 'c');` <br> PostgreSQL: `WHERE title ~ '^(An?\|The) +';` <br> SQLite: `WHERE title REGEXP '^(An?\|The) +';` | |
| iregex | `{"title__iregex" : "^(an?\|the) +"}` | MySQL: `WHERE title REGEXP '^(an?\|the) +'` <br> Oracle: `WHERE REGEXP_LIKE(title, '^(an?\|the) +', 'i');` <br> PostgreSQL: `WHERE title ~* '^(an?\|the) +';` <br> SQLite: `WHERE title REGEXP '(?i)^(an?\|the) +';` | | | iregex | `{"title__iregex" : "^(an?\|the) +"}` | MySQL: `WHERE title REGEXP '^(an?\|the) +'` <br> Oracle: `WHERE REGEXP_LIKE(title, '^(an?\|the) +', 'i');` <br> PostgreSQL: `WHERE title ~* '^(an?\|the) +';` <br> SQLite: `WHERE title REGEXP '(?i)^(an?\|the) +';` | |
| search | | | | | search | | | |
以及将日期提取出来的查找类型: 以及将日期提取出来的查找类型:

View File

@@ -2,6 +2,7 @@ package entgo
import ( import (
"encoding/json" "encoding/json"
"fmt"
"strings" "strings"
"entgo.io/ent/dialect" "entgo.io/ent/dialect"
@@ -15,29 +16,63 @@ import (
type FilterOp int type FilterOp int
const ( const (
FilterNot = "not" // 不等于 FilterNot DatePart = iota // 不等于
FilterIn = "in" // 检查值是否在列表中 FilterIn // 检查值是否在列表中
FilterNotIn = "not_in" // 不在列表中 FilterNotIn // 不在列表中
FilterGTE = "gte" // 大于或等于传递的值 FilterGTE // 大于或等于传递的值
FilterGT = "gt" // 大于传递值 FilterGT // 大于传递值
FilterLTE = "lte" // 小于或等于传递值 FilterLTE // 小于或等于传递值
FilterLT = "lt" // 小于传递值 FilterLT // 小于传递值
FilterRange = "range" // 是否介于和给定的两个值之间 FilterRange // 是否介于和给定的两个值之间
FilterIsNull = "isnull" // 是否为空 FilterIsNull // 是否为空
FilterNotIsNull = "not_isnull" // 是否不为空 FilterNotIsNull // 是否不为空
FilterContains = "contains" // 是否包含指定的子字符串 FilterContains // 是否包含指定的子字符串
FilterInsensitiveContains = "icontains" // 不区分大小写,是否包含指定的子字符串 FilterInsensitiveContains // 不区分大小写,是否包含指定的子字符串
FilterStartsWith = "startswith" // 以值开头 FilterStartsWith // 以值开头
FilterInsensitiveStartsWith = "istartswith" // 不区分大小写,以值开头 FilterInsensitiveStartsWith // 不区分大小写,以值开头
FilterEndsWith = "endswith" // 以值结尾 FilterEndsWith // 以值结尾
FilterInsensitiveEndsWith = "iendswith" // 不区分大小写,以值结尾 FilterInsensitiveEndsWith // 不区分大小写,以值结尾
FilterExact = "exact" // 精确匹配 FilterExact // 精确匹配
FilterInsensitiveExact = "iexact" // 不区分大小写,精确匹配 FilterInsensitiveExact // 不区分大小写,精确匹配
FilterRegex = "regex" // 正则表达式 FilterRegex // 正则表达式
FilterInsensitiveRegex = "iregex" // 不区分大小写,正则表达式 FilterInsensitiveRegex // 不区分大小写,正则表达式
FilterSearch = "search" // 全文搜索 FilterSearch // 全文搜索
) )
var ops = [...]string{
FilterNot: "not",
FilterIn: "in",
FilterNotIn: "not_in",
FilterGTE: "gte",
FilterGT: "gt",
FilterLTE: "lte",
FilterLT: "lt",
FilterRange: "range",
FilterIsNull: "isnull",
FilterNotIsNull: "not_isnull",
FilterContains: "contains",
FilterInsensitiveContains: "icontains",
FilterStartsWith: "startswith",
FilterInsensitiveStartsWith: "istartswith",
FilterEndsWith: "endswith",
FilterInsensitiveEndsWith: "iendswith",
FilterExact: "exact",
FilterInsensitiveExact: "iexact",
FilterRegex: "regex",
FilterInsensitiveRegex: "iregex",
FilterSearch: "search",
}
func hasOperations(str string) bool {
str = strings.ToLower(str)
for _, item := range ops {
if str == item {
return true
}
}
return false
}
type DatePart int type DatePart int
const ( const (
@@ -75,6 +110,7 @@ var dateParts = [...]string{
} }
func hasDatePart(str string) bool { func hasDatePart(str string) bool {
str = strings.ToLower(str)
for _, item := range dateParts { for _, item := range dateParts {
if str == item { if str == item {
return true return true
@@ -155,129 +191,192 @@ func BuildFilterSelector(andFilterJsonString, orFilterJsonString string) (error,
} }
func oneFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate { func oneFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate {
var cond *sql.Predicate if len(keys) == 0 {
return nil
}
if len(value) == 0 {
return nil
}
if len(keys) == 1 { field := keys[0]
field := keys[0] if len(field) == 0 {
cond = filterEqual(s, field, value) return nil
} else if len(keys) == 2 { }
if len(keys[0]) == 0 {
p := sql.P()
switch len(keys) {
case 1:
return filterEqual(s, p, field, value)
case 2:
op := keys[1]
if len(op) == 0 {
return nil return nil
} }
field := keys[0]
op := strings.ToLower(keys[1]) var cond *sql.Predicate
switch op { if hasOperations(op) {
case FilterNot: return processOp(s, p, op, field, value)
cond = filterNot(s, field, value) } else if hasDatePart(op) {
case FilterIn: cond = filterDatePart(s, p, op, field).EQ("", value)
cond = filterIn(s, field, value) } else {
case FilterNotIn: cond = filterJsonb(s, p, op, field).EQ("", value)
cond = filterNotIn(s, field, value)
case FilterGTE:
cond = filterGTE(s, field, value)
case FilterGT:
cond = filterGT(s, field, value)
case FilterLTE:
cond = filterLTE(s, field, value)
case FilterLT:
cond = filterLT(s, field, value)
case FilterRange:
cond = filterRange(s, field, value)
case FilterIsNull:
cond = filterIsNull(s, field, value)
case FilterNotIsNull:
cond = filterIsNotNull(s, field, value)
case FilterContains:
cond = filterContains(s, field, value)
case FilterInsensitiveContains:
cond = filterInsensitiveContains(s, field, value)
case FilterStartsWith:
cond = filterStartsWith(s, field, value)
case FilterInsensitiveStartsWith:
cond = filterInsensitiveStartsWith(s, field, value)
case FilterEndsWith:
cond = filterEndsWith(s, field, value)
case FilterInsensitiveEndsWith:
cond = filterInsensitiveEndsWith(s, field, value)
case FilterExact:
cond = filterExact(s, field, value)
case FilterInsensitiveExact:
cond = filterInsensitiveExact(s, field, value)
case FilterRegex:
cond = filterRegex(s, field, value)
case FilterInsensitiveRegex:
cond = filterInsensitiveRegex(s, field, value)
case FilterSearch:
cond = filterSearch(s, field, value)
default:
cond = filterDatePart(s, op, field, value)
} }
return cond
case 3:
op1 := keys[1]
if len(op1) == 0 {
return nil
}
op2 := keys[2]
if len(op2) == 0 {
return nil
}
// 第二个参数要么是提取日期要么是json字段。
//var cond *sql.Predicate
if hasDatePart(op1) {
str := filterDatePartField(s, op1, field)
if hasOperations(op2) {
return processOp(s, p, op2, str, value)
}
return nil
} else {
str := filterJsonbField(s, op1, field)
if hasOperations(op2) {
return processOp(s, p, op2, str, value)
} else if hasDatePart(op2) {
return filterDatePart(s, p, op2, str)
}
return nil
}
default:
return nil
} }
}
func processOp(s *sql.Selector, p *sql.Predicate, op, field, value string) *sql.Predicate {
var cond *sql.Predicate
switch op {
case ops[FilterNot]:
cond = filterNot(s, p, field, value)
case ops[FilterIn]:
cond = filterIn(s, p, field, value)
case ops[FilterNotIn]:
cond = filterNotIn(s, p, field, value)
case ops[FilterGTE]:
cond = filterGTE(s, p, field, value)
case ops[FilterGT]:
cond = filterGT(s, p, field, value)
case ops[FilterLTE]:
cond = filterLTE(s, p, field, value)
case ops[FilterLT]:
cond = filterLT(s, p, field, value)
case ops[FilterRange]:
cond = filterRange(s, p, field, value)
case ops[FilterIsNull]:
cond = filterIsNull(s, p, field, value)
case ops[FilterNotIsNull]:
cond = filterIsNotNull(s, p, field, value)
case ops[FilterContains]:
cond = filterContains(s, p, field, value)
case ops[FilterInsensitiveContains]:
cond = filterInsensitiveContains(s, p, field, value)
case ops[FilterStartsWith]:
cond = filterStartsWith(s, p, field, value)
case ops[FilterInsensitiveStartsWith]:
cond = filterInsensitiveStartsWith(s, p, field, value)
case ops[FilterEndsWith]:
cond = filterEndsWith(s, p, field, value)
case ops[FilterInsensitiveEndsWith]:
cond = filterInsensitiveEndsWith(s, p, field, value)
case ops[FilterExact]:
cond = filterExact(s, p, field, value)
case ops[FilterInsensitiveExact]:
cond = filterInsensitiveExact(s, p, field, value)
case ops[FilterRegex]:
cond = filterRegex(s, p, field, value)
case ops[FilterInsensitiveRegex]:
cond = filterInsensitiveRegex(s, p, field, value)
case ops[FilterSearch]:
cond = filterSearch(s, p, field, value)
default:
return nil
}
return cond return cond
} }
// filterEqual = 相等操作 // filterEqual = 相等操作
// SQL: WHERE "name" = "tom" // SQL: WHERE "name" = "tom"
func filterEqual(s *sql.Selector, field, value string) *sql.Predicate { func filterEqual(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return sql.EQ(s.C(field), value) return p.EQ(s.C(field), value)
} }
// filterNot NOT 不相等操作 // filterNot NOT 不相等操作
// SQL: WHERE NOT ("name" = "tom") // SQL: WHERE NOT ("name" = "tom")
// 或者: WHERE "name" <> "tom" // 或者: WHERE "name" <> "tom"
// 用NOT可以过滤出NULL而用<>、!=则不能。 // 用NOT可以过滤出NULL而用<>、!=则不能。
func filterNot(s *sql.Selector, field, value string) *sql.Predicate { func filterNot(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return sql.Not(sql.EQ(s.C(field), value)) return p.Not().EQ(s.C(field), value)
} }
// filterIn IN操作 // filterIn IN操作
// SQL: WHERE name IN ("tom", "jimmy") // SQL: WHERE name IN ("tom", "jimmy")
func filterIn(s *sql.Selector, field, value string) *sql.Predicate { func filterIn(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
var values []any var values []any
if err := json.Unmarshal([]byte(value), &values); err == nil { if err := json.Unmarshal([]byte(value), &values); err == nil {
return sql.In(s.C(field), values...) return p.In(s.C(field), values...)
} }
return nil return nil
} }
// filterNotIn NOT IN操作 // filterNotIn NOT IN操作
// SQL: WHERE name NOT IN ("tom", "jimmy")` // SQL: WHERE name NOT IN ("tom", "jimmy")`
func filterNotIn(s *sql.Selector, field, value string) *sql.Predicate { func filterNotIn(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
var values []any var values []any
if err := json.Unmarshal([]byte(value), &values); err == nil { if err := json.Unmarshal([]byte(value), &values); err == nil {
return sql.NotIn(s.C(field), values...) return p.NotIn(s.C(field), values...)
} }
return nil return nil
} }
// filterGTE GTE (Greater Than or Equal) 大于等于 >=操作 // filterGTE GTE (Greater Than or Equal) 大于等于 >=操作
// SQL: WHERE "create_time" >= "2023-10-25" // SQL: WHERE "create_time" >= "2023-10-25"
func filterGTE(s *sql.Selector, field, value string) *sql.Predicate { func filterGTE(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return sql.GTE(s.C(field), value) return p.GTE(s.C(field), value)
} }
// filterGT GT (Greater than) 大于 >操作 // filterGT GT (Greater than) 大于 >操作
// SQL: WHERE "create_time" > "2023-10-25" // SQL: WHERE "create_time" > "2023-10-25"
func filterGT(s *sql.Selector, field, value string) *sql.Predicate { func filterGT(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return sql.GT(s.C(field), value) return p.GT(s.C(field), value)
} }
// filterLTE LTE (Less Than or Equal) 小于等于 <=操作 // filterLTE LTE (Less Than or Equal) 小于等于 <=操作
// SQL: WHERE "create_time" <= "2023-10-25" // SQL: WHERE "create_time" <= "2023-10-25"
func filterLTE(s *sql.Selector, field, value string) *sql.Predicate { func filterLTE(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return sql.LTE(s.C(field), value) return p.LTE(s.C(field), value)
} }
// filterLT LT (Less than) 小于 <操作 // filterLT LT (Less than) 小于 <操作
// SQL: WHERE "create_time" < "2023-10-25" // SQL: WHERE "create_time" < "2023-10-25"
func filterLT(s *sql.Selector, field, value string) *sql.Predicate { func filterLT(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return sql.LT(s.C(field), value) return p.LT(s.C(field), value)
} }
// filterRange 在值域之中 BETWEEN操作 // filterRange 在值域之中 BETWEEN操作
// SQL: WHERE "create_time" BETWEEN "2023-10-25" AND "2024-10-25" // SQL: WHERE "create_time" BETWEEN "2023-10-25" AND "2024-10-25"
// 或者: WHERE "create_time" >= "2023-10-25" AND "create_time" <= "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 { func filterRange(s *sql.Selector, _ *sql.Predicate, field, value string) *sql.Predicate {
var values []any var values []any
if err := json.Unmarshal([]byte(value), &values); err == nil { if err := json.Unmarshal([]byte(value), &values); err == nil {
if len(values) != 2 { if len(values) != 2 {
@@ -295,62 +394,62 @@ func filterRange(s *sql.Selector, field, value string) *sql.Predicate {
// filterIsNull 为空 IS NULL操作 // filterIsNull 为空 IS NULL操作
// SQL: WHERE name IS NULL // SQL: WHERE name IS NULL
func filterIsNull(s *sql.Selector, field, _ string) *sql.Predicate { func filterIsNull(s *sql.Selector, p *sql.Predicate, field, _ string) *sql.Predicate {
return sql.IsNull(s.C(field)) return p.IsNull(s.C(field))
} }
// filterIsNotNull 不为空 IS NOT NULL操作 // filterIsNotNull 不为空 IS NOT NULL操作
// SQL: WHERE name IS NOT NULL // SQL: WHERE name IS NOT NULL
func filterIsNotNull(s *sql.Selector, field, _ string) *sql.Predicate { func filterIsNotNull(s *sql.Selector, p *sql.Predicate, field, _ string) *sql.Predicate {
return sql.Not(sql.IsNull(s.C(field))) return p.Not().IsNull(s.C(field))
} }
// filterContains LIKE 前后模糊查询 // filterContains LIKE 前后模糊查询
// SQL: WHERE name LIKE '%L%'; // SQL: WHERE name LIKE '%L%';
func filterContains(s *sql.Selector, field, value string) *sql.Predicate { func filterContains(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return sql.Contains(s.C(field), value) return p.Contains(s.C(field), value)
} }
// filterInsensitiveContains ILIKE 前后模糊查询 // filterInsensitiveContains ILIKE 前后模糊查询
// SQL: WHERE name ILIKE '%L%'; // SQL: WHERE name ILIKE '%L%';
func filterInsensitiveContains(s *sql.Selector, field, value string) *sql.Predicate { func filterInsensitiveContains(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return sql.ContainsFold(s.C(field), value) return p.ContainsFold(s.C(field), value)
} }
// filterStartsWith LIKE 前缀+模糊查询 // filterStartsWith LIKE 前缀+模糊查询
// SQL: WHERE name LIKE 'La%'; // SQL: WHERE name LIKE 'La%';
func filterStartsWith(s *sql.Selector, field, value string) *sql.Predicate { func filterStartsWith(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return sql.HasPrefix(s.C(field), value) return p.HasPrefix(s.C(field), value)
} }
// filterInsensitiveStartsWith ILIKE 前缀+模糊查询 // filterInsensitiveStartsWith ILIKE 前缀+模糊查询
// SQL: WHERE name ILIKE 'La%'; // SQL: WHERE name ILIKE 'La%';
func filterInsensitiveStartsWith(s *sql.Selector, field, value string) *sql.Predicate { func filterInsensitiveStartsWith(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return sql.EqualFold(s.C(field), value+"%") return p.EqualFold(s.C(field), value+"%")
} }
// filterEndsWith LIKE 后缀+模糊查询 // filterEndsWith LIKE 后缀+模糊查询
// SQL: WHERE name LIKE '%a'; // SQL: WHERE name LIKE '%a';
func filterEndsWith(s *sql.Selector, field, value string) *sql.Predicate { func filterEndsWith(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return sql.HasSuffix(s.C(field), value) return p.HasSuffix(s.C(field), value)
} }
// filterInsensitiveEndsWith ILIKE 后缀+模糊查询 // filterInsensitiveEndsWith ILIKE 后缀+模糊查询
// SQL: WHERE name ILIKE '%a'; // SQL: WHERE name ILIKE '%a';
func filterInsensitiveEndsWith(s *sql.Selector, field, value string) *sql.Predicate { func filterInsensitiveEndsWith(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return sql.EqualFold(s.C(field), "%"+value) return p.EqualFold(s.C(field), "%"+value)
} }
// filterExact LIKE 操作 精确比对 // filterExact LIKE 操作 精确比对
// SQL: WHERE name LIKE 'a'; // SQL: WHERE name LIKE 'a';
func filterExact(s *sql.Selector, field, value string) *sql.Predicate { func filterExact(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return sql.Like(s.C(field), value) return p.Like(s.C(field), value)
} }
// filterInsensitiveExact ILIKE 操作 不区分大小写,精确比对 // filterInsensitiveExact ILIKE 操作 不区分大小写,精确比对
// SQL: WHERE name ILIKE 'a'; // SQL: WHERE name ILIKE 'a';
func filterInsensitiveExact(s *sql.Selector, field, value string) *sql.Predicate { func filterInsensitiveExact(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return sql.EqualFold(s.C(field), value) return p.EqualFold(s.C(field), value)
} }
// filterRegex 正则查找 // filterRegex 正则查找
@@ -358,22 +457,24 @@ func filterInsensitiveExact(s *sql.Selector, field, value string) *sql.Predicate
// Oracle: WHERE REGEXP_LIKE(title, '^(An?|The) +', 'c'); // Oracle: WHERE REGEXP_LIKE(title, '^(An?|The) +', 'c');
// PostgreSQL: WHERE title ~ '^(An?|The) +'; // PostgreSQL: WHERE title ~ '^(An?|The) +';
// SQLite: WHERE title REGEXP '^(An?|The) +'; // SQLite: WHERE title REGEXP '^(An?|The) +';
func filterRegex(s *sql.Selector, field, value string) *sql.Predicate { func filterRegex(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
p := sql.P()
p.Append(func(b *sql.Builder) { p.Append(func(b *sql.Builder) {
switch s.Builder.Dialect() { switch s.Builder.Dialect() {
case dialect.Postgres: case dialect.Postgres:
b.Ident(s.C(field)).WriteString(" ~ ") b.Ident(s.C(field)).WriteString(" ~ ")
b.Arg(value) b.Arg(value)
break break
case dialect.MySQL: case dialect.MySQL:
b.Ident(s.C(field)).WriteString(" REGEXP BINARY ") b.Ident(s.C(field)).WriteString(" REGEXP BINARY ")
b.Arg(value) b.Arg(value)
break break
case dialect.SQLite: case dialect.SQLite:
b.Ident(s.C(field)).WriteString(" REGEXP ") b.Ident(s.C(field)).WriteString(" REGEXP ")
b.Arg(value) b.Arg(value)
break break
case dialect.Gremlin: case dialect.Gremlin:
break break
} }
@@ -386,18 +487,19 @@ func filterRegex(s *sql.Selector, field, value string) *sql.Predicate {
// Oracle: WHERE REGEXP_LIKE(title, '^(an?|the) +', 'i'); // Oracle: WHERE REGEXP_LIKE(title, '^(an?|the) +', 'i');
// PostgreSQL: WHERE title ~* '^(an?|the) +'; // PostgreSQL: WHERE title ~* '^(an?|the) +';
// SQLite: WHERE title REGEXP '(?i)^(an?|the) +'; // SQLite: WHERE title REGEXP '(?i)^(an?|the) +';
func filterInsensitiveRegex(s *sql.Selector, field, value string) *sql.Predicate { func filterInsensitiveRegex(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
p := sql.P()
p.Append(func(b *sql.Builder) { p.Append(func(b *sql.Builder) {
switch s.Builder.Dialect() { switch s.Builder.Dialect() {
case dialect.Postgres: case dialect.Postgres:
b.Ident(s.C(field)).WriteString(" ~* ") b.Ident(s.C(field)).WriteString(" ~* ")
b.Arg(strings.ToLower(value)) b.Arg(strings.ToLower(value))
break break
case dialect.MySQL: case dialect.MySQL:
b.Ident(s.C(field)).WriteString(" REGEXP ") b.Ident(s.C(field)).WriteString(" REGEXP ")
b.Arg(strings.ToLower(value)) b.Arg(strings.ToLower(value))
break break
case dialect.SQLite: case dialect.SQLite:
b.Ident(s.C(field)).WriteString(" REGEXP ") b.Ident(s.C(field)).WriteString(" REGEXP ")
if !strings.HasPrefix(value, "(?i)") { if !strings.HasPrefix(value, "(?i)") {
@@ -405,6 +507,7 @@ func filterInsensitiveRegex(s *sql.Selector, field, value string) *sql.Predicate
} }
b.Arg(strings.ToLower(value)) b.Arg(strings.ToLower(value))
break break
case dialect.Gremlin: case dialect.Gremlin:
break break
} }
@@ -414,19 +517,87 @@ func filterInsensitiveRegex(s *sql.Selector, field, value string) *sql.Predicate
// filterSearch 全文搜索 // filterSearch 全文搜索
// SQL: // SQL:
func filterSearch(s *sql.Selector, _, _ string) *sql.Predicate { func filterSearch(s *sql.Selector, p *sql.Predicate, _, _ string) *sql.Predicate {
p := sql.P()
p.Append(func(b *sql.Builder) { p.Append(func(b *sql.Builder) {
switch s.Builder.Dialect() { switch s.Builder.Dialect() {
} }
}) })
return p
return nil
} }
// filterDatePart 时间戳提取日期 select extract(quarter from timestamp '2018-08-15 12:10:10'); // filterDatePart 时间戳提取日期
// SQL: // SQL: select extract(quarter from timestamp '2018-08-15 12:10:10');
func filterDatePart(s *sql.Selector, datePart, field, value string) *sql.Predicate { func filterDatePart(s *sql.Selector, p *sql.Predicate, datePart, field string) *sql.Predicate {
return nil p.Append(func(b *sql.Builder) {
switch s.Builder.Dialect() {
case dialect.Postgres:
str := fmt.Sprintf("EXTRACT('%s' FROM %s)", strings.ToUpper(datePart), s.C(field))
b.WriteString(str)
//b.Arg(strings.ToLower(value))
break
case dialect.MySQL:
str := fmt.Sprintf("%s(%s)", strings.ToUpper(datePart), s.C(field))
b.WriteString(str)
//b.Arg(strings.ToLower(value))
break
}
})
return p
}
func filterDatePartField(s *sql.Selector, datePart, field string) string {
p := sql.P()
switch s.Builder.Dialect() {
case dialect.Postgres:
str := fmt.Sprintf("EXTRACT('%s' FROM %s)", strings.ToUpper(datePart), s.C(field))
p.WriteString(str)
break
case dialect.MySQL:
str := fmt.Sprintf("%s(%s)", strings.ToUpper(datePart), s.C(field))
p.WriteString(str)
break
}
return p.String()
}
// filterJsonb 提取JSONB字段
// Postgresql: WHERE ("app_profile"."preferences" -> daily_email) = 'true'
func filterJsonb(s *sql.Selector, p *sql.Predicate, jsonbField, field string) *sql.Predicate {
p.Append(func(b *sql.Builder) {
switch s.Builder.Dialect() {
case dialect.Postgres:
b.Ident(s.C(field)).WriteString(" -> ").WriteString(jsonbField)
//b.Arg(strings.ToLower(value))
break
case dialect.MySQL:
str := fmt.Sprintf("JSON_EXTRACT(%s, '$.%s')", s.C(field), jsonbField)
b.WriteString(str)
//b.Arg(strings.ToLower(value))
break
}
})
return p
}
func filterJsonbField(s *sql.Selector, jsonbField, field string) string {
p := sql.P()
switch s.Builder.Dialect() {
case dialect.Postgres:
p.Ident(s.C(field)).WriteString(" -> ").WriteString(jsonbField)
//b.Arg(strings.ToLower(value))
break
case dialect.MySQL:
str := fmt.Sprintf("JSON_EXTRACT(%s, '$.%s')", s.C(field), jsonbField)
p.WriteString(str)
//b.Arg(strings.ToLower(value))
break
}
return p.String()
} }

View File

@@ -1,6 +1,7 @@
package entgo package entgo
import ( import (
"fmt"
"testing" "testing"
"entgo.io/ent/dialect" "entgo.io/ent/dialect"
@@ -13,7 +14,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterEqual", func(t *testing.T) { t.Run("MySQL_FilterEqual", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterEqual(s, "name", "tom") p := sql.P()
p = filterEqual(s, p, "name", "tom")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -24,7 +27,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterEqual", func(t *testing.T) { t.Run("PostgreSQL_FilterEqual", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterEqual(s, "name", "tom") p := sql.P()
p = filterEqual(s, p, "name", "tom")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -38,22 +43,26 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterNot", func(t *testing.T) { t.Run("MySQL_FilterNot", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterNot(s, "name", "tom") p := sql.P()
p = filterNot(s, p, "name", "tom")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
require.Equal(t, "SELECT * FROM `users` WHERE NOT (`users`.`name` = ?)", query) require.Equal(t, "SELECT * FROM `users` WHERE NOT `users`.`name` = ?", query)
require.NotEmpty(t, args) require.NotEmpty(t, args)
require.Equal(t, args[0], "tom") require.Equal(t, args[0], "tom")
}) })
t.Run("PostgreSQL_FilterNot", func(t *testing.T) { t.Run("PostgreSQL_FilterNot", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterNot(s, "name", "tom") p := sql.P()
p = filterNot(s, p, "name", "tom")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
require.Equal(t, "SELECT * FROM \"users\" WHERE NOT (\"users\".\"name\" = $1)", query) require.Equal(t, "SELECT * FROM \"users\" WHERE NOT \"users\".\"name\" = $1", query)
require.NotEmpty(t, args) require.NotEmpty(t, args)
require.Equal(t, args[0], "tom") require.Equal(t, args[0], "tom")
}) })
@@ -63,7 +72,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterIn", func(t *testing.T) { t.Run("MySQL_FilterIn", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterIn(s, "name", "[\"tom\", \"jimmy\", 123]") p := sql.P()
p = filterIn(s, p, "name", "[\"tom\", \"jimmy\", 123]")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -76,7 +87,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterIn", func(t *testing.T) { t.Run("PostgreSQL_FilterIn", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterIn(s, "name", "[\"tom\", \"jimmy\", 123]") p := sql.P()
p = filterIn(s, p, "name", "[\"tom\", \"jimmy\", 123]")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -92,7 +105,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterNotIn", func(t *testing.T) { t.Run("MySQL_FilterNotIn", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterNotIn(s, "name", "[\"tom\", \"jimmy\", 123]") p := sql.P()
p = filterNotIn(s, p, "name", "[\"tom\", \"jimmy\", 123]")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -105,7 +120,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterNotIn", func(t *testing.T) { t.Run("PostgreSQL_FilterNotIn", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterNotIn(s, "name", "[\"tom\", \"jimmy\", 123]") p := sql.P()
p = filterNotIn(s, p, "name", "[\"tom\", \"jimmy\", 123]")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -121,7 +138,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterGTE", func(t *testing.T) { t.Run("MySQL_FilterGTE", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterGTE(s, "create_time", "2023-10-25") p := sql.P()
p = filterGTE(s, p, "create_time", "2023-10-25")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -132,7 +151,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterGTE", func(t *testing.T) { t.Run("PostgreSQL_FilterGTE", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterGTE(s, "create_time", "2023-10-25") p := sql.P()
p = filterGTE(s, p, "create_time", "2023-10-25")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -146,7 +167,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterGT", func(t *testing.T) { t.Run("MySQL_FilterGT", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterGT(s, "create_time", "2023-10-25") p := sql.P()
p = filterGT(s, p, "create_time", "2023-10-25")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -157,7 +180,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterGT", func(t *testing.T) { t.Run("PostgreSQL_FilterGT", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterGT(s, "create_time", "2023-10-25") p := sql.P()
p = filterGT(s, p, "create_time", "2023-10-25")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -171,7 +196,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterLTE", func(t *testing.T) { t.Run("MySQL_FilterLTE", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterLTE(s, "create_time", "2023-10-25") p := sql.P()
p = filterLTE(s, p, "create_time", "2023-10-25")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -182,7 +209,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterLTE", func(t *testing.T) { t.Run("PostgreSQL_FilterLTE", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterLTE(s, "create_time", "2023-10-25") p := sql.P()
p = filterLTE(s, p, "create_time", "2023-10-25")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -196,7 +225,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterLT", func(t *testing.T) { t.Run("MySQL_FilterLT", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterLT(s, "create_time", "2023-10-25") p := sql.P()
p = filterLT(s, p, "create_time", "2023-10-25")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -207,7 +238,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterLT", func(t *testing.T) { t.Run("PostgreSQL_FilterLT", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterLT(s, "create_time", "2023-10-25") p := sql.P()
p = filterLT(s, p, "create_time", "2023-10-25")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -221,7 +254,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterRange", func(t *testing.T) { t.Run("MySQL_FilterRange", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterRange(s, "create_time", "[\"2023-10-25\", \"2024-10-25\"]") p := sql.P()
p = filterRange(s, p, "create_time", "[\"2023-10-25\", \"2024-10-25\"]")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -233,7 +268,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterRange", func(t *testing.T) { t.Run("PostgreSQL_FilterRange", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterRange(s, "create_time", "[\"2023-10-25\", \"2024-10-25\"]") p := sql.P()
p = filterRange(s, p, "create_time", "[\"2023-10-25\", \"2024-10-25\"]")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -248,7 +285,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterIsNull", func(t *testing.T) { t.Run("MySQL_FilterIsNull", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterIsNull(s, "name", "true") p := sql.P()
p = filterIsNull(s, p, "name", "true")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -258,7 +297,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterIsNull", func(t *testing.T) { t.Run("PostgreSQL_FilterIsNull", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterIsNull(s, "name", "true") p := sql.P()
p = filterIsNull(s, p, "name", "true")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -271,21 +312,25 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterIsNotNull", func(t *testing.T) { t.Run("MySQL_FilterIsNotNull", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterIsNotNull(s, "name", "true") p := sql.P()
p = filterIsNotNull(s, p, "name", "true")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
require.Equal(t, "SELECT * FROM `users` WHERE NOT (`users`.`name` IS NULL)", query) require.Equal(t, "SELECT * FROM `users` WHERE NOT `users`.`name` IS NULL", query)
require.Empty(t, args) require.Empty(t, args)
}) })
t.Run("PostgreSQL_FilterIsNotNull", func(t *testing.T) { t.Run("PostgreSQL_FilterIsNotNull", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterIsNotNull(s, "name", "true") p := sql.P()
p = filterIsNotNull(s, p, "name", "true")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
require.Equal(t, "SELECT * FROM \"users\" WHERE NOT (\"users\".\"name\" IS NULL)", query) require.Equal(t, "SELECT * FROM \"users\" WHERE NOT \"users\".\"name\" IS NULL", query)
require.Empty(t, args) require.Empty(t, args)
}) })
@@ -294,7 +339,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterContains", func(t *testing.T) { t.Run("MySQL_FilterContains", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterContains(s, "name", "L") p := sql.P()
p = filterContains(s, p, "name", "L")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -305,7 +352,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterContains", func(t *testing.T) { t.Run("PostgreSQL_FilterContains", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterContains(s, "name", "L") p := sql.P()
p = filterContains(s, p, "name", "L")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -319,7 +368,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterInsensitiveContains", func(t *testing.T) { t.Run("MySQL_FilterInsensitiveContains", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterInsensitiveContains(s, "name", "L") p := sql.P()
p = filterInsensitiveContains(s, p, "name", "L")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -330,7 +381,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterInsensitiveContains", func(t *testing.T) { t.Run("PostgreSQL_FilterInsensitiveContains", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterInsensitiveContains(s, "name", "L") p := sql.P()
p = filterInsensitiveContains(s, p, "name", "L")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -344,7 +397,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterStartsWith", func(t *testing.T) { t.Run("MySQL_FilterStartsWith", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterStartsWith(s, "name", "La") p := sql.P()
p = filterStartsWith(s, p, "name", "La")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -355,7 +410,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterStartsWith", func(t *testing.T) { t.Run("PostgreSQL_FilterStartsWith", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterStartsWith(s, "name", "La") p := sql.P()
p = filterStartsWith(s, p, "name", "La")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -369,7 +426,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterInsensitiveStartsWith", func(t *testing.T) { t.Run("MySQL_FilterInsensitiveStartsWith", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterInsensitiveStartsWith(s, "name", "La") p := sql.P()
p = filterInsensitiveStartsWith(s, p, "name", "La")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -380,7 +439,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterInsensitiveStartsWith", func(t *testing.T) { t.Run("PostgreSQL_FilterInsensitiveStartsWith", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterInsensitiveStartsWith(s, "name", "La") p := sql.P()
p = filterInsensitiveStartsWith(s, p, "name", "La")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -394,7 +455,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterEndsWith", func(t *testing.T) { t.Run("MySQL_FilterEndsWith", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterEndsWith(s, "name", "La") p := sql.P()
p = filterEndsWith(s, p, "name", "La")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -405,7 +468,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterEndsWith", func(t *testing.T) { t.Run("PostgreSQL_FilterEndsWith", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterEndsWith(s, "name", "La") p := sql.P()
p = filterEndsWith(s, p, "name", "La")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -419,7 +484,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterInsensitiveEndsWith", func(t *testing.T) { t.Run("MySQL_FilterInsensitiveEndsWith", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterInsensitiveEndsWith(s, "name", "La") p := sql.P()
p = filterInsensitiveEndsWith(s, p, "name", "La")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -430,7 +497,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterInsensitiveEndsWith", func(t *testing.T) { t.Run("PostgreSQL_FilterInsensitiveEndsWith", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterInsensitiveEndsWith(s, "name", "La") p := sql.P()
p = filterInsensitiveEndsWith(s, p, "name", "La")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -444,7 +513,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterExact", func(t *testing.T) { t.Run("MySQL_FilterExact", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterExact(s, "name", "La") p := sql.P()
p = filterExact(s, p, "name", "La")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -455,7 +526,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterExact", func(t *testing.T) { t.Run("PostgreSQL_FilterExact", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterExact(s, "name", "La") p := sql.P()
p = filterExact(s, p, "name", "La")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -469,7 +542,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterInsensitiveExact", func(t *testing.T) { t.Run("MySQL_FilterInsensitiveExact", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterInsensitiveExact(s, "name", "La") p := sql.P()
p = filterInsensitiveExact(s, p, "name", "La")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -480,7 +555,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterInsensitiveExact", func(t *testing.T) { t.Run("PostgreSQL_FilterInsensitiveExact", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterInsensitiveExact(s, "name", "La") p := sql.P()
p = filterInsensitiveExact(s, p, "name", "La")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -494,7 +571,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterRegex", func(t *testing.T) { t.Run("MySQL_FilterRegex", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterRegex(s, "name", "^(An?|The) +") p := sql.P()
p = filterRegex(s, p, "name", "^(An?|The) +")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -505,7 +584,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterRegex", func(t *testing.T) { t.Run("PostgreSQL_FilterRegex", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterRegex(s, "name", "^(An?|The) +") p := sql.P()
p = filterRegex(s, p, "name", "^(An?|The) +")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -519,7 +600,9 @@ func TestFilter(t *testing.T) {
t.Run("MySQL_FilterInsensitiveRegex", func(t *testing.T) { t.Run("MySQL_FilterInsensitiveRegex", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := filterInsensitiveRegex(s, "name", "^(An?|The) +") p := sql.P()
p = filterInsensitiveRegex(s, p, "name", "^(An?|The) +")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -530,7 +613,9 @@ func TestFilter(t *testing.T) {
t.Run("PostgreSQL_FilterInsensitiveRegex", func(t *testing.T) { t.Run("PostgreSQL_FilterInsensitiveRegex", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users")) s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := filterInsensitiveRegex(s, "name", "^(An?|The) +") p := sql.P()
p = filterInsensitiveRegex(s, p, "name", "^(An?|The) +")
s.Where(p) s.Where(p)
query, args := s.Query() query, args := s.Query()
@@ -538,4 +623,72 @@ func TestFilter(t *testing.T) {
require.NotEmpty(t, args) require.NotEmpty(t, args)
require.Equal(t, args[0], "^(an?|the) +") require.Equal(t, args[0], "^(an?|the) +")
}) })
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
t.Run("MySQL_FilterDatePart", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("publishes"))
p := sql.P()
p = filterDatePart(s, p, "date", "pub_date")
p.EQ("", "2023-01-01")
s.Where(p)
query, args := s.Query()
require.Equal(t, "SELECT * FROM `publishes` WHERE DATE(`publishes`.`pub_date`) = ?", query)
require.NotEmpty(t, args)
require.Equal(t, args[0], "2023-01-01")
})
t.Run("PostgreSQL_FilterDatePart", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("publishes"))
p := sql.P()
p = filterDatePart(s, p, "date", "pub_date")
p.EQ("", "2023-01-01")
s.Where(p)
query, args := s.Query()
require.Equal(t, "SELECT * FROM \"publishes\" WHERE EXTRACT('DATE' FROM \"publishes\".\"pub_date\") = $1", query)
require.NotEmpty(t, args)
require.Equal(t, args[0], "2023-01-01")
})
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
t.Run("MySQL_FilterJsonb", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("app_profile"))
p := sql.P()
p = filterJsonb(s, p, "daily_email", "preferences")
p.EQ("", "true")
s.Where(p)
query, args := s.Query()
require.Equal(t, "SELECT * FROM `app_profile` WHERE JSON_EXTRACT(`app_profile`.`preferences`, '$.daily_email') = ?", query)
require.NotEmpty(t, args)
require.Equal(t, args[0], "true")
})
t.Run("PostgreSQL_FilterJsonb", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("app_profile"))
p := sql.P()
p = filterJsonb(s, p, "daily_email", "preferences")
p.EQ("", "true")
s.Where(p)
query, args := s.Query()
require.Equal(t, "SELECT * FROM \"app_profile\" WHERE \"app_profile\".\"preferences\" -> daily_email = $1", query)
require.NotEmpty(t, args)
require.Equal(t, args[0], "true")
})
}
func TestFilterJsonbField(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("app_profile"))
str := filterJsonbField(s, "daily_email", "preferences")
fmt.Println(str)
} }

View File

@@ -34,6 +34,14 @@ func QueryCommandToOrderConditions(orderBys []string) (error, func(s *sql.Select
} }
} }
func BuildOrderSelect(s *sql.Selector, field string, desc bool) {
if desc {
s.OrderBy(sql.Desc(s.C(field)))
} else {
s.OrderBy(sql.Asc(s.C(field)))
}
}
func BuildOrderSelector(orderBys []string, defaultOrderField string) (error, func(s *sql.Selector)) { func BuildOrderSelector(orderBys []string, defaultOrderField string) (error, func(s *sql.Selector)) {
if len(orderBys) == 0 { if len(orderBys) == 0 {
return nil, func(s *sql.Selector) { return nil, func(s *sql.Selector) {
@@ -43,11 +51,3 @@ func BuildOrderSelector(orderBys []string, defaultOrderField string) (error, fun
return QueryCommandToOrderConditions(orderBys) return QueryCommandToOrderConditions(orderBys)
} }
} }
func BuildOrderSelect(s *sql.Selector, field string, desc bool) {
if desc {
s.OrderBy(sql.Desc(s.C(field)))
} else {
s.OrderBy(sql.Asc(s.C(field)))
}
}

View File

@@ -2,6 +2,7 @@ package entgo
import ( import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
_ "github.com/go-kratos/kratos/v2/encoding/json" _ "github.com/go-kratos/kratos/v2/encoding/json"
) )
@@ -10,6 +11,7 @@ func BuildQuerySelector(
andFilterJsonString, orFilterJsonString string, andFilterJsonString, orFilterJsonString string,
page, pageSize int32, noPaging bool, page, pageSize int32, noPaging bool,
orderBys []string, defaultOrderField string, orderBys []string, defaultOrderField string,
selectFields []string,
) (err error, whereSelectors []func(s *sql.Selector), querySelectors []func(s *sql.Selector)) { ) (err error, whereSelectors []func(s *sql.Selector), querySelectors []func(s *sql.Selector)) {
err, whereSelectors = BuildFilterSelector(andFilterJsonString, orFilterJsonString) err, whereSelectors = BuildFilterSelector(andFilterJsonString, orFilterJsonString)
if err != nil { if err != nil {
@@ -24,6 +26,9 @@ func BuildQuerySelector(
pageSelector := BuildPaginationSelector(page, pageSize, noPaging) pageSelector := BuildPaginationSelector(page, pageSize, noPaging)
var fieldSelector func(s *sql.Selector)
err, fieldSelector = BuildFieldSelector(selectFields)
if len(whereSelectors) > 0 { if len(whereSelectors) > 0 {
querySelectors = append(querySelectors, whereSelectors...) querySelectors = append(querySelectors, whereSelectors...)
} }
@@ -34,6 +39,9 @@ func BuildQuerySelector(
if pageSelector != nil { if pageSelector != nil {
querySelectors = append(querySelectors, pageSelector) querySelectors = append(querySelectors, pageSelector)
} }
if fieldSelector != nil {
querySelectors = append(querySelectors, fieldSelector)
}
return return
} }

View File

@@ -9,11 +9,9 @@ import (
"entgo.io/ent/dialect" "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql" "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"
_ "github.com/go-kratos/kratos/v2/encoding/json" _ "github.com/go-kratos/kratos/v2/encoding/json"
"github.com/stretchr/testify/assert"
) )
func TestKratosJsonCodec(t *testing.T) { func TestKratosJsonCodec(t *testing.T) {
@@ -98,81 +96,56 @@ func TestSplitQuery(t *testing.T) {
} }
func TestBuildQuerySelectorDefault(t *testing.T) { func TestBuildQuerySelectorDefault(t *testing.T) {
t.Run("MySQL_Pagination", func(t *testing.T) { testcases := []struct {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) name string
dialect string
and string
or string
noPaging bool
actualSql string
}{
{"MySQL_Pagination", dialect.MySQL, "", "", false, "SELECT * FROM `users` ORDER BY `users`.`created_at` DESC LIMIT 10 OFFSET 0"},
{"PostgreSQL_Pagination", dialect.Postgres, "", "", false, "SELECT * FROM \"users\" ORDER BY \"users\".\"created_at\" DESC LIMIT 10 OFFSET 0"},
err, whereSelectors, querySelectors := BuildQuerySelector("", "", 1, 10, false, []string{}, "created_at") {"MySQL_NoPagination", dialect.MySQL, "", "", true, "SELECT * FROM `users` ORDER BY `users`.`created_at` DESC"},
require.Nil(t, err) {"PostgreSQL_NoPagination", dialect.Postgres, "", "", true, "SELECT * FROM \"users\" ORDER BY \"users\".\"created_at\" DESC"},
require.Nil(t, whereSelectors)
require.NotNil(t, querySelectors)
for _, fnc := range whereSelectors { {"MySQL_JsonbQuery", dialect.MySQL, "{\"preferences__daily_email\" : \"true\"}", "", true, "SELECT * FROM `users` WHERE JSON_EXTRACT(`users`.`preferences`, '$.daily_email') = ? ORDER BY `users`.`created_at` DESC"},
fnc(s) {"PostgreSQL_JsonbQuery", dialect.Postgres, "{\"preferences__daily_email\" : \"true\"}", "", true, "SELECT * FROM \"users\" WHERE \"users\".\"preferences\" -> daily_email = $1 ORDER BY \"users\".\"created_at\" DESC"},
}
for _, fnc := range querySelectors {
fnc(s)
}
query, args := s.Query() {"MySQL_DatePartQuery", dialect.MySQL, "{\"created_at__date\" : \"2023-01-01\"}", "", true, "SELECT * FROM `users` WHERE DATE(`users`.`created_at`) = ? ORDER BY `users`.`created_at` DESC"},
require.Equal(t, "SELECT * FROM `users` ORDER BY `users`.`created_at` DESC LIMIT 10 OFFSET 0", query) {"PostgreSQL_DatePartQuery", dialect.Postgres, "{\"created_at__date\" : \"2023-01-01\"}", "", true, "SELECT * FROM \"users\" WHERE EXTRACT('DATE' FROM \"users\".\"created_at\") = $1 ORDER BY \"users\".\"created_at\" DESC"},
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") {"MySQL_JsonbCombineQuery", dialect.MySQL, "{\"preferences__pub_date__not\" : \"true\"}", "", true, "SELECT * FROM `users` WHERE NOT JSON_EXTRACT(`users`.`preferences`, '$.pub_date') = ? ORDER BY `users`.`created_at` DESC"},
require.Nil(t, err) {"PostgreSQL_JsonbCombineQuery", dialect.Postgres, "{\"preferences__pub_date__not\" : \"true\"}", "", true, "SELECT * FROM \"users\" WHERE NOT \"users\".\"preferences\" -> pub_date = $1 ORDER BY \"users\".\"created_at\" DESC"},
require.Nil(t, whereSelectors)
require.NotNil(t, querySelectors)
for _, fnc := range whereSelectors { {"MySQL_DatePartCombineQuery", dialect.MySQL, "{\"pub_date__date__not\" : \"true\"}", "", true, "SELECT * FROM `users` WHERE NOT DATE(`users`.`pub_date`) = ? ORDER BY `users`.`created_at` DESC"},
fnc(s) {"PostgreSQL_DatePartCombineQuery", dialect.Postgres, "{\"pub_date__date__not\" : \"true\"}", "", true, "SELECT * FROM \"users\" WHERE NOT EXTRACT('DATE' FROM \"users\".\"pub_date\") = $1 ORDER BY \"users\".\"created_at\" DESC"},
}
for _, fnc := range querySelectors {
fnc(s)
}
query, args := s.Query() {"MySQL_DatePartRangeQuery", dialect.MySQL, "{\"pub_date__date__range\" : \"[\\\"2023-10-25\\\", \\\"2024-10-25\\\"]\"}", "", true, "SELECT * FROM `users` WHERE DATE(`users`.`pub_date`) >= ? AND DATE(`users`.`pub_date`) <= ? ORDER BY `users`.`created_at` DESC"},
require.Equal(t, "SELECT * FROM \"users\" ORDER BY \"users\".\"created_at\" DESC LIMIT 10 OFFSET 0", query) {"PostgreSQL_DatePartRangeQuery", dialect.Postgres, "{\"pub_date__date__range\" : \"[\\\"2023-10-25\\\", \\\"2024-10-25\\\"]\"}", "", true, "SELECT * FROM \"users\" WHERE EXTRACT('DATE' FROM \"users\".\"pub_date\") >= $1 AND EXTRACT('DATE' FROM \"users\".\"pub_date\") <= $2 ORDER BY \"users\".\"created_at\" DESC"},
require.Empty(t, args) }
}) for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
checker := assert.New(t)
s := sql.Dialect(tc.dialect).Select("*").From(sql.Table("users"))
t.Run("MySQL_NoPagination", func(t *testing.T) { err, _, querySelectors := BuildQuerySelector(tc.and, tc.or,
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users")) 1, 10, tc.noPaging,
[]string{}, "created_at",
[]string{},
)
checker.Nil(err)
//checker.NotNil(whereSelectors)
checker.NotNil(querySelectors)
err, whereSelectors, querySelectors := BuildQuerySelector("", "", 1, 10, true, []string{}, "created_at") for _, fnc := range querySelectors {
require.Nil(t, err) fnc(s)
require.Nil(t, whereSelectors) }
require.NotNil(t, querySelectors)
for _, fnc := range whereSelectors { query, _ := s.Query()
fnc(s) checker.Equal(tc.actualSql, query)
} //checker.Empty(t, args)
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)
})
} }

29
entgo/query/select.go Normal file
View File

@@ -0,0 +1,29 @@
package entgo
import (
"entgo.io/ent/dialect/sql"
"github.com/tx7do/go-utils/stringcase"
)
func BuildFieldSelect(s *sql.Selector, fields []string) {
if len(fields) > 0 {
for i, field := range fields {
switch {
case field == "id_" || field == "_id":
field = "id"
}
fields[i] = stringcase.ToSnakeCase(field)
}
s.Select(fields...)
}
}
func BuildFieldSelector(fields []string) (error, func(s *sql.Selector)) {
if len(fields) > 0 {
return nil, func(s *sql.Selector) {
BuildFieldSelect(s, fields)
}
} else {
return nil, nil
}
}

View File

@@ -0,0 +1,48 @@
package entgo
import (
"testing"
"github.com/stretchr/testify/require"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
)
func TestBuildFieldSelect(t *testing.T) {
t.Run("MySQL_2Fields", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
BuildFieldSelect(s, []string{"id", "username"})
query, args := s.Query()
require.Equal(t, "SELECT `id`, `username` FROM `users`", query)
require.Empty(t, args)
})
t.Run("PostgreSQL_2Fields", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
BuildFieldSelect(s, []string{"id", "username"})
query, args := s.Query()
require.Equal(t, `SELECT "id", "username" FROM "users"`, query)
require.Empty(t, args)
})
t.Run("MySQL_AllFields", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
BuildFieldSelect(s, []string{})
query, args := s.Query()
require.Equal(t, "SELECT * FROM `users`", query)
require.Empty(t, args)
})
t.Run("PostgreSQL_AllFields", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
BuildFieldSelect(s, []string{})
query, args := s.Query()
require.Equal(t, `SELECT * FROM "users"`, query)
require.Empty(t, args)
})
}

25
entgo/update/update.go Normal file
View File

@@ -0,0 +1,25 @@
package entgo
import (
"entgo.io/ent/dialect/sql"
"github.com/tx7do/go-utils/stringcase"
)
func BuildSetNullUpdate(u *sql.UpdateBuilder, fields []string) {
if len(fields) > 0 {
for _, field := range fields {
field = stringcase.ToSnakeCase(field)
u.SetNull(field)
}
}
}
func BuildSetNullUpdater(fields []string) (error, func(u *sql.UpdateBuilder)) {
if len(fields) > 0 {
return nil, func(u *sql.UpdateBuilder) {
BuildSetNullUpdate(u, fields)
}
} else {
return nil, nil
}
}

View File

@@ -0,0 +1,30 @@
package entgo
import (
"testing"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"github.com/stretchr/testify/require"
)
func TestBuildSetNullUpdate(t *testing.T) {
t.Run("MySQL_Set2", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Update("users")
BuildSetNullUpdate(s, []string{"id", "username"})
query, args := s.Query()
require.Equal(t, "UPDATE `users` SET `id` = NULL, `username` = NULL", query)
require.Empty(t, args)
})
t.Run("PostgreSQL_Set2", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Update("users")
BuildSetNullUpdate(s, []string{"id", "username"})
query, args := s.Query()
require.Equal(t, `UPDATE "users" SET "id" = NULL, "username" = NULL`, query)
require.Empty(t, args)
})
}

View File

@@ -0,0 +1,234 @@
package fieldmaskutil
import (
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)
// Filter keeps the msg fields that are listed in the paths and clears all the rest.
//
// This is a handy wrapper for NestedMask.Filter method.
// If the same paths are used to process multiple proto messages use NestedMask.Filter method directly.
func Filter(msg proto.Message, paths []string) {
NestedMaskFromPaths(paths).Filter(msg)
}
// Prune clears all the fields listed in paths from the given msg.
//
// This is a handy wrapper for NestedMask.Prune method.
// If the same paths are used to process multiple proto messages use NestedMask.Filter method directly.
func Prune(msg proto.Message, paths []string) {
NestedMaskFromPaths(paths).Prune(msg)
}
// Overwrite overwrites all the fields listed in paths in the dest msg using values from src msg.
//
// This is a handy wrapper for NestedMask.Overwrite method.
// If the same paths are used to process multiple proto messages use NestedMask.Overwrite method directly.
func Overwrite(src, dest proto.Message, paths []string) {
NestedMaskFromPaths(paths).Overwrite(src, dest)
}
// NestedMask represents a field mask as a recursive map.
type NestedMask map[string]NestedMask
// NestedMaskFromPaths creates an instance of NestedMask for the given paths.
func NestedMaskFromPaths(paths []string) NestedMask {
mask := make(NestedMask)
for _, path := range paths {
curr := mask
var letters []rune
for _, letter := range path {
if letter == '.' {
if len(letters) == 0 {
continue
}
key := string(letters)
c, ok := curr[key]
if !ok {
c = make(NestedMask)
curr[key] = c
}
curr = c
letters = nil
continue
}
letters = append(letters, letter)
}
if len(letters) != 0 {
key := string(letters)
if _, ok := curr[key]; !ok {
curr[key] = make(NestedMask)
}
}
}
return mask
}
// Filter keeps the msg fields that are listed in the paths and clears all the rest.
//
// If the mask is empty then all the fields are kept.
// Paths are assumed to be valid and normalized otherwise the function may panic.
// See google.golang.org/protobuf/types/known/fieldmaskpb for details.
func (mask NestedMask) Filter(msg proto.Message) {
if len(mask) == 0 {
return
}
rft := msg.ProtoReflect()
rft.Range(func(fd protoreflect.FieldDescriptor, _ protoreflect.Value) bool {
m, ok := mask[string(fd.Name())]
if !ok {
rft.Clear(fd)
return true
}
if len(m) == 0 {
return true
}
if fd.IsMap() {
xmap := rft.Get(fd).Map()
xmap.Range(func(mk protoreflect.MapKey, mv protoreflect.Value) bool {
if mi, ok := m[mk.String()]; ok {
if i, ok := mv.Interface().(protoreflect.Message); ok && len(mi) > 0 {
mi.Filter(i.Interface())
}
} else {
xmap.Clear(mk)
}
return true
})
} else if fd.IsList() {
list := rft.Get(fd).List()
for i := 0; i < list.Len(); i++ {
m.Filter(list.Get(i).Message().Interface())
}
} else if fd.Kind() == protoreflect.MessageKind {
m.Filter(rft.Get(fd).Message().Interface())
}
return true
})
}
// Prune clears all the fields listed in paths from the given msg.
//
// All other fields are kept untouched. If the mask is empty no fields are cleared.
// This operation is the opposite of NestedMask.Filter.
// Paths are assumed to be valid and normalized otherwise the function may panic.
// See google.golang.org/protobuf/types/known/fieldmaskpb for details.
func (mask NestedMask) Prune(msg proto.Message) {
if len(mask) == 0 {
return
}
rft := msg.ProtoReflect()
rft.Range(func(fd protoreflect.FieldDescriptor, _ protoreflect.Value) bool {
m, ok := mask[string(fd.Name())]
if ok {
if len(m) == 0 {
rft.Clear(fd)
return true
}
if fd.IsMap() {
xmap := rft.Get(fd).Map()
xmap.Range(func(mk protoreflect.MapKey, mv protoreflect.Value) bool {
if mi, ok := m[mk.String()]; ok {
if i, ok := mv.Interface().(protoreflect.Message); ok && len(mi) > 0 {
mi.Prune(i.Interface())
} else {
xmap.Clear(mk)
}
}
return true
})
} else if fd.IsList() {
list := rft.Get(fd).List()
for i := 0; i < list.Len(); i++ {
m.Prune(list.Get(i).Message().Interface())
}
} else if fd.Kind() == protoreflect.MessageKind {
m.Prune(rft.Get(fd).Message().Interface())
}
}
return true
})
}
// Overwrite overwrites all the fields listed in paths in the dest msg using values from src msg.
//
// All other fields are kept untouched. If the mask is empty, no fields are overwritten.
// Supports scalars, messages, repeated fields, and maps.
// If the parent of the field is nil message, the parent is initiated before overwriting the field
// If the field in src is empty value, the field in dest is cleared.
// Paths are assumed to be valid and normalized otherwise the function may panic.
func (mask NestedMask) Overwrite(src, dest proto.Message) {
mask.overwrite(src.ProtoReflect(), dest.ProtoReflect())
}
func (mask NestedMask) overwrite(src, dest protoreflect.Message) {
for k, v := range mask {
srcFD := src.Descriptor().Fields().ByName(protoreflect.Name(k))
destFD := dest.Descriptor().Fields().ByName(protoreflect.Name(k))
if srcFD == nil || destFD == nil {
continue
}
// Leaf mask -> copy value from src to dest
if len(v) == 0 {
if srcFD.Kind() == destFD.Kind() { // TODO: Full type equality check
val := src.Get(srcFD)
if isValid(srcFD, val) {
dest.Set(destFD, val)
} else {
dest.Clear(destFD)
}
}
} else if srcFD.Kind() == protoreflect.MessageKind {
// If dest field is nil
if !dest.Get(destFD).Message().IsValid() {
dest.Set(destFD, protoreflect.ValueOf(dest.Get(destFD).Message().New()))
}
v.overwrite(src.Get(srcFD).Message(), dest.Get(destFD).Message())
}
}
}
func isValid(fd protoreflect.FieldDescriptor, val protoreflect.Value) bool {
if fd.IsMap() {
return val.Map().IsValid()
} else if fd.IsList() {
return val.List().IsValid()
} else if fd.Message() != nil {
return val.Message().IsValid()
}
return true
}
func NilValuePaths(msg proto.Message, paths []string) []string {
if len(paths) == 0 {
return nil
}
var out []string
rft := msg.ProtoReflect()
for _, v := range paths {
fd := rft.Descriptor().Fields().ByName(protoreflect.Name(v))
if fd == nil {
continue
}
if !rft.Has(fd) {
out = append(out, v)
}
}
return out
}

View File

@@ -0,0 +1,59 @@
package fieldmaskutil
import (
"reflect"
"testing"
)
func Test_NestedMaskFromPaths(t *testing.T) {
type args struct {
paths []string
}
tests := []struct {
name string
args args
want NestedMask
}{
{
name: "no nested fields",
args: args{paths: []string{"a", "b", "c"}},
want: NestedMask{"a": NestedMask{}, "b": NestedMask{}, "c": NestedMask{}},
},
{
name: "with nested fields",
args: args{paths: []string{"aaa.bb.c", "dd.e", "f"}},
want: NestedMask{
"aaa": NestedMask{"bb": NestedMask{"c": NestedMask{}}},
"dd": NestedMask{"e": NestedMask{}},
"f": NestedMask{}},
},
{
name: "single field",
args: args{paths: []string{"a"}},
want: NestedMask{"a": NestedMask{}},
},
{
name: "empty fields",
args: args{paths: []string{}},
want: NestedMask{},
},
{
name: "invalid input",
args: args{paths: []string{".", "..", "..."}},
want: NestedMask{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NestedMaskFromPaths(tt.args.paths); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NestedMaskFromPaths() = %v, want %v", got, tt.want)
}
})
}
}
func BenchmarkNestedMaskFromPaths(b *testing.B) {
for i := 0; i < b.N; i++ {
NestedMaskFromPaths([]string{"aaa.bbb.c.d.e.f", "aa.b.cc.ddddddd", "e", "f", "g.h.i.j.k"})
}
}

2
go.mod
View File

@@ -3,12 +3,14 @@ module github.com/tx7do/go-utils
go 1.20 go 1.20
require ( require (
github.com/gobwas/glob v0.2.3
github.com/google/uuid v1.4.0 github.com/google/uuid v1.4.0
github.com/gosimple/slug v1.13.1 github.com/gosimple/slug v1.13.1
github.com/sony/sonyflake v1.2.0 github.com/sony/sonyflake v1.2.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
golang.org/x/crypto v0.14.0 golang.org/x/crypto v0.14.0
golang.org/x/exp v0.0.0-20231006140011-7918f672742d golang.org/x/exp v0.0.0-20231006140011-7918f672742d
google.golang.org/protobuf v1.31.0
) )
require ( require (

9
go.sum
View File

@@ -1,6 +1,11 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= 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/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q= github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q=
@@ -28,6 +33,10 @@ golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

12
ioutil/file.go Normal file
View File

@@ -0,0 +1,12 @@
package ioutil
import "os"
// ReadFile 读取文件
func ReadFile(path string) []byte {
content, err := os.ReadFile(path)
if err != nil {
return nil
}
return content
}

View File

@@ -1,8 +1,14 @@
package ioutil package ioutil
import ( import (
"fmt"
"io/ioutil"
"os" "os"
"os/user"
"path/filepath" "path/filepath"
"strings"
"github.com/gobwas/glob"
) )
// GetWorkingDirPath 获取工作路径 // GetWorkingDirPath 获取工作路径
@@ -33,18 +39,6 @@ func GetAbsPath() string {
return dir return dir
} }
// PathExist 路径是否存在
func PathExist(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
// GetFileList 获取文件夹下面的所有文件的列表 // GetFileList 获取文件夹下面的所有文件的列表
func GetFileList(root string) []string { func GetFileList(root string) []string {
var files []string var files []string
@@ -75,11 +69,211 @@ func GetFolderNameList(root string) []string {
return names return names
} }
// ReadFile 读取文件 // MatchPath Returns whether a given path matches a glob pattern.
func ReadFile(path string) []byte { //
content, err := os.ReadFile(path) // via github.com/gobwas/glob:
if err != nil { //
return nil // Compile creates Glob for given pattern and strings (if any present after pattern) as separators.
// The pattern syntax is:
//
// pattern:
// { term }
//
// term:
// `*` matches any sequence of non-separator characters
// `**` matches any sequence of characters
// `?` matches any single non-separator character
// `[` [ `!` ] { character-range } `]`
// character class (must be non-empty)
// `{` pattern-list `}`
// pattern alternatives
// c matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`)
// `\` c matches character c
//
// character-range:
// c matches character c (c != `\\`, `-`, `]`)
// `\` c matches character c
// lo `-` hi matches character c for lo <= c <= hi
//
// pattern-list:
// pattern { `,` pattern }
// comma-separated (without spaces) patterns
func MatchPath(pattern string, path string) bool {
if g, err := glob.Compile(pattern); err == nil {
return g.Match(path)
}
return false
}
// ExpandUser replaces the tilde (~) in a path into the current user's home directory.
func ExpandUser(path string) (string, error) {
if u, err := user.Current(); err == nil {
fullTilde := fmt.Sprintf("~%s", u.Name)
if strings.HasPrefix(path, `~/`) || path == `~` {
return strings.Replace(path, `~`, u.HomeDir, 1), nil
}
if strings.HasPrefix(path, fullTilde+`/`) || path == fullTilde {
return strings.Replace(path, fullTilde, u.HomeDir, 1), nil
}
return path, nil
} else {
return path, err
}
}
// IsNonemptyExecutableFile Returns true if the given path is a regular file, is executable by any user, and has a non-zero size.
func IsNonemptyExecutableFile(path string) bool {
if stat, err := os.Stat(path); err == nil && stat.Size() > 0 && (stat.Mode().Perm()&0111) != 0 {
return true
}
return false
}
// IsNonemptyFile Returns true if the given path is a regular file with a non-zero size.
func IsNonemptyFile(path string) bool {
if FileExists(path) {
if stat, err := os.Stat(path); err == nil && stat.Size() > 0 {
return true
}
}
return false
}
// IsNonemptyDir Returns true if the given path is a directory with items in it.
func IsNonemptyDir(path string) bool {
if DirExists(path) {
if entries, err := ioutil.ReadDir(path); err == nil && len(entries) > 0 {
return true
}
}
return false
}
// Exists Returns true if the given path exists.
func Exists(path string) bool {
if _, err := os.Stat(path); err == nil {
return true
}
return false
}
// LinkExists Returns true if the given path exists and is a symbolic link.
func LinkExists(path string) bool {
if stat, err := os.Stat(path); err == nil {
return IsSymlink(stat.Mode())
}
return false
}
// FileExists Returns true if the given path exists and is a regular file.
func FileExists(path string) bool {
if stat, err := os.Stat(path); err == nil {
return stat.Mode().IsRegular()
}
return false
}
// DirExists Returns true if the given path exists and is a directory.
func DirExists(path string) bool {
if stat, err := os.Stat(path); err == nil {
return stat.IsDir()
}
return false
}
// PathExist 路径是否存在
func PathExist(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
func IsSymlink(mode os.FileMode) bool {
return mode&os.ModeSymlink != 0
}
func IsDevice(mode os.FileMode) bool {
return mode&os.ModeDevice != 0
}
func IsCharDevice(mode os.FileMode) bool {
return mode&os.ModeCharDevice != 0
}
func IsNamedPipe(mode os.FileMode) bool {
return mode&os.ModeNamedPipe != 0
}
func IsSocket(mode os.FileMode) bool {
return mode&os.ModeSocket != 0
}
func IsSticky(mode os.FileMode) bool {
return mode&os.ModeSticky != 0
}
func IsSetuid(mode os.FileMode) bool {
return mode&os.ModeSetuid != 0
}
func IsSetgid(mode os.FileMode) bool {
return mode&os.ModeSetgid != 0
}
func IsTemporary(mode os.FileMode) bool {
return mode&os.ModeTemporary != 0
}
func IsExclusive(mode os.FileMode) bool {
return mode&os.ModeExclusive != 0
}
func IsAppend(mode os.FileMode) bool {
return mode&os.ModeAppend != 0
}
// IsReadable Returns true if the given file can be opened for reading by the current user.
func IsReadable(filename string) bool {
if f, err := os.OpenFile(filename, os.O_RDONLY, 0); err == nil {
defer f.Close()
return true
} else {
return false
}
}
// IsWritable Returns true if the given file can be opened for writing by the current user.
func IsWritable(filename string) bool {
if f, err := os.OpenFile(filename, os.O_WRONLY, 0); err == nil {
defer f.Close()
return true
} else {
return false
}
}
// IsAppendable Returns true if the given file can be opened for appending by the current user.
func IsAppendable(filename string) bool {
if f, err := os.OpenFile(filename, os.O_APPEND, 0); err == nil {
defer f.Close()
return true
} else {
return false
} }
return content
} }

101
ioutil/path_test.go Normal file
View File

@@ -0,0 +1,101 @@
package ioutil
import (
"os"
"os/user"
"testing"
"github.com/stretchr/testify/require"
)
func TestExpandUser(t *testing.T) {
assert := require.New(t)
var v string
var err error
u, _ := user.Current()
v, err = ExpandUser(`/dev/null`)
assert.Equal(v, `/dev/null`)
assert.Nil(err)
v, err = ExpandUser(`~`)
assert.Equal(v, u.HomeDir)
assert.Nil(err)
v, err = ExpandUser("~" + u.Name)
assert.Equal(v, u.HomeDir)
assert.Nil(err)
v, err = ExpandUser("~/test-123")
assert.Equal(v, u.HomeDir+"/test-123")
assert.Nil(err)
v, err = ExpandUser("~" + u.Name + "/test-123")
assert.Equal(v, u.HomeDir+"/test-123")
assert.Nil(err)
v, err = ExpandUser("~/test-123/~/123")
assert.Equal(v, u.HomeDir+"/test-123/~/123")
assert.Nil(err)
v, err = ExpandUser("~" + u.Name + "/test-123/~" + u.Name + "/123")
assert.Equal(v, u.HomeDir+"/test-123/~"+u.Name+"/123")
assert.Nil(err)
assert.False(IsNonemptyFile(`/nonexistent.txt`))
assert.False(IsNonemptyDir(`/nonexistent/dir`))
assert.True(IsNonemptyFile(`/etc/hosts`))
assert.True(IsNonemptyDir(`/etc`))
x, err := os.Executable()
assert.NoError(err)
assert.True(IsNonemptyExecutableFile(x))
}
func TestMatchPath(t *testing.T) {
require.True(t, MatchPath(`**`, `/hello/there.txt`))
require.True(t, MatchPath(`*.txt`, `/hello/there.txt`))
require.True(t, MatchPath(`**/*.txt`, `/hello/there.txt`))
require.False(t, MatchPath(`**/*.txt`, `/hello/there.jpg`))
require.True(t, MatchPath("* ?at * eyes", "my cat has very bright eyes"))
require.True(t, MatchPath("", ""))
require.False(t, MatchPath("", "b"))
require.True(t, MatchPath("*ä", "åä"))
require.True(t, MatchPath("abc", "abc"))
require.True(t, MatchPath("a*c", "abc"))
require.True(t, MatchPath("a*c", "a12345c"))
require.True(t, MatchPath("a?c", "a1c"))
require.True(t, MatchPath("?at", "cat"))
require.True(t, MatchPath("?at", "fat"))
require.True(t, MatchPath("*", "abc"))
require.True(t, MatchPath(`\*`, "*"))
require.False(t, MatchPath("?at", "at"))
require.True(t, MatchPath("*test", "this is a test"))
require.True(t, MatchPath("this*", "this is a test"))
require.True(t, MatchPath("*is *", "this is a test"))
require.True(t, MatchPath("*is*a*", "this is a test"))
require.True(t, MatchPath("**test**", "this is a test"))
require.True(t, MatchPath("**is**a***test*", "this is a test"))
require.False(t, MatchPath("*is", "this is a test"))
require.False(t, MatchPath("*no*", "this is a test"))
require.True(t, MatchPath("[!a]*", "this is a test3"))
require.True(t, MatchPath("*abc", "abcabc"))
require.True(t, MatchPath("**abc", "abcabc"))
require.True(t, MatchPath("???", "abc"))
require.True(t, MatchPath("?*?", "abc"))
require.True(t, MatchPath("?*?", "ac"))
require.False(t, MatchPath("sta", "stagnation"))
require.True(t, MatchPath("sta*", "stagnation"))
require.False(t, MatchPath("sta?", "stagnation"))
require.False(t, MatchPath("sta?n", "stagnation"))
require.True(t, MatchPath("{abc,def}ghi", "defghi"))
require.True(t, MatchPath("{abc,abcd}a", "abcda"))
require.True(t, MatchPath("{a,ab}{bc,f}", "abc"))
require.True(t, MatchPath("{*,**}{a,b}", "ab"))
require.False(t, MatchPath("{*,**}{a,b}", "ac"))
require.True(t, MatchPath("/{rate,[a-z][a-z][a-z]}*", "/rate"))
require.True(t, MatchPath("/{rate,[0-9][0-9][0-9]}*", "/rate"))
require.True(t, MatchPath("/{rate,[a-z][a-z][a-z]}*", "/usd"))
}

View File

@@ -1,7 +1,7 @@
// This package includes utility functions for handling and manipulating a slice. // This package includes utility functions for handling and manipulating a slice.
// It draws inspiration from JavaScript and Python and uses Go generics as a basis. // It draws inspiration from JavaScript and Python and uses Go generics as a basis.
package sliceutils package sliceutil
import ( import (
"golang.org/x/exp/constraints" "golang.org/x/exp/constraints"

View File

@@ -1,4 +1,4 @@
package sliceutils_test package sliceutil_test
import ( import (
"strconv" "strconv"
@@ -6,7 +6,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tx7do/go-utils/sliceutils" "github.com/tx7do/go-utils/sliceutil"
) )
type MyInt int type MyInt int
@@ -23,7 +23,7 @@ var lastNames = []string{"Jacobs", "Vin", "Jacobs", "Smith"}
func TestFilter(t *testing.T) { func TestFilter(t *testing.T) {
expectedResult := []int{0, 2, 4, 6, 8} expectedResult := []int{0, 2, 4, 6, 8}
actualResult := sliceutils.Filter(numerals, func(value int, _ int, _ []int) bool { actualResult := sliceutil.Filter(numerals, func(value int, _ int, _ []int) bool {
return value%2 == 0 return value%2 == 0
}) })
assert.Equal(t, expectedResult, actualResult) assert.Equal(t, expectedResult, actualResult)
@@ -31,7 +31,7 @@ func TestFilter(t *testing.T) {
func TestForEach(t *testing.T) { func TestForEach(t *testing.T) {
result := 0 result := 0
sliceutils.ForEach(numerals, func(value int, _ int, _ []int) { sliceutil.ForEach(numerals, func(value int, _ int, _ []int) {
result += value result += value
}) })
assert.Equal(t, 45, result) assert.Equal(t, 45, result)
@@ -39,23 +39,23 @@ func TestForEach(t *testing.T) {
func TestMap(t *testing.T) { func TestMap(t *testing.T) {
expectedResult := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} expectedResult := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
actualResult := sliceutils.Map(numerals, func(value int, _ int, _ []int) string { actualResult := sliceutil.Map(numerals, func(value int, _ int, _ []int) string {
return strconv.Itoa(value) return strconv.Itoa(value)
}) })
assert.Equal(t, expectedResult, actualResult) assert.Equal(t, expectedResult, actualResult)
assert.Nil(t, sliceutils.Map([]int{}, func(_ int, _ int, _ []int) string { assert.Nil(t, sliceutil.Map([]int{}, func(_ int, _ int, _ []int) string {
return "" return ""
})) }))
assert.Nil(t, sliceutils.Map([]int(nil), func(_ int, _ int, _ []int) string { assert.Nil(t, sliceutil.Map([]int(nil), func(_ int, _ int, _ []int) string {
return "" return ""
})) }))
} }
func TestReduce(t *testing.T) { func TestReduce(t *testing.T) {
expectedResult := map[string]string{"result": "0123456789"} expectedResult := map[string]string{"result": "0123456789"}
actualResult := sliceutils.Reduce( actualResult := sliceutil.Reduce(
numerals, numerals,
func(acc map[string]string, cur int, _ int, _ []int) map[string]string { func(acc map[string]string, cur int, _ int, _ []int) map[string]string {
acc["result"] += strconv.Itoa(cur) acc["result"] += strconv.Itoa(cur)
@@ -68,139 +68,139 @@ func TestReduce(t *testing.T) {
func TestFind(t *testing.T) { func TestFind(t *testing.T) {
expectedResult := "Wednesday" expectedResult := "Wednesday"
actualResult := sliceutils.Find(days, func(value string, index int, slice []string) bool { actualResult := sliceutil.Find(days, func(value string, index int, slice []string) bool {
return strings.Contains(value, "Wed") return strings.Contains(value, "Wed")
}) })
assert.Equal(t, expectedResult, *actualResult) assert.Equal(t, expectedResult, *actualResult)
assert.Nil(t, sliceutils.Find(days, func(value string, index int, slice []string) bool { assert.Nil(t, sliceutil.Find(days, func(value string, index int, slice []string) bool {
return strings.Contains(value, "Rishon") return strings.Contains(value, "Rishon")
})) }))
} }
func TestFindIndex(t *testing.T) { func TestFindIndex(t *testing.T) {
expectedResult := 3 expectedResult := 3
actualResult := sliceutils.FindIndex(days, func(value string, index int, slice []string) bool { actualResult := sliceutil.FindIndex(days, func(value string, index int, slice []string) bool {
return strings.Contains(value, "Wed") return strings.Contains(value, "Wed")
}) })
assert.Equal(t, expectedResult, actualResult) assert.Equal(t, expectedResult, actualResult)
assert.Equal(t, -1, sliceutils.FindIndex(days, func(value string, index int, slice []string) bool { assert.Equal(t, -1, sliceutil.FindIndex(days, func(value string, index int, slice []string) bool {
return strings.Contains(value, "Rishon") return strings.Contains(value, "Rishon")
})) }))
} }
func TestFindIndexOf(t *testing.T) { func TestFindIndexOf(t *testing.T) {
expectedResult := 3 expectedResult := 3
actualResult := sliceutils.FindIndexOf(days, "Wednesday") actualResult := sliceutil.FindIndexOf(days, "Wednesday")
assert.Equal(t, expectedResult, actualResult) assert.Equal(t, expectedResult, actualResult)
assert.Equal(t, -1, sliceutils.FindIndexOf(days, "Rishon")) assert.Equal(t, -1, sliceutil.FindIndexOf(days, "Rishon"))
} }
func TestFindLastIndex(t *testing.T) { func TestFindLastIndex(t *testing.T) {
expectedResult := 2 expectedResult := 2
actualResult := sliceutils.FindLastIndex(lastNames, func(value string, index int, slice []string) bool { actualResult := sliceutil.FindLastIndex(lastNames, func(value string, index int, slice []string) bool {
return value == "Jacobs" return value == "Jacobs"
}) })
assert.Equal(t, expectedResult, actualResult) assert.Equal(t, expectedResult, actualResult)
assert.Equal(t, -1, sliceutils.FindLastIndex(lastNames, func(value string, index int, slice []string) bool { assert.Equal(t, -1, sliceutil.FindLastIndex(lastNames, func(value string, index int, slice []string) bool {
return value == "Hamudi" return value == "Hamudi"
})) }))
} }
func TestFindLastIndexOf(t *testing.T) { func TestFindLastIndexOf(t *testing.T) {
expectedResult := 2 expectedResult := 2
actualResult := sliceutils.FindLastIndexOf(lastNames, "Jacobs") actualResult := sliceutil.FindLastIndexOf(lastNames, "Jacobs")
assert.Equal(t, expectedResult, actualResult) assert.Equal(t, expectedResult, actualResult)
assert.Equal(t, -1, sliceutils.FindLastIndexOf(lastNames, "Hamudi")) assert.Equal(t, -1, sliceutil.FindLastIndexOf(lastNames, "Hamudi"))
} }
func TestFindIndexes(t *testing.T) { func TestFindIndexes(t *testing.T) {
expectedResult := []int{0, 2} expectedResult := []int{0, 2}
actualResult := sliceutils.FindIndexes(lastNames, func(value string, index int, slice []string) bool { actualResult := sliceutil.FindIndexes(lastNames, func(value string, index int, slice []string) bool {
return value == "Jacobs" return value == "Jacobs"
}) })
assert.Equal(t, expectedResult, actualResult) assert.Equal(t, expectedResult, actualResult)
assert.Nil(t, sliceutils.FindIndexes(lastNames, func(value string, index int, slice []string) bool { assert.Nil(t, sliceutil.FindIndexes(lastNames, func(value string, index int, slice []string) bool {
return value == "Hamudi" return value == "Hamudi"
})) }))
} }
func TestFindIndexesOf(t *testing.T) { func TestFindIndexesOf(t *testing.T) {
expectedResult := []int{0, 2} expectedResult := []int{0, 2}
actualResult := sliceutils.FindIndexesOf(lastNames, "Jacobs") actualResult := sliceutil.FindIndexesOf(lastNames, "Jacobs")
assert.Equal(t, expectedResult, actualResult) assert.Equal(t, expectedResult, actualResult)
assert.Nil(t, sliceutils.FindIndexesOf(lastNames, "Hamudi")) assert.Nil(t, sliceutil.FindIndexesOf(lastNames, "Hamudi"))
} }
func TestIncludes(t *testing.T) { func TestIncludes(t *testing.T) {
assert.True(t, sliceutils.Includes(numerals, 1)) assert.True(t, sliceutil.Includes(numerals, 1))
assert.False(t, sliceutils.Includes(numerals, 11)) assert.False(t, sliceutil.Includes(numerals, 11))
} }
func TestAny(t *testing.T) { func TestAny(t *testing.T) {
assert.True(t, sliceutils.Some(numerals, func(value int, _ int, _ []int) bool { assert.True(t, sliceutil.Some(numerals, func(value int, _ int, _ []int) bool {
return value%5 == 0 return value%5 == 0
})) }))
assert.False(t, sliceutils.Some(numerals, func(value int, _ int, _ []int) bool { assert.False(t, sliceutil.Some(numerals, func(value int, _ int, _ []int) bool {
return value == 11 return value == 11
})) }))
} }
func TestAll(t *testing.T) { func TestAll(t *testing.T) {
assert.True(t, sliceutils.Every([]int{1, 1, 1}, func(value int, _ int, _ []int) bool { assert.True(t, sliceutil.Every([]int{1, 1, 1}, func(value int, _ int, _ []int) bool {
return value == 1 return value == 1
})) }))
assert.False(t, sliceutils.Every([]int{1, 1, 1, 2}, func(value int, _ int, _ []int) bool { assert.False(t, sliceutil.Every([]int{1, 1, 1, 2}, func(value int, _ int, _ []int) bool {
return value == 1 return value == 1
})) }))
} }
func TestMerge(t *testing.T) { func TestMerge(t *testing.T) {
result := sliceutils.Merge(numerals[:5], numerals[5:]) result := sliceutil.Merge(numerals[:5], numerals[5:])
assert.Equal(t, numerals, result) assert.Equal(t, numerals, result)
assert.Nil(t, sliceutils.Merge([]int(nil), []int(nil))) assert.Nil(t, sliceutil.Merge([]int(nil), []int(nil)))
assert.Nil(t, sliceutils.Merge([]int{}, []int{})) assert.Nil(t, sliceutil.Merge([]int{}, []int{}))
} }
func TestSum(t *testing.T) { func TestSum(t *testing.T) {
result := sliceutils.Sum(numerals) result := sliceutil.Sum(numerals)
assert.Equal(t, 45, result) assert.Equal(t, 45, result)
} }
func TestSum2(t *testing.T) { func TestSum2(t *testing.T) {
result := sliceutils.Sum(numeralsWithUserDefinedType) result := sliceutil.Sum(numeralsWithUserDefinedType)
assert.Equal(t, MyInt(45), result) assert.Equal(t, MyInt(45), result)
} }
func TestRemove(t *testing.T) { func TestRemove(t *testing.T) {
testSlice := []int{1, 2, 3} testSlice := []int{1, 2, 3}
result := sliceutils.Remove(testSlice, 1) result := sliceutil.Remove(testSlice, 1)
assert.Equal(t, []int{1, 3}, result) assert.Equal(t, []int{1, 3}, result)
assert.Equal(t, []int{1, 2, 3}, testSlice) assert.Equal(t, []int{1, 2, 3}, testSlice)
result = sliceutils.Remove(result, 1) result = sliceutil.Remove(result, 1)
assert.Equal(t, []int{1}, result) assert.Equal(t, []int{1}, result)
result = sliceutils.Remove(result, 3) result = sliceutil.Remove(result, 3)
assert.Equal(t, []int{1}, result) assert.Equal(t, []int{1}, result)
result = sliceutils.Remove(result, 0) result = sliceutil.Remove(result, 0)
assert.Equal(t, []int{}, result) assert.Equal(t, []int{}, result)
result = sliceutils.Remove(result, 1) result = sliceutil.Remove(result, 1)
assert.Equal(t, []int{}, result) assert.Equal(t, []int{}, result)
} }
func TestCopy(t *testing.T) { func TestCopy(t *testing.T) {
testSlice := []int{1, 2, 3} testSlice := []int{1, 2, 3}
copiedSlice := sliceutils.Copy(testSlice) copiedSlice := sliceutil.Copy(testSlice)
copiedSlice[0] = 2 copiedSlice[0] = 2
assert.NotEqual(t, testSlice, copiedSlice) assert.NotEqual(t, testSlice, copiedSlice)
} }
func TestInsert(t *testing.T) { func TestInsert(t *testing.T) {
testSlice := []int{1, 2} testSlice := []int{1, 2}
result := sliceutils.Insert(testSlice, 0, 3) result := sliceutil.Insert(testSlice, 0, 3)
assert.Equal(t, []int{3, 1, 2}, result) assert.Equal(t, []int{3, 1, 2}, result)
assert.NotEqual(t, testSlice, result) assert.NotEqual(t, testSlice, result)
assert.Equal(t, []int{1, 3, 2}, sliceutils.Insert(testSlice, 1, 3)) assert.Equal(t, []int{1, 3, 2}, sliceutil.Insert(testSlice, 1, 3))
assert.Equal(t, []int{1, 2, 3}, sliceutils.Insert(testSlice, 2, 3)) assert.Equal(t, []int{1, 2, 3}, sliceutil.Insert(testSlice, 2, 3))
} }
func TestIntersection(t *testing.T) { func TestIntersection(t *testing.T) {
@@ -210,7 +210,7 @@ func TestIntersection(t *testing.T) {
second := []int{2, 3, 4, 5, 6} second := []int{2, 3, 4, 5, 6}
third := []int{3, 4, 5, 6, 7} third := []int{3, 4, 5, 6, 7}
assert.Equal(t, expectedResult, sliceutils.Intersection(first, second, third)) assert.Equal(t, expectedResult, sliceutil.Intersection(first, second, third))
} }
func TestDifference(t *testing.T) { func TestDifference(t *testing.T) {
@@ -220,7 +220,7 @@ func TestDifference(t *testing.T) {
second := []int{2, 3, 4, 5, 6} second := []int{2, 3, 4, 5, 6}
third := []int{3, 4, 5, 6, 7} third := []int{3, 4, 5, 6, 7}
assert.Equal(t, expectedResult, sliceutils.Difference(first, second, third)) assert.Equal(t, expectedResult, sliceutil.Difference(first, second, third))
} }
func TestUnion(t *testing.T) { func TestUnion(t *testing.T) {
@@ -230,24 +230,24 @@ func TestUnion(t *testing.T) {
second := []int{2, 3, 4, 5, 6} second := []int{2, 3, 4, 5, 6}
third := []int{3, 4, 5, 6, 7} third := []int{3, 4, 5, 6, 7}
assert.Equal(t, expectedResult, sliceutils.Union(first, second, third)) assert.Equal(t, expectedResult, sliceutil.Union(first, second, third))
} }
func TestReverse(t *testing.T) { func TestReverse(t *testing.T) {
expectedResult := []int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0} expectedResult := []int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
assert.Equal(t, expectedResult, sliceutils.Reverse(numerals)) assert.Equal(t, expectedResult, sliceutil.Reverse(numerals))
// ensure does not modify the original // ensure does not modify the original
assert.Equal(t, expectedResult, sliceutils.Reverse(numerals)) assert.Equal(t, expectedResult, sliceutil.Reverse(numerals))
// test basic odd length case // test basic odd length case
expectedResult = []int{9, 8, 7, 6, 5, 4, 3, 2, 1} expectedResult = []int{9, 8, 7, 6, 5, 4, 3, 2, 1}
assert.Equal(t, expectedResult, sliceutils.Reverse(numerals[1:])) assert.Equal(t, expectedResult, sliceutil.Reverse(numerals[1:]))
} }
func TestUnique(t *testing.T) { func TestUnique(t *testing.T) {
duplicates := []int{6, 6, 6, 9, 0, 0, 0} duplicates := []int{6, 6, 6, 9, 0, 0, 0}
expectedResult := []int{6, 9, 0} expectedResult := []int{6, 9, 0}
assert.Equal(t, expectedResult, sliceutils.Unique(duplicates)) assert.Equal(t, expectedResult, sliceutil.Unique(duplicates))
// Ensure original is unaltered // Ensure original is unaltered
assert.NotEqual(t, expectedResult, duplicates) assert.NotEqual(t, expectedResult, duplicates)
} }
@@ -255,8 +255,8 @@ func TestUnique(t *testing.T) {
func TestChunk(t *testing.T) { func TestChunk(t *testing.T) {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
assert.Equal(t, [][]int{{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}}, sliceutils.Chunk(numbers, 2)) assert.Equal(t, [][]int{{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}}, sliceutil.Chunk(numbers, 2))
assert.Equal(t, [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10}}, sliceutils.Chunk(numbers, 3)) assert.Equal(t, [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10}}, sliceutil.Chunk(numbers, 3))
} }
func TestPluck(t *testing.T) { func TestPluck(t *testing.T) {
@@ -271,10 +271,10 @@ func TestPluck(t *testing.T) {
}, },
} }
assert.Equal(t, []string{"azer", "tyuio"}, sliceutils.Pluck(items, func(item Pluckable) *string { assert.Equal(t, []string{"azer", "tyuio"}, sliceutil.Pluck(items, func(item Pluckable) *string {
return &item.Code return &item.Code
})) }))
assert.Equal(t, []string{"Azer", "Tyuio"}, sliceutils.Pluck(items, func(item Pluckable) *string { assert.Equal(t, []string{"Azer", "Tyuio"}, sliceutil.Pluck(items, func(item Pluckable) *string {
return &item.Value return &item.Value
})) }))
} }
@@ -287,15 +287,15 @@ func TestFlatten(t *testing.T) {
{9, 10, 11}, {9, 10, 11},
} }
flattened := sliceutils.Flatten(items) flattened := sliceutil.Flatten(items)
assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, flattened) assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, flattened)
assert.Nil(t, sliceutils.Flatten([][]int{})) assert.Nil(t, sliceutil.Flatten([][]int{}))
assert.Nil(t, sliceutils.Flatten([][]int(nil))) assert.Nil(t, sliceutil.Flatten([][]int(nil)))
assert.Nil(t, sliceutils.Flatten([][]int{{}, {}})) assert.Nil(t, sliceutil.Flatten([][]int{{}, {}}))
assert.Nil(t, sliceutils.Flatten([][]int{nil, nil})) assert.Nil(t, sliceutil.Flatten([][]int{nil, nil}))
assert.Nil(t, sliceutils.Flatten([][]int{{}, nil})) assert.Nil(t, sliceutil.Flatten([][]int{{}, nil}))
} }

37
structutil/structutils.go Normal file
View File

@@ -0,0 +1,37 @@
package structutil
import "reflect"
// ForEach given a struct, calls the passed in function with each visible struct field's name, value and tag.
func ForEach[T any](structInstance T, function func(key string, value any, tag reflect.StructTag)) {
typeOf := reflect.TypeOf(structInstance)
valueOf := reflect.ValueOf(structInstance)
fields := reflect.VisibleFields(typeOf)
for _, field := range fields {
value := valueOf.FieldByName(field.Name)
function(field.Name, value.Interface(), field.Tag)
}
}
// ToMap given a struct, converts it to a map[string]any.
// This function also takes struct tag names as optional parameters - if passed in, the struct tags will be used to remap or omit values.
func ToMap[T any](structInstance T, structTags ...string) map[string]any {
output := make(map[string]any, 0)
ForEach(structInstance, func(key string, value any, tag reflect.StructTag) {
if tag != "" {
for _, s := range structTags {
if tagValue, isPresent := tag.Lookup(s); isPresent && tagValue != "" {
key = tagValue
break
}
}
}
if key != "-" {
output[key] = value
}
})
return output
}

View File

@@ -0,0 +1,55 @@
package structutil_test
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tx7do/go-utils/structutil"
)
type TestStruct struct {
First string
Second int
Third bool `struct:"third"`
}
func TestForEach(t *testing.T) {
instance := TestStruct{
"moishe",
22,
true,
}
structutil.ForEach(instance, func(key string, value any, tag reflect.StructTag) {
switch key {
case "First":
assert.Equal(t, "moishe", value.(string))
assert.Zero(t, tag)
case "Second":
assert.Equal(t, 22, value.(int))
assert.Zero(t, tag)
case "Third":
assert.Equal(t, true, value.(bool))
assert.Equal(t, "third", tag.Get("struct"))
}
})
}
func TestToMap(t *testing.T) {
instance := TestStruct{
"moishe",
22,
true,
}
assert.Equal(t, map[string]any{
"First": "moishe",
"Second": 22,
"Third": true,
}, structutil.ToMap(instance))
assert.Equal(t, map[string]any{
"First": "moishe",
"Second": 22,
"third": true,
}, structutil.ToMap(instance, "struct"))
}

View File

@@ -1,6 +1,6 @@
git tag v1.1.5 git tag v1.1.8
git tag bank_card/v1.1.0 git tag bank_card/v1.1.0
git tag entgo/v1.1.6 git tag entgo/v1.1.11
git tag geoip/v1.1.0 git tag geoip/v1.1.0
git push origin --tags git push origin --tags

View File

@@ -1,5 +1,7 @@
package util package util
import "time"
const ( const (
DateLayout = "2006-01-02" DateLayout = "2006-01-02"
ClockLayout = "15:04:05" ClockLayout = "15:04:05"
@@ -7,3 +9,5 @@ const (
DefaultTimeLocationName = "Asia/Shanghai" DefaultTimeLocationName = "Asia/Shanghai"
) )
var ReferenceTimeValue time.Time = time.Date(2006, 1, 2, 15, 4, 5, 999999999, time.FixedZone("MST", -7*60*60))

45
timeutil/format.go Normal file
View File

@@ -0,0 +1,45 @@
package util
import (
"fmt"
"strings"
"time"
)
// ReferenceTime Return the standard Golang reference time (2006-01-02T15:04:05.999999999Z07:00)
func ReferenceTime() time.Time {
return ReferenceTimeValue
}
// FormatTimer Formats the given duration in a colon-separated timer format in the form
// [HH:]MM:SS.
func FormatTimer(d time.Duration) string {
h, m, s := DurationHMS(d)
out := fmt.Sprintf("%02d:%02d:%02d", h, m, s)
out = strings.TrimPrefix(out, `00:`)
out = strings.TrimPrefix(out, `0`)
return out
}
// FormatTimerf Formats the given duration using the given format string. The string follows
// the same formatting rules as described in the fmt package, and will receive
// three integer arguments: hours, minutes, and seconds.
func FormatTimerf(format string, d time.Duration) string {
h, m, s := DurationHMS(d)
out := fmt.Sprintf(format, h, m, s)
return out
}
// DurationHMS Extracts the hours, minutes, and seconds from the given duration.
func DurationHMS(d time.Duration) (int, int, int) {
d = d.Round(time.Second)
h := d / time.Hour
d -= h * time.Hour
m := d / time.Minute
d -= m * time.Minute
s := d / time.Second
return int(h), int(m), int(s)
}