Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
902a0ce860 | ||
|
|
576d1bfe2b | ||
|
|
4e084cc4b5 | ||
|
|
ce853c6b87 | ||
|
|
d5819c6ef4 | ||
|
|
4d6b87fae6 | ||
|
|
df9f3f4430 | ||
|
|
871b028e34 | ||
|
|
dcbdf4b7a6 | ||
|
|
c04968803a | ||
|
|
093172ebca | ||
|
|
d65e7bb928 | ||
|
|
78452b1abf | ||
|
|
95ce578ddf | ||
|
|
413e14ac78 | ||
|
|
50a2e139eb | ||
|
|
18755155ba | ||
|
|
da82d442cc | ||
|
|
83bb818ade | ||
|
|
c37d726b4c | ||
|
|
b27c96f932 | ||
|
|
836f4e2461 | ||
|
|
5717d4aefe | ||
|
|
62142a6836 | ||
|
|
9990fa43e0 | ||
|
|
82fbdc15d9 | ||
|
|
b4188ca4d8 | ||
|
|
e9ea8fa536 | ||
|
|
6ed25e948c | ||
|
|
bfea578907 | ||
|
|
b8a6f7f7d8 | ||
|
|
149ded5d4e | ||
|
|
60c0477999 | ||
|
|
ef3aa38cb2 | ||
|
|
498bc3ea18 | ||
|
|
1c225465de |
26
.gitignore
vendored
26
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/mattn/go-sqlite3"
|
"github.com/mattn/go-sqlite3"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
|
||||||
"github.com/tx7do/kratos-utils/bank_card/assets"
|
"github.com/tx7do/go-utils/bank_card/assets"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Database struct {
|
type Database struct {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
module github.com/tx7do/kratos-utils/bank_card
|
module github.com/tx7do/go-utils/bank_card
|
||||||
|
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/mattn/go-sqlite3 v1.14.17
|
github.com/mattn/go-sqlite3 v1.14.22
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.9.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -12,3 +12,5 @@ require (
|
|||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/tx7do/go-utils => ../
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
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/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
44
byteutil/util.go
Normal file
44
byteutil/util.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package byteutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IntToBytes 将int转换为[]byte
|
||||||
|
func IntToBytes(n int) []byte {
|
||||||
|
data := int64(n)
|
||||||
|
byteBuf := bytes.NewBuffer([]byte{})
|
||||||
|
_ = binary.Write(byteBuf, binary.BigEndian, data)
|
||||||
|
return byteBuf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesToInt 将[]byte转换为int
|
||||||
|
func BytesToInt(bys []byte) int {
|
||||||
|
byteBuf := bytes.NewBuffer(bys)
|
||||||
|
var data int64
|
||||||
|
_ = binary.Read(byteBuf, binary.BigEndian, &data)
|
||||||
|
return int(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteToLower lowers a byte
|
||||||
|
func ByteToLower(b byte) byte {
|
||||||
|
if b <= '\u007F' {
|
||||||
|
if 'A' <= b && b <= 'Z' {
|
||||||
|
b += 'a' - 'A'
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteToUpper upper a byte
|
||||||
|
func ByteToUpper(b byte) byte {
|
||||||
|
if b <= '\u007F' {
|
||||||
|
if 'a' <= b && b <= 'z' {
|
||||||
|
b -= 'a' - 'A'
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
11
byteutil/util_test.go
Normal file
11
byteutil/util_test.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package byteutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIntToBytes(t *testing.T) {
|
||||||
|
fmt.Println(IntToBytes(1))
|
||||||
|
fmt.Println(BytesToInt(IntToBytes(1)))
|
||||||
|
}
|
||||||
32
dateutil/dateutils.go
Normal file
32
dateutil/dateutils.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package dateutil
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Floor - takes a datetime and return a datetime from the same day at 00:00:00 (UTC).
|
||||||
|
func Floor(t time.Time) time.Time {
|
||||||
|
return t.UTC().Truncate(time.Hour * 24)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ceil - takes a datetime and return a datetime from the same day at 23:59:59 (UTC).
|
||||||
|
func Ceil(t time.Time) time.Time {
|
||||||
|
// add 24 hours so that we are dealing with tomorrow's datetime
|
||||||
|
// Floor
|
||||||
|
// Substract one second and we have today at 23:59:59
|
||||||
|
return Floor(t.Add(time.Hour * 24)).Add(time.Second * -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BeforeOrEqual(milestone time.Time, date time.Time) bool {
|
||||||
|
return date.UTC().Before(milestone) || date.UTC().Equal(milestone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AfterOrEqual(milestone time.Time, date time.Time) bool {
|
||||||
|
return date.UTC().After(milestone) || date.UTC().Equal(milestone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overlap - returns true if two date intervals overlap.
|
||||||
|
func Overlap(start1 time.Time, end1 time.Time, start2 time.Time, end2 time.Time) bool {
|
||||||
|
return (AfterOrEqual(start2, start1) && BeforeOrEqual(end2, start1)) ||
|
||||||
|
(AfterOrEqual(start2, end1) && BeforeOrEqual(end2, end1)) ||
|
||||||
|
(AfterOrEqual(start1, start2) && BeforeOrEqual(end1, start2)) ||
|
||||||
|
(AfterOrEqual(start1, end2) && BeforeOrEqual(end1, end2))
|
||||||
|
}
|
||||||
66
dateutil/dateutils_test.go
Normal file
66
dateutil/dateutils_test.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package dateutil_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tx7do/go-utils/dateutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFloor(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
assert.Equal(t, "00:00:00", dateutil.Floor(now).Format("15:04:05"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCeil(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
assert.Equal(t, "23:59:59", dateutil.Ceil(now).Format("15:04:05"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBeforeOrEqual(t *testing.T) {
|
||||||
|
milestone, _ := time.Parse("2006-01-02", "2023-01-01")
|
||||||
|
|
||||||
|
dBefore, _ := time.Parse("2006-01-02", "2022-12-31")
|
||||||
|
dEqual, _ := time.Parse("2006-01-02", "2023-01-01")
|
||||||
|
dAfter, _ := time.Parse("2006-01-02", "2023-01-31")
|
||||||
|
|
||||||
|
assert.Equal(t, true, dateutil.BeforeOrEqual(milestone, dBefore))
|
||||||
|
assert.Equal(t, true, dateutil.BeforeOrEqual(milestone, dEqual))
|
||||||
|
assert.Equal(t, false, dateutil.BeforeOrEqual(milestone, dAfter))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAfterOrEqual(t *testing.T) {
|
||||||
|
milestone, _ := time.Parse("2006-01-02", "2023-01-01")
|
||||||
|
|
||||||
|
dBefore, _ := time.Parse("2006-01-02", "2022-12-31")
|
||||||
|
dEqual, _ := time.Parse("2006-01-02", "2023-01-01")
|
||||||
|
dAfter, _ := time.Parse("2006-01-02", "2023-01-31")
|
||||||
|
|
||||||
|
assert.Equal(t, false, dateutil.AfterOrEqual(milestone, dBefore))
|
||||||
|
assert.Equal(t, true, dateutil.AfterOrEqual(milestone, dEqual))
|
||||||
|
assert.Equal(t, true, dateutil.AfterOrEqual(milestone, dAfter))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverlap(t *testing.T) {
|
||||||
|
s1, _ := time.Parse("2006-01-02", "2022-12-28")
|
||||||
|
e1, _ := time.Parse("2006-01-02", "2022-12-31")
|
||||||
|
|
||||||
|
s2, _ := time.Parse("2006-01-02", "2022-12-30")
|
||||||
|
e2, _ := time.Parse("2006-01-02", "2023-01-01")
|
||||||
|
|
||||||
|
s3, _ := time.Parse("2006-01-02", "2023-01-02")
|
||||||
|
e3, _ := time.Parse("2006-01-02", "2023-01-04")
|
||||||
|
|
||||||
|
assert.Equal(t, true, dateutil.Overlap(s1, e1, s2, e2))
|
||||||
|
assert.Equal(t, false, dateutil.Overlap(s1, e1, s3, e3))
|
||||||
|
|
||||||
|
s4, _ := time.Parse("2006-01-02", "2023-07-13")
|
||||||
|
e4, _ := time.Parse("2006-01-02", "2023-07-14")
|
||||||
|
|
||||||
|
s5, _ := time.Parse("2006-01-02", "2023-07-10")
|
||||||
|
e5, _ := time.Parse("2006-01-02", "2023-07-17")
|
||||||
|
|
||||||
|
assert.Equal(t, true, dateutil.Overlap(s4, e4, s5, e5))
|
||||||
|
assert.Equal(t, true, dateutil.Overlap(s5, e5, s4, e4))
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package entgo
|
package entgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"entgo.io/ent/dialect/sql"
|
entSql "entgo.io/ent/dialect/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
type entClientInterface interface {
|
type entClientInterface interface {
|
||||||
@@ -14,10 +16,10 @@ type entClientInterface interface {
|
|||||||
|
|
||||||
type EntClient[T entClientInterface] struct {
|
type EntClient[T entClientInterface] struct {
|
||||||
db T
|
db T
|
||||||
drv *sql.Driver
|
drv *entSql.Driver
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEntClient[T entClientInterface](db T, drv *sql.Driver) *EntClient[T] {
|
func NewEntClient[T entClientInterface](db T, drv *entSql.Driver) *EntClient[T] {
|
||||||
return &EntClient[T]{
|
return &EntClient[T]{
|
||||||
db: db,
|
db: db,
|
||||||
drv: drv,
|
drv: drv,
|
||||||
@@ -28,17 +30,29 @@ func (c *EntClient[T]) Client() T {
|
|||||||
return c.db
|
return c.db
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EntClient[T]) Driver() *sql.Driver {
|
func (c *EntClient[T]) Driver() *entSql.Driver {
|
||||||
return c.drv
|
return c.drv
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EntClient[T]) Close() {
|
func (c *EntClient[T]) DB() *sql.DB {
|
||||||
_ = c.db.Close()
|
return c.drv.DB()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *EntClient[T]) Close() error {
|
||||||
|
return c.db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *EntClient[T]) Query(ctx context.Context, query string, args, v any) error {
|
||||||
|
return c.Driver().Query(ctx, query, args, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *EntClient[T]) Exec(ctx context.Context, query string, args, v any) error {
|
||||||
|
return c.Driver().Exec(ctx, query, args, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateDriver 创建数据库驱动
|
// CreateDriver 创建数据库驱动
|
||||||
func CreateDriver(driverName, dsn string, maxIdleConnections, maxOpenConnections int, connMaxLifetime time.Duration) (*sql.Driver, error) {
|
func CreateDriver(driverName, dsn string, maxIdleConnections, maxOpenConnections int, connMaxLifetime time.Duration) (*entSql.Driver, error) {
|
||||||
drv, err := sql.Open(driverName, dsn)
|
drv, err := entSql.Open(driverName, dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(fmt.Sprintf("failed opening connection to db: %v", err))
|
return nil, errors.New(fmt.Sprintf("failed opening connection to db: %v", err))
|
||||||
}
|
}
|
||||||
|
|||||||
29
entgo/go.mod
29
entgo/go.mod
@@ -1,18 +1,20 @@
|
|||||||
module github.com/tx7do/kratos-utils/entgo
|
module github.com/tx7do/go-utils/entgo
|
||||||
|
|
||||||
go 1.20
|
go 1.21
|
||||||
|
|
||||||
|
toolchain go1.22.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
entgo.io/contrib v0.4.5
|
entgo.io/contrib v0.4.5
|
||||||
entgo.io/ent v0.12.4
|
entgo.io/ent v0.13.1
|
||||||
github.com/go-kratos/kratos/v2 v2.7.1
|
github.com/go-kratos/kratos/v2 v2.7.3
|
||||||
github.com/google/uuid v1.3.1
|
github.com/google/uuid v1.6.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/tx7do/kratos-utils v0.0.0-20231025044346-36a096df76d8
|
github.com/tx7do/go-utils v1.1.9
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
ariga.io/atlas v0.15.0 // indirect
|
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 // indirect
|
||||||
github.com/agext/levenshtein v1.2.3 // indirect
|
github.com/agext/levenshtein v1.2.3 // indirect
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||||
github.com/bufbuild/protocompile v0.6.0 // indirect
|
github.com/bufbuild/protocompile v0.6.0 // indirect
|
||||||
@@ -25,12 +27,15 @@ require (
|
|||||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/sony/sonyflake v1.2.0 // indirect
|
||||||
github.com/zclconf/go-cty v1.14.1 // indirect
|
github.com/zclconf/go-cty v1.14.1 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/mod v0.13.0 // indirect
|
golang.org/x/mod v0.15.0 // indirect
|
||||||
golang.org/x/sys v0.13.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
golang.org/x/text v0.13.0 // indirect
|
golang.org/x/text v0.13.0 // indirect
|
||||||
golang.org/x/tools v0.14.0 // indirect
|
golang.org/x/tools v0.18.0 // indirect
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/tx7do/go-utils => ../
|
||||||
|
|||||||
59
entgo/go.sum
59
entgo/go.sum
@@ -1,10 +1,11 @@
|
|||||||
ariga.io/atlas v0.15.0 h1:9lwSVcO/D3WgaCzstSGqR1hEDtsGibu6JqUofEI/0sY=
|
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 h1:GwdJbXydHCYPedeeLt4x/lrlIISQ4JTH1mRWuE5ZZ14=
|
||||||
ariga.io/atlas v0.15.0/go.mod h1:isZrlzJ5cpoCoKFoY9knZug7Lq4pP1cm8g3XciLZ0Pw=
|
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43/go.mod h1:uj3pm+hUTVN/X5yfdBexHlZv+1Xu5u5ZbZx7+CDavNU=
|
||||||
entgo.io/contrib v0.4.5 h1:BFaOHwFLE8WZjVJadP0XHCIaxgcC1BAtUvAyw7M/GHk=
|
entgo.io/contrib v0.4.5 h1:BFaOHwFLE8WZjVJadP0XHCIaxgcC1BAtUvAyw7M/GHk=
|
||||||
entgo.io/contrib v0.4.5/go.mod h1:wpZyq2DJgthugFvDBlaqMXj9mV4/9ebyGEn7xlTVQqE=
|
entgo.io/contrib v0.4.5/go.mod h1:wpZyq2DJgthugFvDBlaqMXj9mV4/9ebyGEn7xlTVQqE=
|
||||||
entgo.io/ent v0.12.4 h1:LddPnAyxls/O7DTXZvUGDj0NZIdGSu317+aoNLJWbD8=
|
entgo.io/ent v0.13.1 h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE=
|
||||||
entgo.io/ent v0.12.4/go.mod h1:Y3JVAjtlIk8xVZYSn3t3mf8xlZIn5SAOXZQxD6kKI+Q=
|
entgo.io/ent v0.13.1/go.mod h1:qCEmo+biw3ccBn9OyL4ZK5dfpwg++l1Gxwac5B1206A=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||||
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
||||||
@@ -13,27 +14,32 @@ github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28
|
|||||||
github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE=
|
github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE=
|
||||||
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/go-kratos/kratos/v2 v2.7.1 h1:PNMUaWxS5ZGDp1EyID5ZosJb1OA/YiHnBxB0yUmocnc=
|
github.com/go-kratos/kratos/v2 v2.7.3 h1:T9MS69qk4/HkVUuHw5GS9PDVnOfzn+kxyF0CL5StqxA=
|
||||||
github.com/go-kratos/kratos/v2 v2.7.1/go.mod h1:CPn82O93OLHjtnbuyOKhAG5TkSvw+mFnL32c4lZFDwU=
|
github.com/go-kratos/kratos/v2 v2.7.3/go.mod h1:CQZ7V0qyVPwrotIpS5VNNUJNzEbcyRUl5pRtxLOIvn4=
|
||||||
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
|
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
|
||||||
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
|
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
|
||||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||||
|
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI=
|
github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI=
|
||||||
github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
|
github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
|
||||||
github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls=
|
github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls=
|
||||||
github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k=
|
github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
@@ -41,34 +47,41 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
|
|||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
|
||||||
github.com/tx7do/kratos-utils v0.0.0-20231025044346-36a096df76d8 h1:G+s0DQ6zI0dGcvd9tRpXcYzelO0xRnp2jpFtQ+697Ko=
|
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
|
||||||
github.com/tx7do/kratos-utils v0.0.0-20231025044346-36a096df76d8/go.mod h1:rTeeqeABjK9HamIwKZ+uLozVIk/hGiEbNsi6rVr4l0w=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA=
|
github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA=
|
||||||
github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
|
||||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||||
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
|
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
|
||||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 h1:9JucMWR7sPvCxUFd6UsOUNmA5kCcWOfORaT3tpAsKQs=
|
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 h1:9JucMWR7sPvCxUFd6UsOUNmA5kCcWOfORaT3tpAsKQs=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 h1:DEH99RbiLZhMxrpEJCZ0A+wdTe0EOgou/poSLx9vWf4=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 h1:DEH99RbiLZhMxrpEJCZ0A+wdTe0EOgou/poSLx9vWf4=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||||
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
||||||
|
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
21
entgo/mixin/creator_id.go
Normal file
21
entgo/mixin/creator_id.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package mixin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"entgo.io/ent"
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
_mixin "entgo.io/ent/schema/mixin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreatorId struct {
|
||||||
|
_mixin.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func (CreatorId) Fields() []ent.Field {
|
||||||
|
return []ent.Field{
|
||||||
|
field.Uint64("creator_id").
|
||||||
|
Comment("创建者用户ID").
|
||||||
|
Immutable().
|
||||||
|
Optional().
|
||||||
|
Nillable(),
|
||||||
|
}
|
||||||
|
}
|
||||||
37
entgo/mixin/snowflake_id.go
Normal file
37
entgo/mixin/snowflake_id.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package mixin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"entgo.io/ent"
|
||||||
|
"entgo.io/ent/dialect"
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
"entgo.io/ent/schema/index"
|
||||||
|
"entgo.io/ent/schema/mixin"
|
||||||
|
|
||||||
|
"github.com/tx7do/go-utils/sonyflake"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SnowflackId struct {
|
||||||
|
mixin.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SnowflackId) Fields() []ent.Field {
|
||||||
|
return []ent.Field{
|
||||||
|
field.Uint64("id").
|
||||||
|
Comment("id").
|
||||||
|
DefaultFunc(sonyflake.GenerateSonyflake).
|
||||||
|
Positive().
|
||||||
|
Immutable().
|
||||||
|
StructTag(`json:"id,omitempty"`).
|
||||||
|
SchemaType(map[string]string{
|
||||||
|
dialect.MySQL: "bigint",
|
||||||
|
dialect.Postgres: "bigint",
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indexes of the SnowflackId.
|
||||||
|
func (SnowflackId) Indexes() []ent.Index {
|
||||||
|
return []ent.Index{
|
||||||
|
index.Fields("id"),
|
||||||
|
}
|
||||||
|
}
|
||||||
33
entgo/mixin/string_id.go
Normal file
33
entgo/mixin/string_id.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package mixin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"entgo.io/ent"
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
"entgo.io/ent/schema/index"
|
||||||
|
"entgo.io/ent/schema/mixin"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StringId struct {
|
||||||
|
mixin.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func (StringId) Fields() []ent.Field {
|
||||||
|
return []ent.Field{
|
||||||
|
field.String("id").
|
||||||
|
Comment("id").
|
||||||
|
MaxLen(25).
|
||||||
|
NotEmpty().
|
||||||
|
Unique().
|
||||||
|
Immutable().
|
||||||
|
Match(regexp.MustCompile("^[0-9a-zA-Z_\\-]+$")).
|
||||||
|
StructTag(`json:"id,omitempty"`),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indexes of the StringId.
|
||||||
|
func (StringId) Indexes() []ent.Index {
|
||||||
|
return []ent.Index{
|
||||||
|
index.Fields("id"),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ func (SwitchStatus) Fields() []ent.Field {
|
|||||||
// dialect.MySQL: "switch_status",
|
// dialect.MySQL: "switch_status",
|
||||||
// dialect.Postgres: "switch_status",
|
// dialect.Postgres: "switch_status",
|
||||||
//}).
|
//}).
|
||||||
|
Default("ON").
|
||||||
Values(
|
Values(
|
||||||
"OFF",
|
"OFF",
|
||||||
"ON",
|
"ON",
|
||||||
|
|||||||
@@ -69,11 +69,11 @@ func (TimeAt) Fields() []ent.Field {
|
|||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
var _ ent.Mixin = (*CreatedTime)(nil)
|
var _ ent.Mixin = (*CreateTime)(nil)
|
||||||
|
|
||||||
type CreatedTime struct{ mixin.Schema }
|
type CreateTime struct{ mixin.Schema }
|
||||||
|
|
||||||
func (CreatedTime) Fields() []ent.Field {
|
func (CreateTime) Fields() []ent.Field {
|
||||||
return []ent.Field{
|
return []ent.Field{
|
||||||
// 创建时间
|
// 创建时间
|
||||||
field.Time("create_time").
|
field.Time("create_time").
|
||||||
@@ -86,11 +86,11 @@ func (CreatedTime) Fields() []ent.Field {
|
|||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
var _ ent.Mixin = (*UpdatedTime)(nil)
|
var _ ent.Mixin = (*UpdateTime)(nil)
|
||||||
|
|
||||||
type UpdatedTime struct{ mixin.Schema }
|
type UpdateTime struct{ mixin.Schema }
|
||||||
|
|
||||||
func (UpdatedTime) Fields() []ent.Field {
|
func (UpdateTime) Fields() []ent.Field {
|
||||||
return []ent.Field{
|
return []ent.Field{
|
||||||
// 更新时间
|
// 更新时间
|
||||||
field.Time("update_time").
|
field.Time("update_time").
|
||||||
@@ -102,11 +102,11 @@ func (UpdatedTime) Fields() []ent.Field {
|
|||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
var _ ent.Mixin = (*DeletedTime)(nil)
|
var _ ent.Mixin = (*DeleteTime)(nil)
|
||||||
|
|
||||||
type DeletedTime struct{ mixin.Schema }
|
type DeleteTime struct{ mixin.Schema }
|
||||||
|
|
||||||
func (DeletedTime) Fields() []ent.Field {
|
func (DeleteTime) Fields() []ent.Field {
|
||||||
return []ent.Field{
|
return []ent.Field{
|
||||||
// 删除时间
|
// 删除时间
|
||||||
field.Time("delete_time").
|
field.Time("delete_time").
|
||||||
@@ -122,9 +122,9 @@ type Time struct{ mixin.Schema }
|
|||||||
|
|
||||||
func (Time) Fields() []ent.Field {
|
func (Time) Fields() []ent.Field {
|
||||||
var fields []ent.Field
|
var fields []ent.Field
|
||||||
fields = append(fields, CreatedTime{}.Fields()...)
|
fields = append(fields, CreateTime{}.Fields()...)
|
||||||
fields = append(fields, UpdatedTime{}.Fields()...)
|
fields = append(fields, UpdateTime{}.Fields()...)
|
||||||
fields = append(fields, DeletedTime{}.Fields()...)
|
fields = append(fields, DeleteTime{}.Fields()...)
|
||||||
return fields
|
return fields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package mixin
|
package mixin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"entgo.io/ent"
|
"entgo.io/ent"
|
||||||
"entgo.io/ent/schema/field"
|
"entgo.io/ent/schema/field"
|
||||||
"entgo.io/ent/schema/mixin"
|
"entgo.io/ent/schema/mixin"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -74,3 +75,71 @@ func (Timestamp) Fields() []ent.Field {
|
|||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
var _ ent.Mixin = (*CreatedAtTimestamp)(nil)
|
||||||
|
|
||||||
|
type CreatedAtTimestamp struct{ mixin.Schema }
|
||||||
|
|
||||||
|
func (CreatedAtTimestamp) Fields() []ent.Field {
|
||||||
|
return []ent.Field{
|
||||||
|
// 创建时间,毫秒
|
||||||
|
field.Int64("created_at").
|
||||||
|
Comment("创建时间").
|
||||||
|
Immutable().
|
||||||
|
Optional().
|
||||||
|
Nillable().
|
||||||
|
DefaultFunc(time.Now().UnixMilli),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
var _ ent.Mixin = (*UpdatedAtTimestamp)(nil)
|
||||||
|
|
||||||
|
type UpdatedAtTimestamp struct{ mixin.Schema }
|
||||||
|
|
||||||
|
func (UpdatedAtTimestamp) Fields() []ent.Field {
|
||||||
|
return []ent.Field{
|
||||||
|
// 更新时间,毫秒
|
||||||
|
// 需要注意的是,如果不是程序自动更新,那么这个字段不会被更新,除非在数据库里面下触发器更新。
|
||||||
|
field.Int64("updated_at").
|
||||||
|
Comment("更新时间").
|
||||||
|
Optional().
|
||||||
|
Nillable().
|
||||||
|
UpdateDefault(time.Now().UnixMilli),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
var _ ent.Mixin = (*DeletedAtTimestamp)(nil)
|
||||||
|
|
||||||
|
type DeletedAtTimestamp struct{ mixin.Schema }
|
||||||
|
|
||||||
|
func (DeletedAtTimestamp) Fields() []ent.Field {
|
||||||
|
return []ent.Field{
|
||||||
|
// 删除时间,毫秒
|
||||||
|
field.Int64("deleted_at").
|
||||||
|
Comment("删除时间").
|
||||||
|
Optional().
|
||||||
|
Nillable(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
var _ ent.Mixin = (*TimestampAt)(nil)
|
||||||
|
|
||||||
|
type TimestampAt struct{ mixin.Schema }
|
||||||
|
|
||||||
|
func (TimestampAt) Fields() []ent.Field {
|
||||||
|
var fields []ent.Field
|
||||||
|
fields = append(fields, CreatedAtTimestamp{}.Fields()...)
|
||||||
|
fields = append(fields, UpdatedAtTimestamp{}.Fields()...)
|
||||||
|
fields = append(fields, DeletedAtTimestamp{}.Fields()...)
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@@ -2,14 +2,15 @@
|
|||||||
|
|
||||||
## 通用列表查询请求
|
## 通用列表查询请求
|
||||||
|
|
||||||
| 字段名 | 类型 | 格式 | 字段描述 | 示例 | 备注 |
|
| 字段名 | 类型 | 格式 | 字段描述 | 示例 | 备注 |
|
||||||
|----------|-----------|-------------------------------------|---------|----------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
|
|-----------|-----------|-------------------------------------|---------|----------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
|
||||||
| page | `number` | | 当前页码 | | 默认为`1`,最小值为`1`。 |
|
| page | `number` | | 当前页码 | | 默认为`1`,最小值为`1`。 |
|
||||||
| pageSize | `number` | | 每页的行数 | | 默认为`10`,最小值为`1`。 |
|
| pageSize | `number` | | 每页的行数 | | 默认为`10`,最小值为`1`。 |
|
||||||
| query | `string` | `json object` 或 `json object array` | AND过滤条件 | json字符串: `{"field1":"val1","field2":"val2"}` 或者`[{"field1":"val1"},{"field1":"val2"},{"field2":"val2"}]` | `map`和`array`都支持,当需要同字段名,不同值的情况下,请使用`array`。具体规则请见:[过滤规则](#过滤规则) |
|
| query | `string` | `json object` 或 `json object array` | AND过滤条件 | json字符串: `{"field1":"val1","field2":"val2"}` 或者`[{"field1":"val1"},{"field1":"val2"},{"field2":"val2"}]` | `map`和`array`都支持,当需要同字段名,不同值的情况下,请使用`array`。具体规则请见:[过滤规则](#过滤规则) |
|
||||||
| or | `string` | `json object` 或 `json object array` | OR过滤条件 | 同 AND过滤条件 | |
|
| or | `string` | `json object` 或 `json object array` | OR过滤条件 | 同 AND过滤条件 | |
|
||||||
| orderBy | `string` | `json string array` | 排序条件 | json字符串:`["-create_time", "type"]` | json的`string array`,字段名前加`-`是为降序,不加为升序。具体规则请见:[排序规则](#排序规则) |
|
| orderBy | `string` | `json string array` | 排序条件 | json字符串:`["-create_time", "type"]` | json的`string array`,字段名前加`-`是为降序,不加为升序。具体规则请见:[排序规则](#排序规则) |
|
||||||
| nopaging | `boolean` | | 是否不分页 | | 此字段为`true`时,`page`、`pageSize`字段的传入将无效用。 |
|
| nopaging | `boolean` | | 是否不分页 | | 此字段为`true`时,`page`、`pageSize`字段的传入将无效用。 |
|
||||||
|
| fieldMask | `string` | `json string array` | 字段掩码 | | 此字段是`SELECT`条件,为空的时候是为`*`。 |
|
||||||
|
|
||||||
## 排序规则
|
## 排序规则
|
||||||
|
|
||||||
@@ -37,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 | | | |
|
||||||
|
|
||||||
以及将日期提取出来的查找类型:
|
以及将日期提取出来的查找类型:
|
||||||
|
|
||||||
|
|||||||
@@ -2,55 +2,123 @@ package entgo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"entgo.io/ent/dialect"
|
||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/encoding"
|
"github.com/go-kratos/kratos/v2/encoding"
|
||||||
|
|
||||||
"github.com/tx7do/kratos-utils/stringcase"
|
"github.com/tx7do/go-utils/stringcase"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
type FilterOp int
|
||||||
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" // 全文搜索
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FilterDatePartDate = "date" // 日期
|
FilterNot DatePart = iota // 不等于
|
||||||
FilterDatePartYear = "year" // 年
|
FilterIn // 检查值是否在列表中
|
||||||
FilterDatePartISOYear = "iso_year" // ISO 8601 一年中的周数
|
FilterNotIn // 不在列表中
|
||||||
FilterDatePartQuarter = "quarter" // 季度
|
FilterGTE // 大于或等于传递的值
|
||||||
FilterDatePartMonth = "month" // 月
|
FilterGT // 大于传递值
|
||||||
FilterDatePartWeek = "week" // ISO 8601 周编号 一年中的周数
|
FilterLTE // 小于或等于传递值
|
||||||
FilterDatePartWeekDay = "week_day" // 星期几
|
FilterLT // 小于传递值
|
||||||
FilterDatePartISOWeekDay = "iso_week_day" // 星期几
|
FilterRange // 是否介于和给定的两个值之间
|
||||||
FilterDatePartDay = "day" // 日
|
FilterIsNull // 是否为空
|
||||||
FilterDatePartTime = "time" // 小时:分钟:秒
|
FilterNotIsNull // 是否不为空
|
||||||
FilterDatePartHour = "hour" // 小时
|
FilterContains // 是否包含指定的子字符串
|
||||||
FilterDatePartMinute = "minute" // 分钟
|
FilterInsensitiveContains // 不区分大小写,是否包含指定的子字符串
|
||||||
FilterDatePartSecond = "second" // 秒
|
FilterStartsWith // 以值开头
|
||||||
FilterDatePartMicrosecond = "microsecond" // 微秒
|
FilterInsensitiveStartsWith // 不区分大小写,以值开头
|
||||||
|
FilterEndsWith // 以值结尾
|
||||||
|
FilterInsensitiveEndsWith // 不区分大小写,以值结尾
|
||||||
|
FilterExact // 精确匹配
|
||||||
|
FilterInsensitiveExact // 不区分大小写,精确匹配
|
||||||
|
FilterRegex // 正则表达式
|
||||||
|
FilterInsensitiveRegex // 不区分大小写,正则表达式
|
||||||
|
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
|
||||||
|
|
||||||
|
const (
|
||||||
|
DatePartDate DatePart = iota // 日期
|
||||||
|
DatePartYear // 年
|
||||||
|
DatePartISOYear // ISO 8601 一年中的周数
|
||||||
|
DatePartQuarter // 季度
|
||||||
|
DatePartMonth // 月
|
||||||
|
DatePartWeek // ISO 8601 周编号 一年中的周数
|
||||||
|
DatePartWeekDay // 星期几
|
||||||
|
DatePartISOWeekDay // 星期几
|
||||||
|
DatePartDay // 日
|
||||||
|
DatePartTime // 小时:分钟:秒
|
||||||
|
DatePartHour // 小时
|
||||||
|
DatePartMinute // 分钟
|
||||||
|
DatePartSecond // 秒
|
||||||
|
DatePartMicrosecond // 微秒
|
||||||
|
)
|
||||||
|
|
||||||
|
var dateParts = [...]string{
|
||||||
|
DatePartDate: "date",
|
||||||
|
DatePartYear: "year",
|
||||||
|
DatePartISOYear: "iso_year",
|
||||||
|
DatePartQuarter: "quarter",
|
||||||
|
DatePartMonth: "month",
|
||||||
|
DatePartWeek: "week",
|
||||||
|
DatePartWeekDay: "week_day",
|
||||||
|
DatePartISOWeekDay: "iso_week_day",
|
||||||
|
DatePartDay: "day",
|
||||||
|
DatePartTime: "time",
|
||||||
|
DatePartHour: "hour",
|
||||||
|
DatePartMinute: "minute",
|
||||||
|
DatePartSecond: "second",
|
||||||
|
DatePartMicrosecond: "microsecond",
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasDatePart(str string) bool {
|
||||||
|
str = strings.ToLower(str)
|
||||||
|
for _, item := range dateParts {
|
||||||
|
if str == item {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// QueryCommandToWhereConditions 查询命令转换为选择条件
|
// QueryCommandToWhereConditions 查询命令转换为选择条件
|
||||||
func QueryCommandToWhereConditions(strJson string, isOr bool) (error, func(s *sql.Selector)) {
|
func QueryCommandToWhereConditions(strJson string, isOr bool) (error, func(s *sql.Selector)) {
|
||||||
if len(strJson) == 0 {
|
if len(strJson) == 0 {
|
||||||
@@ -123,162 +191,201 @@ 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 = filterNotIsNull(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 strs []string
|
var values []any
|
||||||
if err := json.Unmarshal([]byte(value), &strs); err == nil {
|
if err := json.Unmarshal([]byte(value), &values); err == nil {
|
||||||
return sql.In(s.C(field), strs)
|
return p.In(s.C(field), values...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var float64s []float64
|
|
||||||
if err := json.Unmarshal([]byte(value), &float64s); err == nil {
|
|
||||||
return sql.In(s.C(field), strs)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
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 strs []string
|
var values []any
|
||||||
if err := json.Unmarshal([]byte(value), &strs); err == nil {
|
if err := json.Unmarshal([]byte(value), &values); err == nil {
|
||||||
return sql.NotIn(s.C(field), strs)
|
return p.NotIn(s.C(field), values...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var float64s []float64
|
|
||||||
if err := json.Unmarshal([]byte(value), &float64s); err == nil {
|
|
||||||
return sql.NotIn(s.C(field), strs)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
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 strs []string
|
var values []any
|
||||||
if err := json.Unmarshal([]byte(value), &strs); err == nil {
|
if err := json.Unmarshal([]byte(value), &values); err == nil {
|
||||||
if len(strs) != 2 {
|
if len(values) != 2 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return sql.And(
|
return sql.And(
|
||||||
sql.GTE(s.C(field), strs[0]),
|
sql.GTE(s.C(field), values[0]),
|
||||||
sql.LTE(s.C(field), strs[1]),
|
sql.LTE(s.C(field), values[1]),
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var float64s []float64
|
|
||||||
if err := json.Unmarshal([]byte(value), &float64s); err == nil {
|
|
||||||
if len(float64s) != 2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return sql.And(
|
|
||||||
sql.GTE(s.C(field), float64s[0]),
|
|
||||||
sql.LTE(s.C(field), float64s[1]),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,90 +394,210 @@ 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))
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterNotIsNull 不为空 IS NOT NULL操作
|
// filterIsNotNull 不为空 IS NOT NULL操作
|
||||||
// SQL: WHERE name IS NOT NULL
|
// SQL: WHERE name IS NOT NULL
|
||||||
func filterNotIsNull(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 LIKE 操作 精确比对
|
// filterRegex 正则查找
|
||||||
// MySQL: WHERE title REGEXP BINARY '^(An?|The) +'
|
// MySQL: WHERE title REGEXP BINARY '^(An?|The) +'
|
||||||
// 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 {
|
||||||
return nil
|
p.Append(func(b *sql.Builder) {
|
||||||
|
switch s.Builder.Dialect() {
|
||||||
|
case dialect.Postgres:
|
||||||
|
b.Ident(s.C(field)).WriteString(" ~ ")
|
||||||
|
b.Arg(value)
|
||||||
|
break
|
||||||
|
|
||||||
|
case dialect.MySQL:
|
||||||
|
b.Ident(s.C(field)).WriteString(" REGEXP BINARY ")
|
||||||
|
b.Arg(value)
|
||||||
|
break
|
||||||
|
|
||||||
|
case dialect.SQLite:
|
||||||
|
b.Ident(s.C(field)).WriteString(" REGEXP ")
|
||||||
|
b.Arg(value)
|
||||||
|
break
|
||||||
|
|
||||||
|
case dialect.Gremlin:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterInsensitiveRegex ILIKE 操作 不区分大小写,精确比对
|
// filterInsensitiveRegex 正则查找 不区分大小写
|
||||||
// MySQL: WHERE title REGEXP '^(an?|the) +'
|
// MySQL: WHERE title REGEXP '^(an?|the) +'
|
||||||
// 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 {
|
||||||
return nil
|
p.Append(func(b *sql.Builder) {
|
||||||
|
switch s.Builder.Dialect() {
|
||||||
|
case dialect.Postgres:
|
||||||
|
b.Ident(s.C(field)).WriteString(" ~* ")
|
||||||
|
b.Arg(strings.ToLower(value))
|
||||||
|
break
|
||||||
|
|
||||||
|
case dialect.MySQL:
|
||||||
|
b.Ident(s.C(field)).WriteString(" REGEXP ")
|
||||||
|
b.Arg(strings.ToLower(value))
|
||||||
|
break
|
||||||
|
|
||||||
|
case dialect.SQLite:
|
||||||
|
b.Ident(s.C(field)).WriteString(" REGEXP ")
|
||||||
|
if !strings.HasPrefix(value, "(?i)") {
|
||||||
|
value = "(?i)" + value
|
||||||
|
}
|
||||||
|
b.Arg(strings.ToLower(value))
|
||||||
|
break
|
||||||
|
|
||||||
|
case dialect.Gremlin:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterSearch 全文搜索
|
// filterSearch 全文搜索
|
||||||
// SQL:
|
// SQL:
|
||||||
func filterSearch(s *sql.Selector, _, _ string) *sql.Predicate {
|
func filterSearch(s *sql.Selector, p *sql.Predicate, _, _ string) *sql.Predicate {
|
||||||
return nil
|
p.Append(func(b *sql.Builder) {
|
||||||
|
switch s.Builder.Dialect() {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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()
|
||||||
}
|
}
|
||||||
|
|||||||
694
entgo/query/filter_test.go
Normal file
694
entgo/query/filter_test.go
Normal file
@@ -0,0 +1,694 @@
|
|||||||
|
package entgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"entgo.io/ent/dialect"
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFilter(t *testing.T) {
|
||||||
|
t.Run("MySQL_FilterEqual", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterEqual(s, p, "name", "tom")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` = ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "tom")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterEqual", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterEqual(s, p, "name", "tom")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" = $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "tom")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterNot", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterNot(s, p, "name", "tom")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE NOT `users`.`name` = ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "tom")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterNot", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterNot(s, p, "name", "tom")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE NOT \"users\".\"name\" = $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "tom")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterIn", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterIn(s, p, "name", "[\"tom\", \"jimmy\", 123]")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` IN (?, ?, ?)", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "tom")
|
||||||
|
require.Equal(t, args[1], "jimmy")
|
||||||
|
require.Equal(t, args[2], float64(123))
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterIn", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterIn(s, p, "name", "[\"tom\", \"jimmy\", 123]")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" IN ($1, $2, $3)", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "tom")
|
||||||
|
require.Equal(t, args[1], "jimmy")
|
||||||
|
require.Equal(t, args[2], float64(123))
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterNotIn", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterNotIn(s, p, "name", "[\"tom\", \"jimmy\", 123]")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` NOT IN (?, ?, ?)", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "tom")
|
||||||
|
require.Equal(t, args[1], "jimmy")
|
||||||
|
require.Equal(t, args[2], float64(123))
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterNotIn", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterNotIn(s, p, "name", "[\"tom\", \"jimmy\", 123]")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" NOT IN ($1, $2, $3)", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "tom")
|
||||||
|
require.Equal(t, args[1], "jimmy")
|
||||||
|
require.Equal(t, args[2], float64(123))
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterGTE", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterGTE(s, p, "create_time", "2023-10-25")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`create_time` >= ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "2023-10-25")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterGTE", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterGTE(s, p, "create_time", "2023-10-25")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"create_time\" >= $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "2023-10-25")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterGT", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterGT(s, p, "create_time", "2023-10-25")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`create_time` > ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "2023-10-25")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterGT", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterGT(s, p, "create_time", "2023-10-25")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"create_time\" > $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "2023-10-25")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterLTE", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterLTE(s, p, "create_time", "2023-10-25")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`create_time` <= ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "2023-10-25")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterLTE", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterLTE(s, p, "create_time", "2023-10-25")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"create_time\" <= $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "2023-10-25")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterLT", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterLT(s, p, "create_time", "2023-10-25")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`create_time` < ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "2023-10-25")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterLT", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterLT(s, p, "create_time", "2023-10-25")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"create_time\" < $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "2023-10-25")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterRange", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterRange(s, p, "create_time", "[\"2023-10-25\", \"2024-10-25\"]")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`create_time` >= ? AND `users`.`create_time` <= ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "2023-10-25")
|
||||||
|
require.Equal(t, args[1], "2024-10-25")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterRange", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterRange(s, p, "create_time", "[\"2023-10-25\", \"2024-10-25\"]")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"create_time\" >= $1 AND \"users\".\"create_time\" <= $2", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "2023-10-25")
|
||||||
|
require.Equal(t, args[1], "2024-10-25")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterIsNull", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterIsNull(s, p, "name", "true")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` IS NULL", query)
|
||||||
|
require.Empty(t, args)
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterIsNull", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterIsNull(s, p, "name", "true")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" IS NULL", query)
|
||||||
|
require.Empty(t, args)
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterIsNotNull", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterIsNotNull(s, p, "name", "true")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE NOT `users`.`name` IS NULL", query)
|
||||||
|
require.Empty(t, args)
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterIsNotNull", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterIsNotNull(s, p, "name", "true")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE NOT \"users\".\"name\" IS NULL", query)
|
||||||
|
require.Empty(t, args)
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterContains", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterContains(s, p, "name", "L")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` LIKE ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "%L%")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterContains", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterContains(s, p, "name", "L")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" LIKE $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "%L%")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterInsensitiveContains", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterInsensitiveContains(s, p, "name", "L")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` COLLATE utf8mb4_general_ci LIKE ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "%l%")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterInsensitiveContains", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterInsensitiveContains(s, p, "name", "L")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" ILIKE $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "%l%")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterStartsWith", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterStartsWith(s, p, "name", "La")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` LIKE ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "La%")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterStartsWith", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterStartsWith(s, p, "name", "La")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" LIKE $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "La%")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterInsensitiveStartsWith", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterInsensitiveStartsWith(s, p, "name", "La")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` COLLATE utf8mb4_general_ci = ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "la%")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterInsensitiveStartsWith", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterInsensitiveStartsWith(s, p, "name", "La")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" ILIKE $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "la\\%")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterEndsWith", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterEndsWith(s, p, "name", "La")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` LIKE ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "%La")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterEndsWith", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterEndsWith(s, p, "name", "La")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" LIKE $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "%La")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterInsensitiveEndsWith", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterInsensitiveEndsWith(s, p, "name", "La")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` COLLATE utf8mb4_general_ci = ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "%la")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterInsensitiveEndsWith", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterInsensitiveEndsWith(s, p, "name", "La")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" ILIKE $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "\\%la")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterExact", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterExact(s, p, "name", "La")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` LIKE ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "La")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterExact", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterExact(s, p, "name", "La")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" LIKE $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "La")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterInsensitiveExact", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterInsensitiveExact(s, p, "name", "La")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` COLLATE utf8mb4_general_ci = ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "la")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterInsensitiveExact", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterInsensitiveExact(s, p, "name", "La")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" ILIKE $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "la")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterRegex", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterRegex(s, p, "name", "^(An?|The) +")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` REGEXP BINARY ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "^(An?|The) +")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterRegex", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterRegex(s, p, "name", "^(An?|The) +")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" ~ $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "^(An?|The) +")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterInsensitiveRegex", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterInsensitiveRegex(s, p, "name", "^(An?|The) +")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE `users`.`name` REGEXP ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "^(an?|the) +")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterInsensitiveRegex", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = filterInsensitiveRegex(s, p, "name", "^(An?|The) +")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE \"users\".\"name\" ~* $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "^(an?|the) +")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_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)
|
||||||
|
}
|
||||||
@@ -21,23 +21,31 @@ func QueryCommandToOrderConditions(orderBys []string) (error, func(s *sql.Select
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
s.OrderBy(sql.Desc(s.C(key)))
|
BuildOrderSelect(s, key, true)
|
||||||
} else {
|
} else {
|
||||||
// 升序
|
// 升序
|
||||||
if len(v) == 0 {
|
if len(v) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
s.OrderBy(sql.Asc(s.C(v)))
|
BuildOrderSelect(s, v, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
s.OrderBy(sql.Desc(s.C(defaultOrderField)))
|
BuildOrderSelect(s, defaultOrderField, true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return QueryCommandToOrderConditions(orderBys)
|
return QueryCommandToOrderConditions(orderBys)
|
||||||
|
|||||||
@@ -3,24 +3,27 @@ package entgo
|
|||||||
import (
|
import (
|
||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
|
|
||||||
paging "github.com/tx7do/kratos-utils/pagination"
|
paging "github.com/tx7do/go-utils/pagination"
|
||||||
)
|
)
|
||||||
|
|
||||||
func BuildPaginationSelector(page, pageSize int32, noPaging bool) func(*sql.Selector) {
|
func BuildPaginationSelector(page, pageSize int32, noPaging bool) func(*sql.Selector) {
|
||||||
if noPaging {
|
if noPaging {
|
||||||
return nil
|
return nil
|
||||||
}
|
} else {
|
||||||
|
return func(s *sql.Selector) {
|
||||||
if page == 0 {
|
BuildPaginationSelect(s, page, pageSize)
|
||||||
page = DefaultPage
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if pageSize == 0 {
|
|
||||||
pageSize = DefaultPageSize
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(s *sql.Selector) {
|
|
||||||
s.Offset(paging.GetPageOffset(page, pageSize)).
|
|
||||||
Limit(int(pageSize))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BuildPaginationSelect(s *sql.Selector, page, pageSize int32) {
|
||||||
|
if page < 1 {
|
||||||
|
page = paging.DefaultPage
|
||||||
|
}
|
||||||
|
|
||||||
|
if pageSize < 1 {
|
||||||
|
pageSize = paging.DefaultPageSize
|
||||||
|
}
|
||||||
|
offset := paging.GetPageOffset(page, pageSize)
|
||||||
|
s.Offset(offset).Limit(int(pageSize))
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,20 +2,16 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// BuildQuerySelector 构建分页过滤查询器
|
||||||
DefaultPage = 1
|
|
||||||
DefaultPageSize = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
// BuildQuerySelector 构建分页查询选择器
|
|
||||||
func BuildQuerySelector(
|
func BuildQuerySelector(
|
||||||
dbDriverName string,
|
|
||||||
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 {
|
||||||
@@ -30,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...)
|
||||||
}
|
}
|
||||||
@@ -40,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"entgo.io/ent/dialect"
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
|
||||||
"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) {
|
||||||
@@ -92,3 +94,58 @@ func TestSplitQuery(t *testing.T) {
|
|||||||
assert.Equal(t, keys[0], "id")
|
assert.Equal(t, keys[0], "id")
|
||||||
assert.Equal(t, keys[1], "not")
|
assert.Equal(t, keys[1], "not")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildQuerySelectorDefault(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
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"},
|
||||||
|
|
||||||
|
{"MySQL_NoPagination", dialect.MySQL, "", "", true, "SELECT * FROM `users` ORDER BY `users`.`created_at` DESC"},
|
||||||
|
{"PostgreSQL_NoPagination", dialect.Postgres, "", "", true, "SELECT * FROM \"users\" ORDER BY \"users\".\"created_at\" DESC"},
|
||||||
|
|
||||||
|
{"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"},
|
||||||
|
{"PostgreSQL_JsonbQuery", dialect.Postgres, "{\"preferences__daily_email\" : \"true\"}", "", true, "SELECT * FROM \"users\" WHERE \"users\".\"preferences\" -> daily_email = $1 ORDER BY \"users\".\"created_at\" DESC"},
|
||||||
|
|
||||||
|
{"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"},
|
||||||
|
{"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"},
|
||||||
|
|
||||||
|
{"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"},
|
||||||
|
{"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"},
|
||||||
|
|
||||||
|
{"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"},
|
||||||
|
{"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"},
|
||||||
|
|
||||||
|
{"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"},
|
||||||
|
{"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"},
|
||||||
|
}
|
||||||
|
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"))
|
||||||
|
|
||||||
|
err, _, querySelectors := BuildQuerySelector(tc.and, tc.or,
|
||||||
|
1, 10, tc.noPaging,
|
||||||
|
[]string{}, "created_at",
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
|
checker.Nil(err)
|
||||||
|
//checker.NotNil(whereSelectors)
|
||||||
|
checker.NotNil(querySelectors)
|
||||||
|
|
||||||
|
for _, fnc := range querySelectors {
|
||||||
|
fnc(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
query, _ := s.Query()
|
||||||
|
checker.Equal(tc.actualSql, query)
|
||||||
|
//checker.Empty(t, args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
29
entgo/query/select.go
Normal file
29
entgo/query/select.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
48
entgo/query/select_test.go
Normal file
48
entgo/query/select_test.go
Normal 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
25
entgo/update/update.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
30
entgo/update/update_test.go
Normal file
30
entgo/update/update_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
234
fieldmaskutil/fieldmaskutil.go
Normal file
234
fieldmaskutil/fieldmaskutil.go
Normal 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
|
||||||
|
}
|
||||||
59
fieldmaskutil/fieldmaskutil_test.go
Normal file
59
fieldmaskutil/fieldmaskutil_test.go
Normal 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"})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"github.com/go-kratos/kratos/v2/log"
|
"github.com/go-kratos/kratos/v2/log"
|
||||||
"github.com/oschwald/geoip2-golang"
|
"github.com/oschwald/geoip2-golang"
|
||||||
|
|
||||||
"github.com/tx7do/kratos-utils/geoip"
|
"github.com/tx7do/go-utils/geoip"
|
||||||
"github.com/tx7do/kratos-utils/geoip/geolite/assets"
|
"github.com/tx7do/go-utils/geoip/geolite/assets"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultOutputLanguage = "zh-CN"
|
const defaultOutputLanguage = "zh-CN"
|
||||||
|
|||||||
15
geoip/go.mod
15
geoip/go.mod
@@ -1,19 +1,22 @@
|
|||||||
module github.com/tx7do/kratos-utils/geoip
|
module github.com/tx7do/go-utils/geoip
|
||||||
|
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-kratos/kratos/v2 v2.7.1
|
github.com/go-kratos/kratos/v2 v2.7.3
|
||||||
github.com/oschwald/geoip2-golang v1.9.0
|
github.com/oschwald/geoip2-golang v1.9.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.9.0
|
||||||
golang.org/x/text v0.13.0
|
golang.org/x/text v0.14.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/oschwald/maxminddb-golang v1.11.0 // indirect
|
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
golang.org/x/sys v0.10.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/tx7do/go-utils => ../
|
||||||
|
|||||||
22
geoip/go.sum
22
geoip/go.sum
@@ -1,24 +1,26 @@
|
|||||||
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/go-kratos/kratos/v2 v2.7.1 h1:PNMUaWxS5ZGDp1EyID5ZosJb1OA/YiHnBxB0yUmocnc=
|
github.com/go-kratos/kratos/v2 v2.7.3 h1:T9MS69qk4/HkVUuHw5GS9PDVnOfzn+kxyF0CL5StqxA=
|
||||||
github.com/go-kratos/kratos/v2 v2.7.1/go.mod h1:CPn82O93OLHjtnbuyOKhAG5TkSvw+mFnL32c4lZFDwU=
|
github.com/go-kratos/kratos/v2 v2.7.3/go.mod h1:CQZ7V0qyVPwrotIpS5VNNUJNzEbcyRUl5pRtxLOIvn4=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/oschwald/geoip2-golang v1.9.0 h1:uvD3O6fXAXs+usU+UGExshpdP13GAqp4GBrzN7IgKZc=
|
github.com/oschwald/geoip2-golang v1.9.0 h1:uvD3O6fXAXs+usU+UGExshpdP13GAqp4GBrzN7IgKZc=
|
||||||
github.com/oschwald/geoip2-golang v1.9.0/go.mod h1:BHK6TvDyATVQhKNbQBdrj9eAvuwOMi2zSFXizL3K81Y=
|
github.com/oschwald/geoip2-golang v1.9.0/go.mod h1:BHK6TvDyATVQhKNbQBdrj9eAvuwOMi2zSFXizL3K81Y=
|
||||||
github.com/oschwald/maxminddb-golang v1.11.0 h1:aSXMqYR/EPNjGE8epgqwDay+P30hCBZIveY0WZbAWh0=
|
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
||||||
github.com/oschwald/maxminddb-golang v1.11.0/go.mod h1:YmVI+H0zh3ySFR3w+oz8PCfglAFj3PuCmui13+P9zDg=
|
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ package qqwry
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/tx7do/kratos-utils/geoip"
|
"github.com/tx7do/go-utils/geoip"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/tx7do/kratos-utils/geoip/qqwry/assets"
|
"github.com/tx7do/go-utils/geoip/qqwry/assets"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
|
|||||||
36
gitautotag.sh
Normal file
36
gitautotag.sh
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
CURTAG=`git describe --abbrev=0 --tags`;
|
||||||
|
CURTAG="${CURTAG/v/}"
|
||||||
|
|
||||||
|
IFS='.' read -a vers <<< "$CURTAG"
|
||||||
|
|
||||||
|
MAJ=${vers[0]}
|
||||||
|
MIN=${vers[1]}
|
||||||
|
BUG=${vers[2]}
|
||||||
|
echo "Current Tag: v$MAJ.$MIN.$BUG"
|
||||||
|
|
||||||
|
for cmd in "$@"
|
||||||
|
do
|
||||||
|
case $cmd in
|
||||||
|
"--major")
|
||||||
|
# $((MAJ+1))
|
||||||
|
((MAJ+=1))
|
||||||
|
MIN=0
|
||||||
|
BUG=0
|
||||||
|
echo "Incrementing Major Version#"
|
||||||
|
;;
|
||||||
|
"--minor")
|
||||||
|
((MIN+=1))
|
||||||
|
BUG=0
|
||||||
|
echo "Incrementing Minor Version#"
|
||||||
|
;;
|
||||||
|
"--bug")
|
||||||
|
((BUG+=1))
|
||||||
|
echo "Incrementing Bug Version#"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
NEWTAG="v$MAJ.$MIN.$BUG"
|
||||||
|
echo "Adding Tag: $NEWTAG";
|
||||||
|
git tag -a $NEWTAG -m $NEWTAG
|
||||||
12
go.mod
12
go.mod
@@ -1,15 +1,21 @@
|
|||||||
module github.com/tx7do/kratos-utils
|
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.6.0
|
||||||
|
github.com/gosimple/slug v1.14.0
|
||||||
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.9.0
|
||||||
golang.org/x/crypto v0.14.0
|
golang.org/x/crypto v0.22.0
|
||||||
|
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0
|
||||||
|
google.golang.org/protobuf v1.33.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/gosimple/unidecode v1.0.1 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||||
|
|||||||
21
go.sum
21
go.sum
@@ -1,6 +1,15 @@
|
|||||||
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/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gosimple/slug v1.14.0 h1:RtTL/71mJNDfpUbCOmnf/XFkzKRtD6wL6Uy+3akm4Es=
|
||||||
|
github.com/gosimple/slug v1.14.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
|
||||||
|
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
|
||||||
|
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
@@ -16,10 +25,14 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR
|
|||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
|
github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
|
||||||
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
|
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||||
|
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8=
|
||||||
|
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||||
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
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=
|
||||||
|
|||||||
48
gorm/go.mod
Normal file
48
gorm/go.mod
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
module github.com/tx7do/go-utils/gorm
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
toolchain go1.22.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
gorm.io/driver/clickhouse v0.6.0
|
||||||
|
gorm.io/driver/mysql v1.5.6
|
||||||
|
gorm.io/driver/postgres v1.5.7
|
||||||
|
gorm.io/driver/sqlite v1.5.5
|
||||||
|
gorm.io/driver/sqlserver v1.5.3
|
||||||
|
gorm.io/gorm v1.25.10
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/ClickHouse/ch-go v0.58.2 // indirect
|
||||||
|
github.com/ClickHouse/clickhouse-go/v2 v2.15.0 // indirect
|
||||||
|
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||||
|
github.com/go-faster/city v1.0.1 // indirect
|
||||||
|
github.com/go-faster/errors v0.6.1 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||||
|
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||||
|
github.com/google/uuid v1.3.1 // indirect
|
||||||
|
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.4.3 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/klauspost/compress v1.16.7 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||||
|
github.com/microsoft/go-mssqldb v1.6.0 // indirect
|
||||||
|
github.com/paulmach/orb v0.10.0 // indirect
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.18 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/segmentio/asm v1.2.0 // indirect
|
||||||
|
github.com/shopspring/decimal v1.3.1 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.19.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.19.0 // indirect
|
||||||
|
golang.org/x/crypto v0.14.0 // indirect
|
||||||
|
golang.org/x/sys v0.13.0 // indirect
|
||||||
|
golang.org/x/text v0.13.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/tx7do/go-utils => ../
|
||||||
2926
gorm/go.sum
Normal file
2926
gorm/go.sum
Normal file
File diff suppressed because it is too large
Load Diff
77
gorm/gorm_client.go
Normal file
77
gorm/gorm_client.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package gorm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"gorm.io/driver/clickhouse"
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/driver/sqlserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
*gorm.DB
|
||||||
|
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(driverName, dsn string, enableMigrate bool, gormCfg *gorm.Config) *Client {
|
||||||
|
c := &Client{}
|
||||||
|
|
||||||
|
if gormCfg == nil {
|
||||||
|
gormCfg = &gorm.Config{}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.err = c.createGormClient(driverName, dsn, enableMigrate, gormCfg)
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Error() error {
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// createGormClient 创建GORM的客户端
|
||||||
|
func (c *Client) createGormClient(driverName, dsn string, enableMigrate bool, gormCfg *gorm.Config) error {
|
||||||
|
var driver gorm.Dialector
|
||||||
|
switch driverName {
|
||||||
|
default:
|
||||||
|
fallthrough
|
||||||
|
case "mysql":
|
||||||
|
driver = mysql.Open(dsn)
|
||||||
|
break
|
||||||
|
case "postgres":
|
||||||
|
driver = postgres.Open(dsn)
|
||||||
|
break
|
||||||
|
case "clickhouse":
|
||||||
|
driver = clickhouse.Open(dsn)
|
||||||
|
break
|
||||||
|
case "sqlite":
|
||||||
|
driver = sqlite.Open(dsn)
|
||||||
|
break
|
||||||
|
case "sqlserver":
|
||||||
|
driver = sqlserver.Open(dsn)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := gorm.Open(driver, gormCfg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed opening connection to db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行数据库迁移工具
|
||||||
|
if enableMigrate {
|
||||||
|
if err = client.AutoMigrate(
|
||||||
|
getMigrateModels()...,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("failed creating schema resources: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.DB = client
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
13
gorm/migrates.go
Normal file
13
gorm/migrates.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package gorm
|
||||||
|
|
||||||
|
var migrateModels []interface{}
|
||||||
|
|
||||||
|
// RegisterMigrateModel 注册用于数据库迁移的数据库模型
|
||||||
|
func RegisterMigrateModel(model interface{}) {
|
||||||
|
migrateModels = append(migrateModels, &model)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMigrateModels 获取用于数据库迁移的数据库模型
|
||||||
|
func getMigrateModels() []interface{} {
|
||||||
|
return migrateModels
|
||||||
|
}
|
||||||
12
ioutil/file.go
Normal file
12
ioutil/file.go
Normal 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
|
||||||
|
}
|
||||||
279
ioutil/path.go
Normal file
279
ioutil/path.go
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
package ioutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gobwas/glob"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetWorkingDirPath 获取工作路径
|
||||||
|
func GetWorkingDirPath() string {
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExePath 获取可执行程序路径
|
||||||
|
func GetExePath() string {
|
||||||
|
ex, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
exePath := filepath.Dir(ex)
|
||||||
|
return exePath
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAbsPath 获取绝对路径
|
||||||
|
func GetAbsPath() string {
|
||||||
|
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileList 获取文件夹下面的所有文件的列表
|
||||||
|
func GetFileList(root string) []string {
|
||||||
|
var files []string
|
||||||
|
|
||||||
|
if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
files = append(files, path)
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFolderNameList 获取当前文件夹下面的所有文件夹名的列表
|
||||||
|
func GetFolderNameList(root string) []string {
|
||||||
|
var names []string
|
||||||
|
fs, _ := os.ReadDir(root)
|
||||||
|
for _, file := range fs {
|
||||||
|
if file.IsDir() {
|
||||||
|
names = append(names, file.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchPath Returns whether a given path matches a glob pattern.
|
||||||
|
//
|
||||||
|
// via github.com/gobwas/glob:
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
101
ioutil/path_test.go
Normal file
101
ioutil/path_test.go
Normal 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"))
|
||||||
|
}
|
||||||
94
maputils/maputils.go
Normal file
94
maputils/maputils.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
// This package includes utility functions for handling and manipulating maputils.
|
||||||
|
// It draws inspiration from JavaScript and Python and uses Go generics as a basis.
|
||||||
|
|
||||||
|
package maputils
|
||||||
|
|
||||||
|
// Keys - takes a map with keys K and values V, returns a slice of type K of the map's keys.
|
||||||
|
// Note: Go maps do not preserve insertion order.
|
||||||
|
func Keys[K comparable, V any](mapInstance map[K]V) []K {
|
||||||
|
keys := make([]K, len(mapInstance))
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for k := range mapInstance {
|
||||||
|
keys[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values - takes a map with keys K and values V, returns a slice of type V of the map's values.
|
||||||
|
// Note: Go maps do not preserve insertion order.
|
||||||
|
func Values[K comparable, V any](mapInstance map[K]V) []V {
|
||||||
|
values := make([]V, len(mapInstance))
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for _, v := range mapInstance {
|
||||||
|
values[i] = v
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge - takes an arbitrary number of map instances with keys K and values V and merges them into a single map.
|
||||||
|
// Note: merge works from left to right. If a key already exists in a previous map, its value is over-written.
|
||||||
|
func Merge[K comparable, V any](mapInstances ...map[K]V) map[K]V {
|
||||||
|
var mergedMapSize int
|
||||||
|
|
||||||
|
for _, mapInstance := range mapInstances {
|
||||||
|
mergedMapSize += len(mapInstance)
|
||||||
|
}
|
||||||
|
|
||||||
|
mergedMap := make(map[K]V, mergedMapSize)
|
||||||
|
|
||||||
|
for _, mapInstance := range mapInstances {
|
||||||
|
for k, v := range mapInstance {
|
||||||
|
mergedMap[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForEach - given a map with keys K and values V, executes the passed in function for each key-value pair.
|
||||||
|
func ForEach[K comparable, V any](mapInstance map[K]V, function func(key K, value V)) {
|
||||||
|
for key, value := range mapInstance {
|
||||||
|
function(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop - takes a map with keys K and values V, and a slice of keys K, dropping all the key-value pairs that match the keys in the slice.
|
||||||
|
// Note: this function will modify the passed in map. To get a different object, use the Copy function to pass a copy to this function.
|
||||||
|
func Drop[K comparable, V any](mapInstance map[K]V, keys []K) map[K]V {
|
||||||
|
for _, key := range keys {
|
||||||
|
delete(mapInstance, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy - takes a map with keys K and values V and returns a copy of the map.
|
||||||
|
func Copy[K comparable, V any](mapInstance map[K]V) map[K]V {
|
||||||
|
mapCopy := make(map[K]V, len(mapInstance))
|
||||||
|
|
||||||
|
for key, value := range mapInstance {
|
||||||
|
mapCopy[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter - takes a map with keys K and values V, and executes the passed in function for each key-value pair.
|
||||||
|
// If the filter function returns true, the key-value pair will be included in the output, otherwise it is filtered out.
|
||||||
|
func Filter[K comparable, V any](mapInstance map[K]V, function func(key K, value V) bool) map[K]V {
|
||||||
|
mapCopy := make(map[K]V, len(mapInstance))
|
||||||
|
|
||||||
|
for key, value := range mapInstance {
|
||||||
|
if function(key, value) {
|
||||||
|
mapCopy[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapCopy
|
||||||
|
}
|
||||||
139
maputils/maputils_test.go
Normal file
139
maputils/maputils_test.go
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
package maputils_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tx7do/go-utils/maputils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKeys(t *testing.T) {
|
||||||
|
var daysMap = map[string]int{
|
||||||
|
"Sunday": 1,
|
||||||
|
"Monday": 2,
|
||||||
|
"Tuesday": 3,
|
||||||
|
"Wednesday": 4,
|
||||||
|
"Thursday": 5,
|
||||||
|
"Friday": 6,
|
||||||
|
"Saturday": 7,
|
||||||
|
}
|
||||||
|
keys := maputils.Keys(daysMap)
|
||||||
|
days := []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
|
||||||
|
sort.Strings(days)
|
||||||
|
sort.Strings(keys)
|
||||||
|
assert.Equal(t, days, keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValues(t *testing.T) {
|
||||||
|
var daysMap = map[string]int{
|
||||||
|
"Sunday": 1,
|
||||||
|
"Monday": 2,
|
||||||
|
"Tuesday": 3,
|
||||||
|
"Wednesday": 4,
|
||||||
|
"Thursday": 5,
|
||||||
|
"Friday": 6,
|
||||||
|
"Saturday": 7,
|
||||||
|
}
|
||||||
|
values := maputils.Values(daysMap)
|
||||||
|
sort.Ints(values)
|
||||||
|
assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7}, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMerge(t *testing.T) {
|
||||||
|
first := map[string]string{
|
||||||
|
"George": "Harrison",
|
||||||
|
"Betty": "Davis",
|
||||||
|
}
|
||||||
|
second := map[string]string{
|
||||||
|
"Ronald": "Reagen",
|
||||||
|
"Betty": "Stewart",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t,
|
||||||
|
map[string]string{
|
||||||
|
"George": "Harrison",
|
||||||
|
"Betty": "Stewart",
|
||||||
|
"Ronald": "Reagen",
|
||||||
|
}, maputils.Merge(first, second))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForEach(t *testing.T) {
|
||||||
|
var daysMap = map[string]int{
|
||||||
|
"Sunday": 1,
|
||||||
|
"Monday": 2,
|
||||||
|
"Tuesday": 3,
|
||||||
|
"Wednesday": 4,
|
||||||
|
"Thursday": 5,
|
||||||
|
"Friday": 6,
|
||||||
|
"Saturday": 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := 0
|
||||||
|
|
||||||
|
maputils.ForEach(daysMap, func(_ string, day int) {
|
||||||
|
sum += day
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, 28, sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDrop(t *testing.T) {
|
||||||
|
var daysMap = map[string]int{
|
||||||
|
"Sunday": 1,
|
||||||
|
"Monday": 2,
|
||||||
|
"Tuesday": 3,
|
||||||
|
"Wednesday": 4,
|
||||||
|
"Thursday": 5,
|
||||||
|
"Friday": 6,
|
||||||
|
"Saturday": 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedResult := map[string]int{
|
||||||
|
"Sunday": 1,
|
||||||
|
"Friday": 6,
|
||||||
|
}
|
||||||
|
assert.Equal(t, expectedResult, maputils.Drop(daysMap, []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Saturday"}))
|
||||||
|
// ensure we do not modify the original value
|
||||||
|
assert.Equal(t, expectedResult, daysMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopy(t *testing.T) {
|
||||||
|
var daysMap = map[string]int{
|
||||||
|
"Sunday": 1,
|
||||||
|
"Monday": 2,
|
||||||
|
"Tuesday": 3,
|
||||||
|
"Wednesday": 4,
|
||||||
|
"Thursday": 5,
|
||||||
|
"Friday": 6,
|
||||||
|
"Saturday": 7,
|
||||||
|
}
|
||||||
|
daysCopy := maputils.Copy(daysMap)
|
||||||
|
assert.Equal(t, daysMap, daysCopy)
|
||||||
|
delete(daysCopy, "Sunday")
|
||||||
|
assert.NotEqual(t, daysMap, daysCopy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilter(t *testing.T) {
|
||||||
|
var daysMap = map[string]int{
|
||||||
|
"Sunday": 1,
|
||||||
|
"Monday": 2,
|
||||||
|
"Tuesday": 3,
|
||||||
|
"Wednesday": 4,
|
||||||
|
"Thursday": 5,
|
||||||
|
"Friday": 6,
|
||||||
|
"Saturday": 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedResult = map[string]int{
|
||||||
|
"Monday": 2,
|
||||||
|
"Wednesday": 4,
|
||||||
|
"Friday": 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredDays := maputils.Filter(daysMap, func(_ string, value int) bool {
|
||||||
|
return value%2 == 0
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, expectedResult, filteredDays)
|
||||||
|
}
|
||||||
134
math/gaussian.go
Normal file
134
math/gaussian.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
package math
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// prop
|
||||||
|
//mean: the mean (μ) of the distribution
|
||||||
|
//variance: the variance (σ^2) of the distribution
|
||||||
|
//standardDeviation: the standard deviation (σ) of the distribution
|
||||||
|
|
||||||
|
// combination
|
||||||
|
|
||||||
|
type Gaussian struct {
|
||||||
|
mean float64
|
||||||
|
variance float64
|
||||||
|
standardDeviation float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGaussian(mean, variance float64) *Gaussian {
|
||||||
|
if variance <= 0.0 {
|
||||||
|
panic("error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Gaussian{
|
||||||
|
mean: mean,
|
||||||
|
variance: variance,
|
||||||
|
standardDeviation: math.Sqrt(float64(variance)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erfc Complementary error function
|
||||||
|
// From Numerical Recipes in C 2e p221
|
||||||
|
func Erfc(x float64) float64 {
|
||||||
|
z := math.Abs(x)
|
||||||
|
t := 1 / (1 + z/2)
|
||||||
|
r := t * math.Exp(-z*z-1.26551223+t*(1.00002368+
|
||||||
|
t*(0.37409196+t*(0.09678418+t*(-0.18628806+
|
||||||
|
t*(0.27886807+t*(-1.13520398+t*(1.48851587+
|
||||||
|
t*(-0.82215223+t*0.17087277)))))))))
|
||||||
|
if x >= 0 {
|
||||||
|
return r
|
||||||
|
} else {
|
||||||
|
return 2 - r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ierfc Inverse complementary error function
|
||||||
|
// From Numerical Recipes 3e p265
|
||||||
|
func Ierfc(x float64) float64 {
|
||||||
|
if x >= 2 {
|
||||||
|
return -100
|
||||||
|
}
|
||||||
|
if x <= 0 {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
var xx float64
|
||||||
|
if x < 1 {
|
||||||
|
xx = x
|
||||||
|
} else {
|
||||||
|
xx = 2 - x
|
||||||
|
}
|
||||||
|
t := math.Sqrt(-2 * math.Log(xx/2))
|
||||||
|
r := -0.70711 * ((2.30753+t*0.27061)/
|
||||||
|
(1+t*(0.99229+t*0.04481)) - t)
|
||||||
|
|
||||||
|
for j := 0; j < 2; j++ {
|
||||||
|
e := Erfc(r) - xx
|
||||||
|
r += e / (1.12837916709551257*math.Exp(-(r*r)) - r*e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if x < 1 {
|
||||||
|
return r
|
||||||
|
} else {
|
||||||
|
return -r
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromPrecisionMean Construct a new distribution from the precision and precisionmean
|
||||||
|
func fromPrecisionMean(precision, precisionmean float64) *Gaussian {
|
||||||
|
return NewGaussian(precisionmean/precision, 1/precision)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PROB
|
||||||
|
|
||||||
|
// Pdf pdf(x): the probability density function, which describes the probability
|
||||||
|
// of a random variable taking on the value x
|
||||||
|
func (g *Gaussian) Pdf(x float64) float64 {
|
||||||
|
m := g.standardDeviation * math.Sqrt(2*math.Pi)
|
||||||
|
e := math.Exp(-math.Pow(x-g.mean, 2) / (2 * g.variance))
|
||||||
|
return e / m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cdf cdf(x): the cumulative distribution function,
|
||||||
|
// which describes the probability of a random
|
||||||
|
// variable falling in the interval (−∞, x]
|
||||||
|
func (g *Gaussian) Cdf(x float64) float64 {
|
||||||
|
return 0.5 * Erfc(-(x-g.mean)/(g.standardDeviation*math.Sqrt(2)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ppf ppf(x): the percent point function, the inverse of cdf
|
||||||
|
func (g *Gaussian) Ppf(x float64) float64 {
|
||||||
|
return g.mean - g.standardDeviation*math.Sqrt(2)*Ierfc(2*x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add add(d): returns the result of adding this and the given distribution
|
||||||
|
func (g *Gaussian) Add(d *Gaussian) *Gaussian {
|
||||||
|
return NewGaussian(g.mean+d.mean, g.variance+d.variance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub sub(d): returns the result of subtracting this and the given distribution
|
||||||
|
func (g *Gaussian) Sub(d *Gaussian) *Gaussian {
|
||||||
|
return NewGaussian(g.mean-d.mean, g.variance+d.variance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale scale(c): returns the result of scaling this distribution by the given constant
|
||||||
|
func (g *Gaussian) Scale(c float64) *Gaussian {
|
||||||
|
return NewGaussian(g.mean*c, g.variance*c*c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mul mul(d): returns the product distribution of this and the given distribution. If a constant is passed in the distribution is scaled.
|
||||||
|
func (g *Gaussian) Mul(d *Gaussian) *Gaussian {
|
||||||
|
precision := 1 / g.variance
|
||||||
|
dprecision := 1 / d.variance
|
||||||
|
return fromPrecisionMean(precision+dprecision, precision*g.mean+dprecision*d.mean)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Div div(d): returns the quotient distribution of this and the given distribution. If a constant is passed in the distribution is scaled by 1/d.
|
||||||
|
func (g *Gaussian) Div(d *Gaussian) *Gaussian {
|
||||||
|
precision := 1 / g.variance
|
||||||
|
dprecision := 1 / d.variance
|
||||||
|
return fromPrecisionMean(precision-dprecision, precision*g.mean-dprecision*d.mean)
|
||||||
|
}
|
||||||
47
math/gaussian_test.go
Normal file
47
math/gaussian_test.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package math
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGaussian(t *testing.T) {
|
||||||
|
g := NewGaussian(3.0, 1)
|
||||||
|
|
||||||
|
fmt.Printf("g: %#v\n", g)
|
||||||
|
fmt.Printf("pdf: %f\n", g.Pdf(5))
|
||||||
|
fmt.Printf("cdf: %f\n", g.Cdf(2))
|
||||||
|
fmt.Printf("ppf: %f\n", g.Ppf(5))
|
||||||
|
|
||||||
|
d := NewGaussian(0, 1)
|
||||||
|
fmt.Printf("ppf: %f, %f\n", d.Pdf(-2), 0.053991)
|
||||||
|
fmt.Printf("ppf: %f, %f\n", d.Pdf(-1), 0.241971)
|
||||||
|
fmt.Printf("ppf: %f, %f\n", d.Pdf(0), 0.398942)
|
||||||
|
fmt.Printf("ppf: %f, %f\n", d.Pdf(1), 0.241971)
|
||||||
|
fmt.Printf("ppf: %f, %f\n", d.Pdf(2), 0.053991)
|
||||||
|
|
||||||
|
fmt.Printf("cdf: %f, %f\n", d.Cdf(-1.28155), 0.1)
|
||||||
|
fmt.Printf("cdf: %f, %f\n", d.Cdf(-0.67499), 0.25)
|
||||||
|
fmt.Printf("cdf: %f, %f\n", d.Cdf(0), 0.5)
|
||||||
|
fmt.Printf("cdf: %f, %f\n", d.Cdf(0.67499), 0.75)
|
||||||
|
fmt.Printf("cdf: %f, %f\n", d.Cdf(1.28155), 0.9)
|
||||||
|
|
||||||
|
fmt.Printf("ppf: %f, %f\n", d.Ppf(0.1), -1.28155)
|
||||||
|
fmt.Printf("ppf: %f, %f\n", d.Ppf(0.25), -0.67499)
|
||||||
|
fmt.Printf("ppf: %f, %f\n", d.Ppf(0.5), 0.0)
|
||||||
|
fmt.Printf("ppf: %f, %f\n", d.Ppf(0.75), 0.67449)
|
||||||
|
fmt.Printf("ppf: %f, %f\n", d.Ppf(0.9), 1.28155)
|
||||||
|
|
||||||
|
d = d.Mul(NewGaussian(0, 1))
|
||||||
|
fmt.Printf("Mul: %#v\n", d)
|
||||||
|
fmt.Printf("%#v\n%#v", NewGaussian(1, 1).Scale(2), NewGaussian(2, 4))
|
||||||
|
|
||||||
|
d = NewGaussian(1, 1).Div(NewGaussian(1, 2))
|
||||||
|
fmt.Printf("div\n")
|
||||||
|
fmt.Printf("%#v\n%#v\n", d, NewGaussian(1, 2))
|
||||||
|
fmt.Printf("%#v\n%#v\n", NewGaussian(1, 1).Scale(1/(1.0/2.0)), NewGaussian(2, 4))
|
||||||
|
|
||||||
|
fmt.Printf("ADD:\n%#v\n%#v\n", NewGaussian(1, 1).Add(NewGaussian(1, 2)), NewGaussian(2, 3))
|
||||||
|
fmt.Printf("SUB:\n%#v\n%#v\n", NewGaussian(1, 1).Sub(NewGaussian(1, 2)), NewGaussian(0, 3))
|
||||||
|
fmt.Printf("SCALE:\n%#v\n%#v\n", NewGaussian(1, 1).Scale(2), NewGaussian(2, 4))
|
||||||
|
}
|
||||||
44
math/math.go
Normal file
44
math/math.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package math
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sign 符号函数(Sign function,简称sgn)是一个逻辑函数,用以判断实数的正负号。为避免和英文读音相似的正弦函数(sine)混淆,它亦称为Signum function。
|
||||||
|
func Sign[T int | int8 | int16 | int32 | int64 | float32 | float64](x T) T {
|
||||||
|
switch {
|
||||||
|
case x < 0: // x < 0 : -1
|
||||||
|
return -1
|
||||||
|
case x > 0: // x > 0 : +1
|
||||||
|
return +1
|
||||||
|
default: // x == 0 : 0
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mean 计算给定数据的平均值
|
||||||
|
func Mean(num []float64) float64 {
|
||||||
|
var count = len(num)
|
||||||
|
var sum float64 = 0
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
sum += num[i]
|
||||||
|
}
|
||||||
|
return sum / float64(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variance 使用平均值计算给定数据的方差
|
||||||
|
func Variance(mean float64, num []float64) float64 {
|
||||||
|
var count = len(num)
|
||||||
|
var variance float64 = 0
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
variance += math.Pow(num[i]-mean, 2)
|
||||||
|
}
|
||||||
|
return variance / float64(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StandardDeviation 使用方差计算给定数据的标准偏差
|
||||||
|
func StandardDeviation(num []float64) float64 {
|
||||||
|
var mean = Mean(num)
|
||||||
|
var variance = Variance(mean, num)
|
||||||
|
return math.Sqrt(variance)
|
||||||
|
}
|
||||||
30
math/math_test.go
Normal file
30
math/math_test.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package math
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSign(t *testing.T) {
|
||||||
|
assert.True(t, Sign(2) == 1)
|
||||||
|
assert.True(t, Sign(-2) == -1)
|
||||||
|
assert.True(t, Sign(0) == 0)
|
||||||
|
|
||||||
|
assert.True(t, Sign(int64(2)) == 1)
|
||||||
|
assert.True(t, Sign(int64(-2)) == -1)
|
||||||
|
assert.True(t, Sign(int64(0)) == 0)
|
||||||
|
|
||||||
|
assert.True(t, Sign(float32(2)) == 1)
|
||||||
|
assert.True(t, Sign(float32(-2)) == -1)
|
||||||
|
assert.True(t, Sign(float32(0)) == 0)
|
||||||
|
|
||||||
|
assert.True(t, Sign(float64(2)) == 1)
|
||||||
|
assert.True(t, Sign(float64(-2)) == -1)
|
||||||
|
assert.True(t, Sign(float64(0)) == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStandardDeviation(t *testing.T) {
|
||||||
|
assert.Equal(t, StandardDeviation([]float64{3, 5, 9, 1, 8, 6, 58, 9, 4, 10}), 15.8117045254457)
|
||||||
|
assert.Equal(t, StandardDeviation([]float64{1, 3, 5, 7, 9, 11, 2, 4, 6, 8}), 3.0397368307141326)
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tx7do/kratos-utils/trans"
|
"github.com/tx7do/go-utils/trans"
|
||||||
)
|
)
|
||||||
|
|
||||||
type idCounter uint32
|
type idCounter uint32
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/tx7do/kratos-utils/trans"
|
"github.com/tx7do/go-utils/trans"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGenerateOrderIdWithRandom(t *testing.T) {
|
func TestGenerateOrderIdWithRandom(t *testing.T) {
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
package pagination
|
package pagination
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultPage = 1 // 默认页数
|
||||||
|
DefaultPageSize = 10 // 默认每页行数
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPageOffset 计算偏移量
|
||||||
func GetPageOffset(pageNum, pageSize int32) int {
|
func GetPageOffset(pageNum, pageSize int32) int {
|
||||||
return int((pageNum - 1) * pageSize)
|
return int((pageNum - 1) * pageSize)
|
||||||
}
|
}
|
||||||
|
|||||||
375
sliceutil/sliceutils.go
Normal file
375
sliceutil/sliceutils.go
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package sliceutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filter - given a slice of type T, executes the given predicate function on each element in the slice.
|
||||||
|
// The predicate is passed the current element, the current index and the slice itself as function arguments.
|
||||||
|
// If the predicate returns true, the value is included in the result, otherwise it is filtered out.
|
||||||
|
func Filter[T any](slice []T, predicate func(value T, index int, slice []T) bool) (filtered []T) {
|
||||||
|
for i, el := range slice {
|
||||||
|
if ok := predicate(el, i, slice); ok {
|
||||||
|
filtered = append(filtered, el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForEach - given a slice of type T, executes the passed in function for each element in the slice.
|
||||||
|
// The function is passed the current element, the current index and the slice itself as function arguments.
|
||||||
|
func ForEach[T any](slice []T, function func(value T, index int, slice []T)) {
|
||||||
|
for i, el := range slice {
|
||||||
|
function(el, i, slice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map - given a slice of type T, executes the passed in mapper function for each element in the slice, returning a slice of type R.
|
||||||
|
// The function is passed the current element, the current index and the slice itself as function arguments.
|
||||||
|
func Map[T any, R any](slice []T, mapper func(value T, index int, slice []T) R) (mapped []R) {
|
||||||
|
if len(slice) > 0 {
|
||||||
|
mapped = make([]R, len(slice))
|
||||||
|
for i, el := range slice {
|
||||||
|
mapped[i] = mapper(el, i, slice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce - given a slice of type T, executes the passed in reducer function for each element in the slice, returning a result of type R.
|
||||||
|
// The function is passed the accumulator, current element, current index and the slice itself as function arguments.
|
||||||
|
// The third argument to reduce is the initial value of type R to use.
|
||||||
|
func Reduce[T any, R any](slice []T, reducer func(acc R, value T, index int, slice []T) R, initial R) R {
|
||||||
|
acc := initial
|
||||||
|
for i, el := range slice {
|
||||||
|
acc = reducer(acc, el, i, slice)
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find - given a slice of type T, executes the passed in predicate function for each element in the slice.
|
||||||
|
// If the predicate returns true - a pointer to the element is returned. If no element is found, nil is returned.
|
||||||
|
// The function is passed the current element, the current index and the slice itself as function arguments.
|
||||||
|
func Find[T any](slice []T, predicate func(value T, index int, slice []T) bool) *T {
|
||||||
|
for i, el := range slice {
|
||||||
|
if ok := predicate(el, i, slice); ok {
|
||||||
|
return &el
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindIndex - given a slice of type T, executes the passed in predicate function for each element in the slice.
|
||||||
|
// If the predicate returns true - the index of the element is returned. If no element is found, -1 is returned.
|
||||||
|
// The function is passed the current element, the current index and the slice itself as function arguments.
|
||||||
|
func FindIndex[T any](slice []T, predicate func(value T, index int, slice []T) bool) int {
|
||||||
|
for i, el := range slice {
|
||||||
|
if ok := predicate(el, i, slice); ok {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindIndexOf - given a slice of type T and a value of type T, return ths first index of an element equal to value.
|
||||||
|
// If no element is found, -1 is returned.
|
||||||
|
func FindIndexOf[T comparable](slice []T, value T) int {
|
||||||
|
for i, el := range slice {
|
||||||
|
if el == value {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindLastIndex - given a slice of type T, executes the passed in predicate function for each element in the slice starting from its end.
|
||||||
|
// If no element is found, -1 is returned. The function is passed the current element, the current index and the slice itself as function arguments.
|
||||||
|
func FindLastIndex[T any](slice []T, predicate func(value T, index int, slice []T) bool) int {
|
||||||
|
for i := len(slice) - 1; i > 0; i-- {
|
||||||
|
el := slice[i]
|
||||||
|
if ok := predicate(el, i, slice); ok {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindLastIndexOf - given a slice of type T and a value of type T, returning the last index matching the given value.
|
||||||
|
// If no element is found, -1 is returned.
|
||||||
|
func FindLastIndexOf[T comparable](slice []T, value T) int {
|
||||||
|
for i := len(slice) - 1; i > 0; i-- {
|
||||||
|
el := slice[i]
|
||||||
|
if el == value {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindIndexes - given a slice of type T, executes the passed in predicate function for each element in the slice.
|
||||||
|
// Returns a slice containing all indexes of elements for which the predicate returned true. If no element are found, returns a nil int slice.
|
||||||
|
// The function is passed the current element, the current index and the slice itself as function arguments.
|
||||||
|
func FindIndexes[T any](slice []T, predicate func(value T, index int, slice []T) bool) []int {
|
||||||
|
var indexes []int
|
||||||
|
for i, el := range slice {
|
||||||
|
if ok := predicate(el, i, slice); ok {
|
||||||
|
indexes = append(indexes, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return indexes
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindIndexesOf - given a slice of type T and a value of type T, returns a slice containing all indexes match the given value.
|
||||||
|
// If no element are found, returns a nil int slice.
|
||||||
|
func FindIndexesOf[T comparable](slice []T, value T) []int {
|
||||||
|
var indexes []int
|
||||||
|
for i, el := range slice {
|
||||||
|
if el == value {
|
||||||
|
indexes = append(indexes, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return indexes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Includes - given a slice of type T and a value of type T, determines whether the value is contained by the slice.
|
||||||
|
// Note: T is constrained to comparable types only and comparison is determined using the equality operator.
|
||||||
|
func Includes[T comparable](slice []T, value T) bool {
|
||||||
|
for _, el := range slice {
|
||||||
|
if el == value {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some - given a slice of type T, executes the given predicate for each element of the slice.
|
||||||
|
// If the predicate returns true for any element, it returns true, otherwise it returns false.
|
||||||
|
// The function is passed the current element, the current index and the slice itself as function arguments.
|
||||||
|
func Some[T any](slice []T, predicate func(value T, index int, slice []T) bool) bool {
|
||||||
|
for i, el := range slice {
|
||||||
|
if ok := predicate(el, i, slice); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every - given a slice of type T, executes the given predicate for each element of the slice.
|
||||||
|
// If the predicate returns true for all elements, it returns true, otherwise it returns false.
|
||||||
|
// The function is passed the current element, the current index and the slice itself as function arguments.
|
||||||
|
func Every[T any](slice []T, predicate func(value T, index int, slice []T) bool) bool {
|
||||||
|
for i, el := range slice {
|
||||||
|
if ok := predicate(el, i, slice); !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge - receives slices of type T and merges them into a single slice of type T.
|
||||||
|
// Note: The elements are merged in their order in a slice,
|
||||||
|
// i.e. first the elements of the first slice, then that of the second and so forth.
|
||||||
|
func Merge[T any](slices ...[]T) (mergedSlice []T) {
|
||||||
|
if len(slices) > 0 {
|
||||||
|
mergedSliceCap := 0
|
||||||
|
|
||||||
|
for _, slice := range slices {
|
||||||
|
mergedSliceCap += len(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mergedSliceCap > 0 {
|
||||||
|
mergedSlice = make([]T, 0, mergedSliceCap)
|
||||||
|
|
||||||
|
for _, slice := range slices {
|
||||||
|
mergedSlice = append(mergedSlice, slice...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mergedSlice
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum - receives a slice of type T and returns a value T that is the sum of the numbers.
|
||||||
|
// Note: T is constrained to be a number type.
|
||||||
|
func Sum[T constraints.Complex | constraints.Integer | constraints.Float](slice []T) (result T) {
|
||||||
|
for _, el := range slice {
|
||||||
|
result += el
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove - receives a slice of type T and an index, removing the element at the given index.
|
||||||
|
// Note: this function does not modify the input slice.
|
||||||
|
func Remove[T any](slice []T, i int) []T {
|
||||||
|
if len(slice) == 0 || i > len(slice)-1 {
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
copied := Copy(slice)
|
||||||
|
if i == 0 {
|
||||||
|
return copied[1:]
|
||||||
|
}
|
||||||
|
if i != len(copied)-1 {
|
||||||
|
return append(copied[:i], copied[i+1:]...)
|
||||||
|
}
|
||||||
|
return copied[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert - receives a slice of type T, an index and a value.
|
||||||
|
// The value is inserted at the given index. If there is an existing value at this index, it is shifted to the next index.
|
||||||
|
// Note: this function does not modify the input slice.
|
||||||
|
func Insert[T any](slice []T, i int, value T) []T {
|
||||||
|
if len(slice) == i {
|
||||||
|
return append(slice, value)
|
||||||
|
}
|
||||||
|
slice = append(slice[:i+1], slice[i:]...)
|
||||||
|
slice[i] = value
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy - receives a slice of type T and copies it.
|
||||||
|
func Copy[T any](slice []T) []T {
|
||||||
|
duplicate := make([]T, len(slice), cap(slice))
|
||||||
|
copy(duplicate, slice)
|
||||||
|
return duplicate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersection - takes a variadic number of slices of type T and returns a slice of type T containing any values that exist in all the slices.
|
||||||
|
// For example, given []int{1, 2, 3}, []int{1, 7, 3}, the intersection would be []int{1, 3}.
|
||||||
|
func Intersection[T comparable](slices ...[]T) []T {
|
||||||
|
possibleIntersections := map[T]int{}
|
||||||
|
for i, slice := range slices {
|
||||||
|
for _, el := range slice {
|
||||||
|
if i == 0 {
|
||||||
|
possibleIntersections[el] = 0
|
||||||
|
} else if _, elementExists := possibleIntersections[el]; elementExists {
|
||||||
|
possibleIntersections[el] = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
intersected := make([]T, 0)
|
||||||
|
for _, el := range slices[0] {
|
||||||
|
if lastVisitorIndex, exists := possibleIntersections[el]; exists && lastVisitorIndex == len(slices)-1 {
|
||||||
|
intersected = append(intersected, el)
|
||||||
|
delete(possibleIntersections, el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return intersected
|
||||||
|
}
|
||||||
|
|
||||||
|
// Difference - takes a variadic number of slices of type T and returns a slice of type T containing the elements that are different between the slices.
|
||||||
|
// For example, given []int{1, 2, 3}, []int{2, 3, 4}, []{3, 4, 5}, the difference would be []int{1, 5}.
|
||||||
|
func Difference[T comparable](slices ...[]T) []T {
|
||||||
|
possibleDifferences := map[T]int{}
|
||||||
|
nonDifferentElements := map[T]int{}
|
||||||
|
|
||||||
|
for i, slice := range slices {
|
||||||
|
for _, el := range slice {
|
||||||
|
if lastVisitorIndex, elementExists := possibleDifferences[el]; elementExists && lastVisitorIndex != i {
|
||||||
|
nonDifferentElements[el] = i
|
||||||
|
} else if !elementExists {
|
||||||
|
possibleDifferences[el] = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
differentElements := make([]T, 0)
|
||||||
|
|
||||||
|
for _, slice := range slices {
|
||||||
|
for _, el := range slice {
|
||||||
|
if _, exists := nonDifferentElements[el]; !exists {
|
||||||
|
differentElements = append(differentElements, el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return differentElements
|
||||||
|
}
|
||||||
|
|
||||||
|
// Union - takes a variadic number of slices of type T and returns a slice of type T containing the unique elements in the different slices
|
||||||
|
// For example, given []int{1, 2, 3}, []int{2, 3, 4}, []int{3, 4, 5}, the union would be []int{1, 2, 3, 4, 5}.
|
||||||
|
func Union[T comparable](slices ...[]T) []T {
|
||||||
|
return Unique(Merge(slices...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse - takes a slice of type T and returns a slice of type T with a reverse order of elements.
|
||||||
|
func Reverse[T any](slice []T) []T {
|
||||||
|
result := make([]T, len(slice))
|
||||||
|
|
||||||
|
itemCount := len(slice)
|
||||||
|
middle := itemCount / 2
|
||||||
|
result[middle] = slice[middle]
|
||||||
|
|
||||||
|
for i := 0; i < middle; i++ {
|
||||||
|
mirrorIdx := itemCount - i - 1
|
||||||
|
result[i], result[mirrorIdx] = slice[mirrorIdx], slice[i]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unique - receives a slice of type T and returns a slice of type T containing all unique elements.
|
||||||
|
func Unique[T comparable](slice []T) []T {
|
||||||
|
unique := make([]T, 0)
|
||||||
|
visited := map[T]bool{}
|
||||||
|
|
||||||
|
for _, value := range slice {
|
||||||
|
if exists := visited[value]; !exists {
|
||||||
|
unique = append(unique, value)
|
||||||
|
visited[value] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unique
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chunk - receives a slice of type T and size N and returns a slice of slices T of size N.
|
||||||
|
func Chunk[T any](input []T, size int) [][]T {
|
||||||
|
var chunks [][]T
|
||||||
|
|
||||||
|
for i := 0; i < len(input); i += size {
|
||||||
|
end := i + size
|
||||||
|
if end > len(input) {
|
||||||
|
end = len(input)
|
||||||
|
}
|
||||||
|
chunks = append(chunks, input[i:end])
|
||||||
|
}
|
||||||
|
return chunks
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pluck - receives a slice of type I and a getter func to a field
|
||||||
|
// and returns a slice containing the requested field's value from each item in the slice.
|
||||||
|
func Pluck[I any, O any](input []I, getter func(I) *O) []O {
|
||||||
|
var output []O
|
||||||
|
|
||||||
|
for _, item := range input {
|
||||||
|
field := getter(item)
|
||||||
|
|
||||||
|
if field != nil {
|
||||||
|
output = append(output, *field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flatten - receives a slice of slices of type I and flattens it to a slice of type I.
|
||||||
|
func Flatten[I any](input [][]I) (output []I) {
|
||||||
|
if len(input) > 0 {
|
||||||
|
var outputSize int
|
||||||
|
|
||||||
|
for _, item := range input {
|
||||||
|
outputSize += len(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
if outputSize > 0 {
|
||||||
|
output = make([]I, 0, outputSize)
|
||||||
|
|
||||||
|
for _, item := range input {
|
||||||
|
output = append(output, item...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
301
sliceutil/sliceutils_test.go
Normal file
301
sliceutil/sliceutils_test.go
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
package sliceutil_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tx7do/go-utils/sliceutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MyInt int
|
||||||
|
|
||||||
|
type Pluckable struct {
|
||||||
|
Code string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
var numerals = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||||
|
var numeralsWithUserDefinedType = []MyInt{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||||
|
var days = []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
|
||||||
|
var lastNames = []string{"Jacobs", "Vin", "Jacobs", "Smith"}
|
||||||
|
|
||||||
|
func TestFilter(t *testing.T) {
|
||||||
|
expectedResult := []int{0, 2, 4, 6, 8}
|
||||||
|
actualResult := sliceutil.Filter(numerals, func(value int, _ int, _ []int) bool {
|
||||||
|
return value%2 == 0
|
||||||
|
})
|
||||||
|
assert.Equal(t, expectedResult, actualResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForEach(t *testing.T) {
|
||||||
|
result := 0
|
||||||
|
sliceutil.ForEach(numerals, func(value int, _ int, _ []int) {
|
||||||
|
result += value
|
||||||
|
})
|
||||||
|
assert.Equal(t, 45, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMap(t *testing.T) {
|
||||||
|
expectedResult := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
|
||||||
|
actualResult := sliceutil.Map(numerals, func(value int, _ int, _ []int) string {
|
||||||
|
return strconv.Itoa(value)
|
||||||
|
})
|
||||||
|
assert.Equal(t, expectedResult, actualResult)
|
||||||
|
|
||||||
|
assert.Nil(t, sliceutil.Map([]int{}, func(_ int, _ int, _ []int) string {
|
||||||
|
return ""
|
||||||
|
}))
|
||||||
|
|
||||||
|
assert.Nil(t, sliceutil.Map([]int(nil), func(_ int, _ int, _ []int) string {
|
||||||
|
return ""
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReduce(t *testing.T) {
|
||||||
|
expectedResult := map[string]string{"result": "0123456789"}
|
||||||
|
actualResult := sliceutil.Reduce(
|
||||||
|
numerals,
|
||||||
|
func(acc map[string]string, cur int, _ int, _ []int) map[string]string {
|
||||||
|
acc["result"] += strconv.Itoa(cur)
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
map[string]string{"result": ""},
|
||||||
|
)
|
||||||
|
assert.Equal(t, expectedResult, actualResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFind(t *testing.T) {
|
||||||
|
expectedResult := "Wednesday"
|
||||||
|
actualResult := sliceutil.Find(days, func(value string, index int, slice []string) bool {
|
||||||
|
return strings.Contains(value, "Wed")
|
||||||
|
})
|
||||||
|
assert.Equal(t, expectedResult, *actualResult)
|
||||||
|
assert.Nil(t, sliceutil.Find(days, func(value string, index int, slice []string) bool {
|
||||||
|
return strings.Contains(value, "Rishon")
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindIndex(t *testing.T) {
|
||||||
|
expectedResult := 3
|
||||||
|
actualResult := sliceutil.FindIndex(days, func(value string, index int, slice []string) bool {
|
||||||
|
return strings.Contains(value, "Wed")
|
||||||
|
})
|
||||||
|
assert.Equal(t, expectedResult, actualResult)
|
||||||
|
assert.Equal(t, -1, sliceutil.FindIndex(days, func(value string, index int, slice []string) bool {
|
||||||
|
return strings.Contains(value, "Rishon")
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindIndexOf(t *testing.T) {
|
||||||
|
expectedResult := 3
|
||||||
|
actualResult := sliceutil.FindIndexOf(days, "Wednesday")
|
||||||
|
assert.Equal(t, expectedResult, actualResult)
|
||||||
|
assert.Equal(t, -1, sliceutil.FindIndexOf(days, "Rishon"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindLastIndex(t *testing.T) {
|
||||||
|
expectedResult := 2
|
||||||
|
actualResult := sliceutil.FindLastIndex(lastNames, func(value string, index int, slice []string) bool {
|
||||||
|
return value == "Jacobs"
|
||||||
|
})
|
||||||
|
assert.Equal(t, expectedResult, actualResult)
|
||||||
|
assert.Equal(t, -1, sliceutil.FindLastIndex(lastNames, func(value string, index int, slice []string) bool {
|
||||||
|
return value == "Hamudi"
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindLastIndexOf(t *testing.T) {
|
||||||
|
expectedResult := 2
|
||||||
|
actualResult := sliceutil.FindLastIndexOf(lastNames, "Jacobs")
|
||||||
|
assert.Equal(t, expectedResult, actualResult)
|
||||||
|
assert.Equal(t, -1, sliceutil.FindLastIndexOf(lastNames, "Hamudi"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindIndexes(t *testing.T) {
|
||||||
|
expectedResult := []int{0, 2}
|
||||||
|
actualResult := sliceutil.FindIndexes(lastNames, func(value string, index int, slice []string) bool {
|
||||||
|
return value == "Jacobs"
|
||||||
|
})
|
||||||
|
assert.Equal(t, expectedResult, actualResult)
|
||||||
|
assert.Nil(t, sliceutil.FindIndexes(lastNames, func(value string, index int, slice []string) bool {
|
||||||
|
return value == "Hamudi"
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindIndexesOf(t *testing.T) {
|
||||||
|
expectedResult := []int{0, 2}
|
||||||
|
actualResult := sliceutil.FindIndexesOf(lastNames, "Jacobs")
|
||||||
|
assert.Equal(t, expectedResult, actualResult)
|
||||||
|
assert.Nil(t, sliceutil.FindIndexesOf(lastNames, "Hamudi"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIncludes(t *testing.T) {
|
||||||
|
assert.True(t, sliceutil.Includes(numerals, 1))
|
||||||
|
assert.False(t, sliceutil.Includes(numerals, 11))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAny(t *testing.T) {
|
||||||
|
assert.True(t, sliceutil.Some(numerals, func(value int, _ int, _ []int) bool {
|
||||||
|
return value%5 == 0
|
||||||
|
}))
|
||||||
|
assert.False(t, sliceutil.Some(numerals, func(value int, _ int, _ []int) bool {
|
||||||
|
return value == 11
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAll(t *testing.T) {
|
||||||
|
assert.True(t, sliceutil.Every([]int{1, 1, 1}, func(value int, _ int, _ []int) bool {
|
||||||
|
return value == 1
|
||||||
|
}))
|
||||||
|
assert.False(t, sliceutil.Every([]int{1, 1, 1, 2}, func(value int, _ int, _ []int) bool {
|
||||||
|
return value == 1
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMerge(t *testing.T) {
|
||||||
|
result := sliceutil.Merge(numerals[:5], numerals[5:])
|
||||||
|
assert.Equal(t, numerals, result)
|
||||||
|
|
||||||
|
assert.Nil(t, sliceutil.Merge([]int(nil), []int(nil)))
|
||||||
|
assert.Nil(t, sliceutil.Merge([]int{}, []int{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSum(t *testing.T) {
|
||||||
|
result := sliceutil.Sum(numerals)
|
||||||
|
assert.Equal(t, 45, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSum2(t *testing.T) {
|
||||||
|
result := sliceutil.Sum(numeralsWithUserDefinedType)
|
||||||
|
assert.Equal(t, MyInt(45), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemove(t *testing.T) {
|
||||||
|
testSlice := []int{1, 2, 3}
|
||||||
|
result := sliceutil.Remove(testSlice, 1)
|
||||||
|
assert.Equal(t, []int{1, 3}, result)
|
||||||
|
assert.Equal(t, []int{1, 2, 3}, testSlice)
|
||||||
|
result = sliceutil.Remove(result, 1)
|
||||||
|
assert.Equal(t, []int{1}, result)
|
||||||
|
result = sliceutil.Remove(result, 3)
|
||||||
|
assert.Equal(t, []int{1}, result)
|
||||||
|
result = sliceutil.Remove(result, 0)
|
||||||
|
assert.Equal(t, []int{}, result)
|
||||||
|
result = sliceutil.Remove(result, 1)
|
||||||
|
assert.Equal(t, []int{}, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopy(t *testing.T) {
|
||||||
|
testSlice := []int{1, 2, 3}
|
||||||
|
copiedSlice := sliceutil.Copy(testSlice)
|
||||||
|
copiedSlice[0] = 2
|
||||||
|
assert.NotEqual(t, testSlice, copiedSlice)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsert(t *testing.T) {
|
||||||
|
testSlice := []int{1, 2}
|
||||||
|
result := sliceutil.Insert(testSlice, 0, 3)
|
||||||
|
assert.Equal(t, []int{3, 1, 2}, result)
|
||||||
|
assert.NotEqual(t, testSlice, result)
|
||||||
|
assert.Equal(t, []int{1, 3, 2}, sliceutil.Insert(testSlice, 1, 3))
|
||||||
|
assert.Equal(t, []int{1, 2, 3}, sliceutil.Insert(testSlice, 2, 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntersection(t *testing.T) {
|
||||||
|
expectedResult := []int{3, 4, 5}
|
||||||
|
|
||||||
|
first := []int{1, 2, 3, 4, 5}
|
||||||
|
second := []int{2, 3, 4, 5, 6}
|
||||||
|
third := []int{3, 4, 5, 6, 7}
|
||||||
|
|
||||||
|
assert.Equal(t, expectedResult, sliceutil.Intersection(first, second, third))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDifference(t *testing.T) {
|
||||||
|
expectedResult := []int{1, 7}
|
||||||
|
|
||||||
|
first := []int{1, 2, 3, 4, 5}
|
||||||
|
second := []int{2, 3, 4, 5, 6}
|
||||||
|
third := []int{3, 4, 5, 6, 7}
|
||||||
|
|
||||||
|
assert.Equal(t, expectedResult, sliceutil.Difference(first, second, third))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnion(t *testing.T) {
|
||||||
|
expectedResult := []int{1, 2, 3, 4, 5, 6, 7}
|
||||||
|
|
||||||
|
first := []int{1, 2, 3, 4, 5}
|
||||||
|
second := []int{2, 3, 4, 5, 6}
|
||||||
|
third := []int{3, 4, 5, 6, 7}
|
||||||
|
|
||||||
|
assert.Equal(t, expectedResult, sliceutil.Union(first, second, third))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReverse(t *testing.T) {
|
||||||
|
expectedResult := []int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
|
||||||
|
assert.Equal(t, expectedResult, sliceutil.Reverse(numerals))
|
||||||
|
// ensure does not modify the original
|
||||||
|
assert.Equal(t, expectedResult, sliceutil.Reverse(numerals))
|
||||||
|
|
||||||
|
// test basic odd length case
|
||||||
|
expectedResult = []int{9, 8, 7, 6, 5, 4, 3, 2, 1}
|
||||||
|
assert.Equal(t, expectedResult, sliceutil.Reverse(numerals[1:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnique(t *testing.T) {
|
||||||
|
duplicates := []int{6, 6, 6, 9, 0, 0, 0}
|
||||||
|
expectedResult := []int{6, 9, 0}
|
||||||
|
assert.Equal(t, expectedResult, sliceutil.Unique(duplicates))
|
||||||
|
// Ensure original is unaltered
|
||||||
|
assert.NotEqual(t, expectedResult, duplicates)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChunk(t *testing.T) {
|
||||||
|
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}}, sliceutil.Chunk(numbers, 2))
|
||||||
|
assert.Equal(t, [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10}}, sliceutil.Chunk(numbers, 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPluck(t *testing.T) {
|
||||||
|
items := []Pluckable{
|
||||||
|
{
|
||||||
|
Code: "azer",
|
||||||
|
Value: "Azer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Code: "tyuio",
|
||||||
|
Value: "Tyuio",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"azer", "tyuio"}, sliceutil.Pluck(items, func(item Pluckable) *string {
|
||||||
|
return &item.Code
|
||||||
|
}))
|
||||||
|
assert.Equal(t, []string{"Azer", "Tyuio"}, sliceutil.Pluck(items, func(item Pluckable) *string {
|
||||||
|
return &item.Value
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlatten(t *testing.T) {
|
||||||
|
items := [][]int{
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
{5, 6},
|
||||||
|
{7, 8},
|
||||||
|
{9, 10, 11},
|
||||||
|
}
|
||||||
|
|
||||||
|
flattened := sliceutil.Flatten(items)
|
||||||
|
|
||||||
|
assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, flattened)
|
||||||
|
|
||||||
|
assert.Nil(t, sliceutil.Flatten([][]int{}))
|
||||||
|
assert.Nil(t, sliceutil.Flatten([][]int(nil)))
|
||||||
|
|
||||||
|
assert.Nil(t, sliceutil.Flatten([][]int{{}, {}}))
|
||||||
|
assert.Nil(t, sliceutil.Flatten([][]int{nil, nil}))
|
||||||
|
|
||||||
|
assert.Nil(t, sliceutil.Flatten([][]int{{}, nil}))
|
||||||
|
}
|
||||||
29
slug/slug.go
Normal file
29
slug/slug.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package slug
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gosimple/slug"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Generate 生成短链接
|
||||||
|
func Generate(input string) string {
|
||||||
|
slug.Lowercase = true
|
||||||
|
return slug.MakeLang(input, "en")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateCaseSensitive 生成大小写敏感的短链接
|
||||||
|
func GenerateCaseSensitive(input string) string {
|
||||||
|
slug.Lowercase = false
|
||||||
|
return slug.MakeLang(input, "en")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateEnglish 生成英文短链接
|
||||||
|
func GenerateEnglish(input string) string {
|
||||||
|
slug.Lowercase = true
|
||||||
|
return slug.MakeLang(input, "en")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateGerman 生成德文短链接
|
||||||
|
func GenerateGerman(input string) string {
|
||||||
|
slug.Lowercase = true
|
||||||
|
return slug.MakeLang(input, "de")
|
||||||
|
}
|
||||||
88
slug/slug_test.go
Normal file
88
slug/slug_test.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package slug
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gosimple/slug"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGoSimple(t *testing.T) {
|
||||||
|
// 俄文
|
||||||
|
text := slug.Make("Hellö Wörld хелло ворлд")
|
||||||
|
assert.Equal(t, text, "hello-world-khello-vorld")
|
||||||
|
fmt.Println(text)
|
||||||
|
|
||||||
|
// 繁体中文
|
||||||
|
someText := slug.Make("影師")
|
||||||
|
assert.Equal(t, someText, "ying-shi")
|
||||||
|
fmt.Println(someText)
|
||||||
|
|
||||||
|
// 简体中文
|
||||||
|
cnText := slug.Make("天下太平")
|
||||||
|
assert.Equal(t, cnText, "tian-xia-tai-ping")
|
||||||
|
fmt.Println(cnText)
|
||||||
|
|
||||||
|
// 英文
|
||||||
|
enText := slug.MakeLang("This & that", "en")
|
||||||
|
assert.Equal(t, enText, "this-and-that")
|
||||||
|
fmt.Println(enText)
|
||||||
|
|
||||||
|
// 德文
|
||||||
|
deText := slug.MakeLang("Diese & Dass", "de")
|
||||||
|
assert.Equal(t, deText, "diese-und-dass")
|
||||||
|
fmt.Println(deText)
|
||||||
|
|
||||||
|
// 保持大小写
|
||||||
|
slug.Lowercase = false
|
||||||
|
deUppercaseText := slug.MakeLang("Diese & Dass", "de")
|
||||||
|
assert.Equal(t, deUppercaseText, "Diese-und-Dass")
|
||||||
|
fmt.Println(deUppercaseText)
|
||||||
|
|
||||||
|
// 字符替换
|
||||||
|
slug.CustomSub = map[string]string{
|
||||||
|
"water": "sand",
|
||||||
|
}
|
||||||
|
textSub := slug.Make("water is hot")
|
||||||
|
assert.Equal(t, textSub, "sand-is-hot")
|
||||||
|
fmt.Println(textSub)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerate(t *testing.T) {
|
||||||
|
// 俄文
|
||||||
|
text := Generate("Hellö Wörld хелло ворлд")
|
||||||
|
assert.Equal(t, text, "hello-world-khello-vorld")
|
||||||
|
fmt.Println(text)
|
||||||
|
|
||||||
|
// 繁体中文
|
||||||
|
someText := Generate("影師")
|
||||||
|
assert.Equal(t, someText, "ying-shi")
|
||||||
|
fmt.Println(someText)
|
||||||
|
|
||||||
|
// 简体中文
|
||||||
|
cnText := Generate("天下太平")
|
||||||
|
assert.Equal(t, cnText, "tian-xia-tai-ping")
|
||||||
|
fmt.Println(cnText)
|
||||||
|
|
||||||
|
// 英文
|
||||||
|
enText := GenerateEnglish("This & that")
|
||||||
|
assert.Equal(t, enText, "this-and-that")
|
||||||
|
fmt.Println(enText)
|
||||||
|
|
||||||
|
enText = GenerateCaseSensitive("This & That")
|
||||||
|
assert.Equal(t, enText, "This-and-That")
|
||||||
|
fmt.Println(enText)
|
||||||
|
|
||||||
|
// 德文
|
||||||
|
deText := GenerateGerman("Diese & Dass")
|
||||||
|
assert.Equal(t, deText, "diese-und-dass")
|
||||||
|
fmt.Println(deText)
|
||||||
|
|
||||||
|
slug.CustomSub = map[string]string{
|
||||||
|
"water": "sand",
|
||||||
|
}
|
||||||
|
textSub := Generate("water is hot")
|
||||||
|
assert.Equal(t, textSub, "sand-is-hot")
|
||||||
|
fmt.Println(textSub)
|
||||||
|
}
|
||||||
37
structutil/structutils.go
Normal file
37
structutil/structutils.go
Normal 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
|
||||||
|
}
|
||||||
55
structutil/structutils_test.go
Normal file
55
structutil/structutils_test.go
Normal 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"))
|
||||||
|
}
|
||||||
8
tag.bat
Normal file
8
tag.bat
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
git tag v1.1.11
|
||||||
|
git tag bank_card/v1.1.1
|
||||||
|
git tag geoip/v1.1.1
|
||||||
|
|
||||||
|
git tag entgo/v1.1.13
|
||||||
|
git tag gorm/v1.1.0
|
||||||
|
|
||||||
|
git push origin --tags
|
||||||
@@ -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
45
timeutil/format.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -3,7 +3,9 @@ package util
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tx7do/kratos-utils/trans"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/tx7do/go-utils/trans"
|
||||||
)
|
)
|
||||||
|
|
||||||
var DefaultTimeLocation *time.Location
|
var DefaultTimeLocation *time.Location
|
||||||
@@ -114,3 +116,19 @@ func TimeToDateString(tm *time.Time) *string {
|
|||||||
}
|
}
|
||||||
return trans.String(tm.Format(DateLayout))
|
return trans.String(tm.Format(DateLayout))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TimestamppbToTime timestamppb.Timestamp -> time.Time
|
||||||
|
func TimestamppbToTime(tm *timestamppb.Timestamp) *time.Time {
|
||||||
|
if tm != nil {
|
||||||
|
return trans.Ptr(tm.AsTime())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeToTimestamppb time.Time -> timestamppb.Timestamp
|
||||||
|
func TimeToTimestamppb(tm *time.Time) *timestamppb.Timestamp {
|
||||||
|
if tm != nil {
|
||||||
|
return timestamppb.New(*tm)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tx7do/kratos-utils/trans"
|
"github.com/tx7do/go-utils/trans"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUnixMilliToStringPtr(t *testing.T) {
|
func TestUnixMilliToStringPtr(t *testing.T) {
|
||||||
18
trans/to.go
Normal file
18
trans/to.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//go:build go1.18
|
||||||
|
// +build go1.18
|
||||||
|
|
||||||
|
package trans
|
||||||
|
|
||||||
|
// Ptr returns a pointer to the provided value.
|
||||||
|
func Ptr[T any](v T) *T {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceOfPtrs returns a slice of *T from the specified values.
|
||||||
|
func SliceOfPtrs[T any](vv ...T) []*T {
|
||||||
|
slc := make([]*T, len(vv))
|
||||||
|
for i := range vv {
|
||||||
|
slc[i] = Ptr(vv[i])
|
||||||
|
}
|
||||||
|
return slc
|
||||||
|
}
|
||||||
30
trans/to_test.go
Normal file
30
trans/to_test.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
//go:build go1.18
|
||||||
|
// +build go1.18
|
||||||
|
|
||||||
|
package trans
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestPtr(t *testing.T) {
|
||||||
|
b := true
|
||||||
|
pb := Ptr(b)
|
||||||
|
if pb == nil {
|
||||||
|
t.Fatal("unexpected nil conversion")
|
||||||
|
}
|
||||||
|
if *pb != b {
|
||||||
|
t.Fatalf("got %v, want %v", *pb, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSliceOfPtrs(t *testing.T) {
|
||||||
|
arr := SliceOfPtrs[int]()
|
||||||
|
if len(arr) != 0 {
|
||||||
|
t.Fatal("expected zero length")
|
||||||
|
}
|
||||||
|
arr = SliceOfPtrs(1, 2, 3, 4, 5)
|
||||||
|
for i, v := range arr {
|
||||||
|
if *v != i+1 {
|
||||||
|
t.Fatal("values don't match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
upgrade.bat
Normal file
19
upgrade.bat
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
echo off
|
||||||
|
|
||||||
|
::指定起始文件夹
|
||||||
|
set DIR="%cd%"
|
||||||
|
|
||||||
|
go get all
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
cd %DIR%/bank_card
|
||||||
|
go get all
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
cd %DIR%/entgo
|
||||||
|
go get all
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
cd %DIR%/geoip
|
||||||
|
go get all
|
||||||
|
go mod tidy
|
||||||
7
uuid/test_trans.go
Normal file
7
uuid/test_trans.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package uuid
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestUUID(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
||||||
31
uuid/trans.go
Normal file
31
uuid/trans.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/tx7do/go-utils/trans"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToUuidPtr(str *string) *uuid.UUID {
|
||||||
|
var id *uuid.UUID
|
||||||
|
if str != nil {
|
||||||
|
_id, err := uuid.Parse(*str)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
id = &_id
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToUuid(str string) uuid.UUID {
|
||||||
|
id, _ := uuid.Parse(str)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToStringPtr(id *uuid.UUID) *string {
|
||||||
|
var strUUID *string
|
||||||
|
if id != nil {
|
||||||
|
strUUID = trans.String(id.String())
|
||||||
|
}
|
||||||
|
return strUUID
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user