Compare commits

..

23 Commits

Author SHA1 Message Date
tx7do
d5819c6ef4 feat: upgrade go.mod. 2024-04-07 13:31:34 +08:00
tx7do
4d6b87fae6 feat: upgrade go.mod. 2024-04-07 13:29:42 +08:00
tx7do
df9f3f4430 feat: upgrade go.mod. 2024-04-07 13:26:43 +08:00
tx7do
871b028e34 feat: returns a pointer to the provided value use template function. 2024-04-07 13:19:53 +08:00
tx7do
dcbdf4b7a6 feat: ent filter support 3 params and json filed. 2023-11-29 20:36:41 +08:00
tx7do
c04968803a fix: #1 document error. 2023-11-24 13:10:52 +08:00
tx7do
093172ebca feat: ent update fields. 2023-11-08 18:17:47 +08:00
tx7do
d65e7bb928 feat: ent update fields. 2023-11-08 18:15:48 +08:00
tx7do
78452b1abf feat: field mask util. 2023-11-08 15:16:20 +08:00
tx7do
95ce578ddf feat: field mask util. 2023-11-06 20:15:07 +08:00
tx7do
413e14ac78 feat: ent select fields. 2023-11-06 18:40:47 +08:00
tx7do
50a2e139eb feat: ent select fields. 2023-11-06 17:57:40 +08:00
tx7do
18755155ba feat: ent select fields. 2023-11-06 17:02:55 +08:00
tx7do
da82d442cc feat: add. 2023-11-06 14:11:20 +08:00
tx7do
83bb818ade feat: dateutils, maputils, sliceutils. 2023-11-06 13:54:50 +08:00
tx7do
c37d726b4c feat: ent query filter. 2023-11-04 21:35:54 +08:00
tx7do
b27c96f932 feat: ent mixin. 2023-11-01 18:23:12 +08:00
tx7do
836f4e2461 feat: slug generate. 2023-10-31 07:05:48 +08:00
tx7do
5717d4aefe feat: entgo mixin. 2023-10-31 04:46:44 +08:00
tx7do
62142a6836 feat: entgo mixin. 2023-10-31 04:46:17 +08:00
tx7do
9990fa43e0 fix: pagination bug. 2023-10-28 20:14:31 +08:00
tx7do
82fbdc15d9 feat: byte utils 2023-10-28 20:06:50 +08:00
tx7do
b4188ca4d8 feat: io utils 2023-10-27 17:01:35 +08:00
56 changed files with 3644 additions and 379 deletions

26
.gitignore vendored
View File

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

View File

@@ -3,8 +3,8 @@ module github.com/tx7do/go-utils/bank_card
go 1.20
require (
github.com/mattn/go-sqlite3 v1.14.17
github.com/stretchr/testify v1.8.4
github.com/mattn/go-sqlite3 v1.14.22
github.com/stretchr/testify v1.9.0
)
require (

View File

@@ -1,11 +1,11 @@
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/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

44
byteutil/util.go Normal file
View 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
View 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
View 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))
}

View 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))
}

View File

@@ -1,18 +1,20 @@
module github.com/tx7do/go-utils/entgo
go 1.20
go 1.21
toolchain go1.22.1
require (
entgo.io/contrib v0.4.5
entgo.io/ent v0.12.4
github.com/go-kratos/kratos/v2 v2.7.1
github.com/google/uuid v1.3.1
github.com/stretchr/testify v1.8.4
github.com/tx7do/go-utils v1.1.0
entgo.io/ent v0.13.1
github.com/go-kratos/kratos/v2 v2.7.3
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.9.0
github.com/tx7do/go-utils v1.1.9
)
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/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/bufbuild/protocompile v0.6.0 // indirect
@@ -25,13 +27,14 @@ require (
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sony/sonyflake v1.2.0 // indirect
github.com/zclconf/go-cty v1.14.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/mod v0.15.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.14.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
golang.org/x/tools v0.18.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,22 +1,21 @@
ariga.io/atlas v0.15.0 h1:9lwSVcO/D3WgaCzstSGqR1hEDtsGibu6JqUofEI/0sY=
ariga.io/atlas v0.15.0/go.mod h1:isZrlzJ5cpoCoKFoY9knZug7Lq4pP1cm8g3XciLZ0Pw=
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 h1:GwdJbXydHCYPedeeLt4x/lrlIISQ4JTH1mRWuE5ZZ14=
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/go.mod h1:wpZyq2DJgthugFvDBlaqMXj9mV4/9ebyGEn7xlTVQqE=
entgo.io/ent v0.12.4 h1:LddPnAyxls/O7DTXZvUGDj0NZIdGSu317+aoNLJWbD8=
entgo.io/ent v0.12.4/go.mod h1:Y3JVAjtlIk8xVZYSn3t3mf8xlZIn5SAOXZQxD6kKI+Q=
entgo.io/ent v0.13.1 h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE=
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/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY=
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/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.1/go.mod h1:CPn82O93OLHjtnbuyOKhAG5TkSvw+mFnL32c4lZFDwU=
github.com/go-kratos/kratos/v2 v2.7.3 h1:T9MS69qk4/HkVUuHw5GS9PDVnOfzn+kxyF0CL5StqxA=
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/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
@@ -27,17 +26,20 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
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/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls=
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/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/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/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
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/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
@@ -45,35 +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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
github.com/stretchr/testify v1.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/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
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/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
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=
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/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
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/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
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 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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

21
entgo/mixin/creator_id.go Normal file
View 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(),
}
}

View 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
View 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"),
}
}

View File

@@ -29,6 +29,7 @@ func (SwitchStatus) Fields() []ent.Field {
// dialect.MySQL: "switch_status",
// dialect.Postgres: "switch_status",
//}).
Default("ON").
Values(
"OFF",
"ON",

View File

@@ -2,14 +2,15 @@
## 通用列表查询请求
| 字段名 | 类型 | 格式 | 字段描述 | 示例 | 备注 |
|----------|-----------|-------------------------------------|---------|----------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
| page | `number` | | 当前页码 | | 默认为`1`,最小值为`1`。 |
| pageSize | `number` | | 每页的行数 | | 默认为`10`,最小值为`1`。 |
| query | `string` | `json object``json object array` | AND过滤条件 | json字符串: `{"field1":"val1","field2":"val2"}` 或者`[{"field1":"val1"},{"field1":"val2"},{"field2":"val2"}]` | `map``array`都支持,当需要同字段名,不同值的情况下,请使用`array`。具体规则请见:[过滤规则](#过滤规则) |
| or | `string` | `json object``json object array` | OR过滤条件 | 同 AND过滤条件 | |
| orderBy | `string` | `json string array` | 排序条件 | json字符串`["-create_time", "type"]` | json的`string array`,字段名前加`-`是为降序,不加为升序。具体规则请见:[排序规则](#排序规则) |
| nopaging | `boolean` | | 是否不分页 | | 此字段为`true`时,`page``pageSize`字段的传入将无效用。 |
| 字段名 | 类型 | 格式 | 字段描述 | 示例 | 备注 |
|-----------|-----------|-------------------------------------|---------|----------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
| page | `number` | | 当前页码 | | 默认为`1`,最小值为`1`。 |
| pageSize | `number` | | 每页的行数 | | 默认为`10`,最小值为`1`。 |
| query | `string` | `json object``json object array` | AND过滤条件 | json字符串: `{"field1":"val1","field2":"val2"}` 或者`[{"field1":"val1"},{"field1":"val2"},{"field2":"val2"}]` | `map``array`都支持,当需要同字段名,不同值的情况下,请使用`array`。具体规则请见:[过滤规则](#过滤规则) |
| or | `string` | `json object``json object array` | OR过滤条件 | 同 AND过滤条件 | |
| orderBy | `string` | `json string array` | 排序条件 | json字符串`["-create_time", "type"]` | json的`string array`,字段名前加`-`是为降序,不加为升序。具体规则请见:[排序规则](#排序规则) |
| nopaging | `boolean` | | 是否不分页 | | 此字段为`true`时,`page``pageSize`字段的传入将无效用。 |
| fieldMask | `string` | `json string array` | 字段掩码 | | 此字段是`SELECT`条件,为空的时候是为`*`。 |
## 排序规则
@@ -37,29 +38,29 @@
{字段名}__{查找类型} : {值}
```
| 查找类型 | 示例 | SQL | 备注 |
|-------------|---------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|
| not | `{"name__not" : "tom"}` | `WHERE NOT ("name" = "tom")` | |
| in | `{"name__in" : ["tom", "jimmy"]}` | `WHERE name 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"` | |
| 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"` | |
| 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`,两个日期一致就会导致查询不到数据。 |
| isnull | `{"name__isnull" : "True"}` | `WHERE name IS NULL` | |
| not_isnull | `{"name__not_isnull" : "False"}` | `WHERE name IS NOT NULL` | |
| contains | `{"name__contains" : "L"}` | `WHERE name LIKE '%L%';` | |
| icontains | `{"name__icontains" : "L"}` | `WHERE name ILIKE '%L%';` | |
| startswith | `{"name__startswith" : "La"}` | `WHERE name LIKE 'La%';` | |
| istartswith | `{"name__istartswith" : "La"}` | `WHERE name ILIKE 'La%';` | |
| endswith | `{"name__endswith" : "a"}` | `WHERE name LIKE '%a';` | |
| iendswith | `{"name__iendswith" : "a"}` | `WHERE name ILIKE '%a';` | |
| exact | `{"name__exact" : "a"}` | `WHERE name LIKE '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) +';` | |
| 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 | | | |
| 查找类型 | 示例 | SQL | 备注 |
|-------------|---------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|
| not | `{"name__not" : "tom"}` | `WHERE NOT ("name" = "tom")` | |
| in | `{"name__in" : "[\"tom\", \"jimmy\"]"}` | `WHERE name 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"` | |
| 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"` | |
| 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`,两个日期一致就会导致查询不到数据。 |
| isnull | `{"name__isnull" : "True"}` | `WHERE name IS NULL` | |
| not_isnull | `{"name__not_isnull" : "False"}` | `WHERE name IS NOT NULL` | |
| contains | `{"name__contains" : "L"}` | `WHERE name LIKE '%L%';` | |
| icontains | `{"name__icontains" : "L"}` | `WHERE name ILIKE '%L%';` | |
| startswith | `{"name__startswith" : "La"}` | `WHERE name LIKE 'La%';` | |
| istartswith | `{"name__istartswith" : "La"}` | `WHERE name ILIKE 'La%';` | |
| endswith | `{"name__endswith" : "a"}` | `WHERE name LIKE '%a';` | |
| iendswith | `{"name__iendswith" : "a"}` | `WHERE name ILIKE '%a';` | |
| exact | `{"name__exact" : "a"}` | `WHERE name LIKE '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) +';` | |
| 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 | | | |
以及将日期提取出来的查找类型:

View File

@@ -2,55 +2,123 @@ package entgo
import (
"encoding/json"
"fmt"
"strings"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"github.com/go-kratos/kratos/v2/encoding"
"github.com/tx7do/go-utils/stringcase"
)
const (
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" // 全文搜索
)
type FilterOp int
const (
FilterDatePartDate = "date" // 日期
FilterDatePartYear = "year" //
FilterDatePartISOYear = "iso_year" // ISO 8601 一年中的周数
FilterDatePartQuarter = "quarter" // 季度
FilterDatePartMonth = "month" // 月
FilterDatePartWeek = "week" // ISO 8601 周编号 一年中的周数
FilterDatePartWeekDay = "week_day" // 星期几
FilterDatePartISOWeekDay = "iso_week_day" // 星期几
FilterDatePartDay = "day" // 日
FilterDatePartTime = "time" // 小时:分钟:秒
FilterDatePartHour = "hour" // 小时
FilterDatePartMinute = "minute" // 分钟
FilterDatePartSecond = "second" //
FilterDatePartMicrosecond = "microsecond" // 微秒
FilterNot DatePart = iota // 不等于
FilterIn // 检查值是否在列表中
FilterNotIn // 不在列表中
FilterGTE // 大于或等于传递的值
FilterGT // 大于传递值
FilterLTE // 小于或等于传递值
FilterLT // 小于传递值
FilterRange // 是否介于和给定的两个值之间
FilterIsNull // 是否为空
FilterNotIsNull // 是否不为空
FilterContains // 是否包含指定的子字符串
FilterInsensitiveContains // 不区分大小写,是否包含指定的子字符串
FilterStartsWith // 以值开头
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 查询命令转换为选择条件
func QueryCommandToWhereConditions(strJson string, isOr bool) (error, func(s *sql.Selector)) {
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 {
var cond *sql.Predicate
if len(keys) == 0 {
return nil
}
if len(value) == 0 {
return nil
}
if len(keys) == 1 {
field := keys[0]
cond = filterEqual(s, field, value)
} else if len(keys) == 2 {
if len(keys[0]) == 0 {
field := keys[0]
if len(field) == 0 {
return nil
}
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
}
field := keys[0]
op := strings.ToLower(keys[1])
switch op {
case FilterNot:
cond = filterNot(s, field, value)
case FilterIn:
cond = filterIn(s, field, value)
case FilterNotIn:
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)
var cond *sql.Predicate
if hasOperations(op) {
return processOp(s, p, op, field, value)
} else if hasDatePart(op) {
cond = filterDatePart(s, p, op, field).EQ("", value)
} else {
cond = filterJsonb(s, p, op, field).EQ("", 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
}
// filterEqual = 相等操作
// SQL: WHERE "name" = "tom"
func filterEqual(s *sql.Selector, field, value string) *sql.Predicate {
return sql.EQ(s.C(field), value)
func filterEqual(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return p.EQ(s.C(field), value)
}
// filterNot NOT 不相等操作
// SQL: WHERE NOT ("name" = "tom")
// 或者: WHERE "name" <> "tom"
// 用NOT可以过滤出NULL而用<>、!=则不能。
func filterNot(s *sql.Selector, field, value string) *sql.Predicate {
return sql.Not(sql.EQ(s.C(field), value))
func filterNot(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return p.Not().EQ(s.C(field), value)
}
// filterIn IN操作
// SQL: WHERE name IN ("tom", "jimmy")
func filterIn(s *sql.Selector, field, value string) *sql.Predicate {
var strs []string
if err := json.Unmarshal([]byte(value), &strs); err == nil {
return sql.In(s.C(field), strs)
func filterIn(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
var values []any
if err := json.Unmarshal([]byte(value), &values); err == nil {
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
}
// filterNotIn NOT IN操作
// SQL: WHERE name NOT IN ("tom", "jimmy")`
func filterNotIn(s *sql.Selector, field, value string) *sql.Predicate {
var strs []string
if err := json.Unmarshal([]byte(value), &strs); err == nil {
return sql.NotIn(s.C(field), strs)
func filterNotIn(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
var values []any
if err := json.Unmarshal([]byte(value), &values); err == nil {
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
}
// filterGTE GTE (Greater Than or Equal) 大于等于 >=操作
// SQL: WHERE "create_time" >= "2023-10-25"
func filterGTE(s *sql.Selector, field, value string) *sql.Predicate {
return sql.GTE(s.C(field), value)
func filterGTE(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return p.GTE(s.C(field), value)
}
// filterGT GT (Greater than) 大于 >操作
// SQL: WHERE "create_time" > "2023-10-25"
func filterGT(s *sql.Selector, field, value string) *sql.Predicate {
return sql.GT(s.C(field), value)
func filterGT(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return p.GT(s.C(field), value)
}
// filterLTE LTE (Less Than or Equal) 小于等于 <=操作
// SQL: WHERE "create_time" <= "2023-10-25"
func filterLTE(s *sql.Selector, field, value string) *sql.Predicate {
return sql.LTE(s.C(field), value)
func filterLTE(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return p.LTE(s.C(field), value)
}
// filterLT LT (Less than) 小于 <操作
// SQL: WHERE "create_time" < "2023-10-25"
func filterLT(s *sql.Selector, field, value string) *sql.Predicate {
return sql.LT(s.C(field), value)
func filterLT(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return p.LT(s.C(field), value)
}
// filterRange 在值域之中 BETWEEN操作
// SQL: WHERE "create_time" BETWEEN "2023-10-25" AND "2024-10-25"
// 或者: WHERE "create_time" >= "2023-10-25" AND "create_time" <= "2024-10-25"
func filterRange(s *sql.Selector, field, value string) *sql.Predicate {
var strs []string
if err := json.Unmarshal([]byte(value), &strs); err == nil {
if len(strs) != 2 {
func filterRange(s *sql.Selector, _ *sql.Predicate, field, value string) *sql.Predicate {
var values []any
if err := json.Unmarshal([]byte(value), &values); err == nil {
if len(values) != 2 {
return nil
}
return sql.And(
sql.GTE(s.C(field), strs[0]),
sql.LTE(s.C(field), strs[1]),
)
}
var float64s []float64
if err := json.Unmarshal([]byte(value), &float64s); err == nil {
if len(float64s) != 2 {
return nil
}
return sql.And(
sql.GTE(s.C(field), float64s[0]),
sql.LTE(s.C(field), float64s[1]),
sql.GTE(s.C(field), values[0]),
sql.LTE(s.C(field), values[1]),
)
}
@@ -287,90 +394,210 @@ func filterRange(s *sql.Selector, field, value string) *sql.Predicate {
// filterIsNull 为空 IS NULL操作
// SQL: WHERE name IS NULL
func filterIsNull(s *sql.Selector, field, _ string) *sql.Predicate {
return sql.IsNull(s.C(field))
func filterIsNull(s *sql.Selector, p *sql.Predicate, field, _ string) *sql.Predicate {
return p.IsNull(s.C(field))
}
// filterNotIsNull 不为空 IS NOT NULL操作
// filterIsNotNull 不为空 IS NOT NULL操作
// SQL: WHERE name IS NOT NULL
func filterNotIsNull(s *sql.Selector, field, _ string) *sql.Predicate {
return sql.Not(sql.IsNull(s.C(field)))
func filterIsNotNull(s *sql.Selector, p *sql.Predicate, field, _ string) *sql.Predicate {
return p.Not().IsNull(s.C(field))
}
// filterContains LIKE 前后模糊查询
// SQL: WHERE name LIKE '%L%';
func filterContains(s *sql.Selector, field, value string) *sql.Predicate {
return sql.Contains(s.C(field), value)
func filterContains(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return p.Contains(s.C(field), value)
}
// filterInsensitiveContains ILIKE 前后模糊查询
// SQL: WHERE name ILIKE '%L%';
func filterInsensitiveContains(s *sql.Selector, field, value string) *sql.Predicate {
return sql.ContainsFold(s.C(field), value)
func filterInsensitiveContains(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return p.ContainsFold(s.C(field), value)
}
// filterStartsWith LIKE 前缀+模糊查询
// SQL: WHERE name LIKE 'La%';
func filterStartsWith(s *sql.Selector, field, value string) *sql.Predicate {
return sql.HasPrefix(s.C(field), value)
func filterStartsWith(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return p.HasPrefix(s.C(field), value)
}
// filterInsensitiveStartsWith ILIKE 前缀+模糊查询
// SQL: WHERE name ILIKE 'La%';
func filterInsensitiveStartsWith(s *sql.Selector, field, value string) *sql.Predicate {
return sql.EqualFold(s.C(field), value+"%")
func filterInsensitiveStartsWith(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return p.EqualFold(s.C(field), value+"%")
}
// filterEndsWith LIKE 后缀+模糊查询
// SQL: WHERE name LIKE '%a';
func filterEndsWith(s *sql.Selector, field, value string) *sql.Predicate {
return sql.HasSuffix(s.C(field), value)
func filterEndsWith(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return p.HasSuffix(s.C(field), value)
}
// filterInsensitiveEndsWith ILIKE 后缀+模糊查询
// SQL: WHERE name ILIKE '%a';
func filterInsensitiveEndsWith(s *sql.Selector, field, value string) *sql.Predicate {
return sql.EqualFold(s.C(field), "%"+value)
func filterInsensitiveEndsWith(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return p.EqualFold(s.C(field), "%"+value)
}
// filterExact LIKE 操作 精确比对
// SQL: WHERE name LIKE 'a';
func filterExact(s *sql.Selector, field, value string) *sql.Predicate {
return sql.Like(s.C(field), value)
func filterExact(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return p.Like(s.C(field), value)
}
// filterInsensitiveExact ILIKE 操作 不区分大小写,精确比对
// SQL: WHERE name ILIKE 'a';
func filterInsensitiveExact(s *sql.Selector, field, value string) *sql.Predicate {
return sql.EqualFold(s.C(field), value)
func filterInsensitiveExact(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
return p.EqualFold(s.C(field), value)
}
// filterRegex LIKE 操作 精确比对
// filterRegex 正则查找
// MySQL: WHERE title REGEXP BINARY '^(An?|The) +'
// Oracle: WHERE REGEXP_LIKE(title, '^(An?|The) +', 'c');
// PostgreSQL: WHERE title ~ '^(An?|The) +';
// SQLite: WHERE title REGEXP '^(An?|The) +';
func filterRegex(s *sql.Selector, field, value string) *sql.Predicate {
return nil
func filterRegex(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
p.Append(func(b *sql.Builder) {
switch s.Builder.Dialect() {
case dialect.Postgres:
b.Ident(s.C(field)).WriteString(" ~ ")
b.Arg(value)
break
case dialect.MySQL:
b.Ident(s.C(field)).WriteString(" REGEXP BINARY ")
b.Arg(value)
break
case dialect.SQLite:
b.Ident(s.C(field)).WriteString(" REGEXP ")
b.Arg(value)
break
case dialect.Gremlin:
break
}
})
return p
}
// filterInsensitiveRegex ILIKE 操作 不区分大小写,精确比对
// filterInsensitiveRegex 正则查找 不区分大小写
// MySQL: WHERE title REGEXP '^(an?|the) +'
// Oracle: WHERE REGEXP_LIKE(title, '^(an?|the) +', 'i');
// PostgreSQL: WHERE title ~* '^(an?|the) +';
// SQLite: WHERE title REGEXP '(?i)^(an?|the) +';
func filterInsensitiveRegex(s *sql.Selector, field, value string) *sql.Predicate {
return nil
func filterInsensitiveRegex(s *sql.Selector, p *sql.Predicate, field, value string) *sql.Predicate {
p.Append(func(b *sql.Builder) {
switch s.Builder.Dialect() {
case dialect.Postgres:
b.Ident(s.C(field)).WriteString(" ~* ")
b.Arg(strings.ToLower(value))
break
case dialect.MySQL:
b.Ident(s.C(field)).WriteString(" REGEXP ")
b.Arg(strings.ToLower(value))
break
case dialect.SQLite:
b.Ident(s.C(field)).WriteString(" REGEXP ")
if !strings.HasPrefix(value, "(?i)") {
value = "(?i)" + value
}
b.Arg(strings.ToLower(value))
break
case dialect.Gremlin:
break
}
})
return p
}
// filterSearch 全文搜索
// SQL:
func filterSearch(s *sql.Selector, _, _ string) *sql.Predicate {
return nil
func filterSearch(s *sql.Selector, p *sql.Predicate, _, _ string) *sql.Predicate {
p.Append(func(b *sql.Builder) {
switch s.Builder.Dialect() {
}
})
return p
}
// filterDatePart 时间戳提取日期 select extract(quarter from timestamp '2018-08-15 12:10:10');
// SQL:
func filterDatePart(s *sql.Selector, datePart, field, value string) *sql.Predicate {
return nil
// filterDatePart 时间戳提取日期
// SQL: select extract(quarter from timestamp '2018-08-15 12:10:10');
func filterDatePart(s *sql.Selector, p *sql.Predicate, datePart, field string) *sql.Predicate {
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
View 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)
}

View File

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

View File

@@ -17,6 +17,13 @@ func BuildPaginationSelector(page, pageSize int32, noPaging bool) func(*sql.Sele
}
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))
}

View File

@@ -2,15 +2,16 @@ package entgo
import (
"entgo.io/ent/dialect/sql"
_ "github.com/go-kratos/kratos/v2/encoding/json"
)
// BuildQuerySelector 构建分页查询选择
// BuildQuerySelector 构建分页过滤查询器
func BuildQuerySelector(
dbDriverName string,
andFilterJsonString, orFilterJsonString string,
page, pageSize int32, noPaging bool,
orderBys []string, defaultOrderField string,
selectFields []string,
) (err error, whereSelectors []func(s *sql.Selector), querySelectors []func(s *sql.Selector)) {
err, whereSelectors = BuildFilterSelector(andFilterJsonString, orFilterJsonString)
if err != nil {
@@ -25,6 +26,9 @@ func BuildQuerySelector(
pageSelector := BuildPaginationSelector(page, pageSize, noPaging)
var fieldSelector func(s *sql.Selector)
err, fieldSelector = BuildFieldSelector(selectFields)
if len(whereSelectors) > 0 {
querySelectors = append(querySelectors, whereSelectors...)
}
@@ -35,6 +39,9 @@ func BuildQuerySelector(
if pageSelector != nil {
querySelectors = append(querySelectors, pageSelector)
}
if fieldSelector != nil {
querySelectors = append(querySelectors, fieldSelector)
}
return
}

View File

@@ -6,10 +6,12 @@ import (
"strings"
"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/json"
"github.com/stretchr/testify/assert"
)
func TestKratosJsonCodec(t *testing.T) {
@@ -92,3 +94,58 @@ func TestSplitQuery(t *testing.T) {
assert.Equal(t, keys[0], "id")
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
View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

@@ -3,18 +3,19 @@ module github.com/tx7do/go-utils/geoip
go 1.20
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/stretchr/testify v1.8.4
golang.org/x/text v0.13.0
github.com/stretchr/testify v1.9.0
golang.org/x/text v0.14.0
)
require (
github.com/davecgh/go-spew v1.1.1 // 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
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
)

View File

@@ -1,24 +1,26 @@
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/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.1/go.mod h1:CPn82O93OLHjtnbuyOKhAG5TkSvw+mFnL32c4lZFDwU=
github.com/go-kratos/kratos/v2 v2.7.3 h1:T9MS69qk4/HkVUuHw5GS9PDVnOfzn+kxyF0CL5StqxA=
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/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
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/go.mod h1:BHK6TvDyATVQhKNbQBdrj9eAvuwOMi2zSFXizL3K81Y=
github.com/oschwald/maxminddb-golang v1.11.0 h1:aSXMqYR/EPNjGE8epgqwDay+P30hCBZIveY0WZbAWh0=
github.com/oschwald/maxminddb-golang v1.11.0/go.mod h1:YmVI+H0zh3ySFR3w+oz8PCfglAFj3PuCmui13+P9zDg=
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
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=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
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 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

10
go.mod
View File

@@ -3,13 +3,19 @@ module github.com/tx7do/go-utils
go 1.20
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/stretchr/testify v1.8.4
golang.org/x/crypto v0.14.0
github.com/stretchr/testify v1.9.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 (
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/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect

21
go.sum
View File

@@ -1,6 +1,15 @@
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/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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
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/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
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=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
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 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@@ -1,82 +0,0 @@
package common
import (
"os"
"path/filepath"
)
func GetWorkingDirPath() string {
dir, err := os.Getwd()
if err != nil {
panic(err)
}
return dir
}
func GetExePath() string {
ex, err := os.Executable()
if err != nil {
panic(err)
}
exePath := filepath.Dir(ex)
return exePath
}
func GetAbsPath() string {
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
panic(err)
}
return dir
}
// PathExist 路径是否存在
func PathExist(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
// GetFileList 获取文件夹下面的所有文件的列表
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
}
// ReadFile 读取文件
func ReadFile(path string) []byte {
content, err := os.ReadFile(path)
if err != nil {
return nil
}
return content
}

12
ioutil/file.go Normal file
View File

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

279
ioutil/path.go Normal file
View 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
View File

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

94
maputils/maputils.go Normal file
View 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
View 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)
}

View File

@@ -7,13 +7,5 @@ const (
// GetPageOffset 计算偏移量
func GetPageOffset(pageNum, pageSize int32) int {
if pageNum < 1 {
pageNum = DefaultPage
}
if pageSize < 1 {
pageSize = DefaultPageSize
}
return int((pageNum - 1) * pageSize)
}

375
sliceutil/sliceutils.go Normal file
View 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
}

View 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
View 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
View 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
View File

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

View File

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

View File

@@ -1,6 +1,6 @@
git tag v1.1.0
git tag bank_card/v1.1.0
git tag entgo/v1.1.2
git tag geoip/v1.1.0
git tag v1.1.9
git tag bank_card/v1.1.1
git tag entgo/v1.1.12
git tag geoip/v1.1.1
git push origin --tags

View File

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

45
timeutil/format.go Normal file
View File

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

18
trans/to.go Normal file
View 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
View 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")
}
}
}

7
uuid/test_trans.go Normal file
View File

@@ -0,0 +1,7 @@
package uuid
import "testing"
func TestUUID(t *testing.T) {
}

31
uuid/trans.go Normal file
View 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
}