Compare commits

..

6 Commits

Author SHA1 Message Date
Bobo
bed43b3576 feat: query_parser support new one filter parse pattern. 2025-06-24 10:48:26 +08:00
Bobo
b9665d4f9b feat: entgo. 2025-06-24 00:25:03 +08:00
Bobo
1d53d3af4c feat: query_parser. 2025-06-23 23:46:37 +08:00
Bobo
a502539417 feat: query_parser + stringcase. 2025-06-23 23:44:49 +08:00
Bobo
0cd2eb95f2 feat: query_parser + stringcase. 2025-06-23 23:44:34 +08:00
Bobo
2e38fe77d7 feat: query_parser + stringcase. 2025-06-23 23:44:00 +08:00
10 changed files with 440 additions and 52 deletions

View File

@@ -1,26 +1,26 @@
module github.com/tx7do/go-utils/entgo
go 1.23.0
go 1.24
toolchain go1.23.2
toolchain go1.24.4
replace github.com/tx7do/go-utils/id => ../id
require (
entgo.io/contrib v0.6.0
entgo.io/ent v0.14.4
github.com/XSAM/otelsql v0.38.0
github.com/XSAM/otelsql v0.39.0
github.com/go-kratos/kratos/v2 v2.8.4
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.10.0
github.com/tx7do/go-utils v1.1.28
github.com/tx7do/go-utils v1.1.29
github.com/tx7do/go-utils/id v0.0.2
go.opentelemetry.io/otel v1.36.0
google.golang.org/protobuf v1.36.6
)
require (
ariga.io/atlas v0.34.0 // indirect
ariga.io/atlas v0.35.0 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/bmatcuk/doublestar v1.3.4 // indirect
@@ -43,15 +43,15 @@ require (
github.com/sony/sonyflake v1.2.1 // indirect
github.com/zclconf/go-cty v1.16.3 // indirect
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
go.mongodb.org/mongo-driver v1.17.3 // indirect
go.mongodb.org/mongo-driver v1.17.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/tools v0.33.0 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/tools v0.34.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,13 +1,13 @@
ariga.io/atlas v0.34.0 h1:4hdy+2x+xNs6Lx2anuJ/4Q7lCaqddbEj5CtRDVOBu0M=
ariga.io/atlas v0.34.0/go.mod h1:WJesu2UCpGQvgUh3oVP94EiRT61nNy1W/VN5g+vqP1I=
ariga.io/atlas v0.35.0 h1:tzco6CEZm1/jGD2ifHhKFlsQB7Bfsc/mty4zwm6Mlbc=
ariga.io/atlas v0.35.0/go.mod h1:9ZAIr/V85596AVxmN8edyVHYKKpnNsDMdnHLsEliW7k=
entgo.io/contrib v0.6.0 h1:xfo4TbJE7sJZWx7BV7YrpSz7IPFvS8MzL3fnfzZjKvQ=
entgo.io/contrib v0.6.0/go.mod h1:3qWIseJ/9Wx2Hu5zVh15FDzv7d/UvKNcYKdViywWCQg=
entgo.io/ent v0.14.4 h1:/DhDraSLXIkBhyiVoJeSshr4ZYi7femzhj6/TckzZuI=
entgo.io/ent v0.14.4/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM=
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/XSAM/otelsql v0.38.0 h1:zWU0/YM9cJhPE71zJcQ2EBHwQDp+G4AX2tPpljslaB8=
github.com/XSAM/otelsql v0.38.0/go.mod h1:5ePOgcLEkWvZtN9H3GV4BUlPeM3p3pzLDCnRG73X8h8=
github.com/XSAM/otelsql v0.39.0 h1:4o374mEIMweaeevL7fd8Q3C710Xi2Jh/c8G4Qy9bvCY=
github.com/XSAM/otelsql v0.39.0/go.mod h1:uMOXLUX+wkuAuP0AR3B45NXX7E9lJS2mERa8gqdU8R0=
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-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
@@ -71,34 +71,34 @@ github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0=
github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
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.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=

4
go.sum
View File

@@ -22,12 +22,8 @@ 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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=

View File

@@ -53,8 +53,13 @@ const (
)
const (
QueryDelimiter = "__" // 分隔符
JsonFieldDelimiter = "." // JSONB字段分隔符
JSONFilterFieldOperatorDelimiter = "__" // JSON过滤器 - 字段名和操作符的分隔符
QueryFilterFieldOperatorDelimiter = ":" // 自定义查询字符串过滤器 - 字段名和操作符的分隔符
QueryFilterQueriesDelimiter = "," // 自定义查询字符串过滤器 - 多个键值对的分隔符
QueryFilterValuesDelimiter = "|" // 自定义查询字符串过滤器 - 多个值的分隔符
JsonFieldDelimiter = "." // JSON字段分隔符
)
type FilterHandler func(field, operator, value string)
@@ -89,6 +94,40 @@ func ParseFilterJSONString(query string, handler FilterHandler) error {
return err
}
// ParseFilterQueryString 解析过滤条件的查询字符串,调用处理函数
func ParseFilterQueryString(query string, handler FilterHandler) error {
if query == "" {
return nil // 如果查询字符串为空,直接返回
}
// 按逗号分割查询字符串,得到多个键值对
pairs := SplitQueryQueries(query)
for _, pair := range pairs {
// 按冒号分割键值对,提取字段名和值
parts := SplitQueryFieldAndOperator(pair)
if len(parts) != 2 {
continue // 跳过无效的键值对
}
// 解码字段名
key, err := DecodeSpecialCharacters(strings.TrimSpace(parts[0]))
if err != nil {
continue // 跳过解码失败的键值对
}
// 解码字段值
value, err := DecodeSpecialCharacters(strings.TrimSpace(parts[1]))
if err != nil {
continue // 跳过解码失败的键值对
}
// 调用 ParseFilterField 解析字段和操作符
ParseFilterField(key, value, handler)
}
return nil
}
// ParseFilterField 解析过滤条件字符串,调用处理函数
func ParseFilterField(key, value string, handler FilterHandler) {
if key == "" || value == "" {
@@ -96,7 +135,7 @@ func ParseFilterField(key, value string, handler FilterHandler) {
}
// 处理字段和操作符
parts := SplitFieldAndOperator(key)
parts := SplitJsonFieldAndOperator(key)
if len(parts) < 1 {
return // 无效的字段格式
}
@@ -115,9 +154,24 @@ func ParseFilterField(key, value string, handler FilterHandler) {
handler(field, op, value)
}
// SplitFieldAndOperator 将字段字符串按分隔符分割字段名操作符
func SplitFieldAndOperator(field string) []string {
return strings.Split(field, QueryDelimiter)
// SplitJsonFieldAndOperator JSON过滤器 - 分割字段名”和“操作符
func SplitJsonFieldAndOperator(field string) []string {
return strings.Split(field, JSONFilterFieldOperatorDelimiter)
}
// SplitQueryFieldAndOperator 自定义查询字符串过滤器 - 分割“字段名”和“操作符”
func SplitQueryFieldAndOperator(field string) []string {
return strings.Split(field, QueryFilterFieldOperatorDelimiter)
}
// SplitQueryQueries 自定义查询字符串过滤器 - 分割多个键值对
func SplitQueryQueries(field string) []string {
return strings.Split(field, QueryFilterQueriesDelimiter)
}
// SplitQueryValues 自定义查询字符串过滤器 - 分割多个值
func SplitQueryValues(field string) []string {
return strings.Split(field, QueryFilterValuesDelimiter)
}
// SplitJSONField 将JSONB字段字符串按分隔符分割成多个字段

View File

@@ -58,7 +58,7 @@ func TestParseFilterJSONString(t *testing.T) {
err = ParseFilterJSONString(`{"na!me__exact":"Jo@hn"}`, handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
assert.Equal(t, "na!me", results[0].Field)
assert.Equal(t, "na_me", results[0].Field)
assert.Equal(t, "exact", results[0].Operator)
assert.Equal(t, "Jo@hn", results[0].Value)
@@ -67,10 +67,10 @@ func TestParseFilterJSONString(t *testing.T) {
err = ParseFilterJSONString(`[{"ag#e__gte":"30"},{"sta$tus__exact":"ac^tive"}]`, handler)
assert.NoError(t, err)
assert.Equal(t, 2, len(results))
assert.Equal(t, "ag#e", results[0].Field)
assert.Equal(t, "ag_e", results[0].Field)
assert.Equal(t, "gte", results[0].Operator)
assert.Equal(t, "30", results[0].Value)
assert.Equal(t, "sta$tus", results[1].Field)
assert.Equal(t, "sta_tus", results[1].Field)
assert.Equal(t, "exact", results[1].Operator)
assert.Equal(t, "ac^tive", results[1].Value)
@@ -81,6 +81,149 @@ func TestParseFilterJSONString(t *testing.T) {
assert.Equal(t, 0, len(results))
}
func TestParseFilterQueryString(t *testing.T) {
var results []struct {
Field string
Operator string
Value string
}
handler := func(field, operator, value string) {
results = append(results, struct {
Field string
Operator string
Value string
}{Field: field, Operator: operator, Value: value})
}
// 测试解析单个过滤条件
results = nil
err := ParseFilterQueryString("name__exact:John", handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
assert.Equal(t, "name", results[0].Field)
assert.Equal(t, "exact", results[0].Operator)
assert.Equal(t, "John", results[0].Value)
// 测试解析多个过滤条件
results = nil
err = ParseFilterQueryString("age__gte:30,status__exact:active", handler)
assert.NoError(t, err)
assert.Equal(t, 2, len(results))
assert.Equal(t, "age", results[0].Field)
assert.Equal(t, "gte", results[0].Operator)
assert.Equal(t, "30", results[0].Value)
assert.Equal(t, "status", results[1].Field)
assert.Equal(t, "exact", results[1].Operator)
assert.Equal(t, "active", results[1].Value)
// 测试空字符串
results = nil
err = ParseFilterQueryString("", handler)
assert.NoError(t, err)
assert.Equal(t, 0, len(results))
// 测试无效的查询字符串
results = nil
err = ParseFilterQueryString("invalid_query", handler)
assert.NoError(t, err)
assert.Equal(t, 0, len(results))
// 测试包含特殊字符的字段和值
results = nil
err = ParseFilterQueryString("na!me__exact:Jo@hn", handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
assert.Equal(t, "na_me", results[0].Field)
assert.Equal(t, "exact", results[0].Operator)
assert.Equal(t, "Jo@hn", results[0].Value)
// 测试包含特殊字符的多个过滤条件
results = nil
err = ParseFilterQueryString("ag#e__gte:30,sta$tus__exact:ac^tive", handler)
assert.NoError(t, err)
assert.Equal(t, 2, len(results))
assert.Equal(t, "ag_e", results[0].Field)
assert.Equal(t, "gte", results[0].Operator)
assert.Equal(t, "30", results[0].Value)
assert.Equal(t, "sta_tus", results[1].Field)
assert.Equal(t, "exact", results[1].Operator)
assert.Equal(t, "ac^tive", results[1].Value)
// 测试 Field 中包含分隔符
results = nil
err = ParseFilterQueryString("na:me__exact:John", handler)
assert.NoError(t, err)
assert.Equal(t, 0, len(results))
// 测试 Operator 中包含分隔符
results = nil
err = ParseFilterQueryString("name__ex:act:John", handler)
assert.NoError(t, err)
assert.Equal(t, 0, len(results))
// 测试 Value 中包含分隔符
results = nil
err = ParseFilterQueryString("name__exact:Jo|hn", handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
assert.Equal(t, "name", results[0].Field)
assert.Equal(t, "exact", results[0].Operator)
assert.Equal(t, "Jo|hn", results[0].Value)
// 测试多个过滤条件中包含分隔符
results = nil
err = ParseFilterQueryString("ag:e__gte:30,sta|tus__exact:ac|tive", handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
// 测试 Field 中包含编码后的分隔符
results = nil
encodedField := EncodeSpecialCharacters("na:me")
err = ParseFilterQueryString(encodedField+"__exact:John", handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
assert.Equal(t, "na_me", results[0].Field) // 注意:这里的字段名会被转换为 snake_case
assert.Equal(t, "exact", results[0].Operator)
assert.Equal(t, "John", results[0].Value)
// 测试 Operator 中包含编码后的分隔符
results = nil
encodedOperator := EncodeSpecialCharacters("ex:act")
err = ParseFilterQueryString("name__"+encodedOperator+":John", handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
assert.Equal(t, "name", results[0].Field)
assert.Equal(t, "ex:act", results[0].Operator)
assert.Equal(t, "John", results[0].Value)
// 测试 Value 中包含编码后的分隔符
results = nil
encodedValue := EncodeSpecialCharacters("Jo|hn")
err = ParseFilterQueryString("name__exact:"+encodedValue, handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
assert.Equal(t, "name", results[0].Field)
assert.Equal(t, "exact", results[0].Operator)
assert.Equal(t, "Jo|hn", results[0].Value)
// 测试多个过滤条件中包含编码后的分隔符
results = nil
encodedField1 := EncodeSpecialCharacters("ag:e")
encodedValue1 := EncodeSpecialCharacters("30")
encodedField2 := EncodeSpecialCharacters("sta|tus")
encodedValue2 := EncodeSpecialCharacters("ac|tive")
err = ParseFilterQueryString(encodedField1+"__gte:"+encodedValue1+","+encodedField2+"__exact:"+encodedValue2, handler)
assert.NoError(t, err)
assert.Equal(t, 2, len(results))
assert.Equal(t, "ag_e", results[0].Field) // 注意:这里的字段名会被转换为 snake_case
assert.Equal(t, "gte", results[0].Operator)
assert.Equal(t, "30", results[0].Value)
assert.Equal(t, "sta_tus", results[1].Field) // 注意:这里的字段名会被转换为 snake_case
assert.Equal(t, "exact", results[1].Operator)
assert.Equal(t, "ac|tive", results[1].Value)
}
func TestParseFilterField(t *testing.T) {
var results []struct {
Field string
@@ -128,25 +271,25 @@ func TestParseFilterField(t *testing.T) {
assert.Equal(t, 0, len(results))
}
func TestSplitFieldAndOperator(t *testing.T) {
func TestSplitJsonFieldAndOperator(t *testing.T) {
// 测试正常分割
result := SplitFieldAndOperator("name__exact")
result := SplitJsonFieldAndOperator("name__exact")
assert.Equal(t, 2, len(result))
assert.Equal(t, "name", result[0])
assert.Equal(t, "exact", result[1])
// 测试无操作符
result = SplitFieldAndOperator("name")
result = SplitJsonFieldAndOperator("name")
assert.Equal(t, 1, len(result))
assert.Equal(t, "name", result[0])
// 测试空字符串
result = SplitFieldAndOperator("")
result = SplitJsonFieldAndOperator("")
assert.Equal(t, 1, len(result))
assert.Equal(t, "", result[0])
// 测试多个分隔符
result = SplitFieldAndOperator("name__exact__extra")
result = SplitJsonFieldAndOperator("name__exact__extra")
assert.Equal(t, 3, len(result))
assert.Equal(t, "name", result[0])
assert.Equal(t, "exact", result[1])
@@ -179,3 +322,80 @@ func TestSplitJSONField(t *testing.T) {
assert.Equal(t, "address", result[2])
assert.Equal(t, "city", result[3])
}
func TestSplitQueryFieldAndOperator(t *testing.T) {
// 测试正常分割
result := SplitQueryFieldAndOperator("name:exact")
assert.Equal(t, 2, len(result))
assert.Equal(t, "name", result[0])
assert.Equal(t, "exact", result[1])
// 测试无操作符
result = SplitQueryFieldAndOperator("name")
assert.Equal(t, 1, len(result))
assert.Equal(t, "name", result[0])
// 测试空字符串
result = SplitQueryFieldAndOperator("")
assert.Equal(t, 1, len(result))
assert.Equal(t, "", result[0])
// 测试多个分隔符
result = SplitQueryFieldAndOperator("name:exact:extra")
assert.Equal(t, 3, len(result))
assert.Equal(t, "name", result[0])
assert.Equal(t, "exact", result[1])
assert.Equal(t, "extra", result[2])
}
func TestSplitQueryQueries(t *testing.T) {
// 测试正常分割多个键值对
result := SplitQueryQueries("name:John,age:30,status:active")
assert.Equal(t, 3, len(result))
assert.Equal(t, "name:John", result[0])
assert.Equal(t, "age:30", result[1])
assert.Equal(t, "status:active", result[2])
// 测试单个键值对
result = SplitQueryQueries("name:John")
assert.Equal(t, 1, len(result))
assert.Equal(t, "name:John", result[0])
// 测试空字符串
result = SplitQueryQueries("")
assert.Equal(t, 1, len(result))
assert.Equal(t, "", result[0])
// 测试多个分隔符
result = SplitQueryQueries("name:John,,age:30")
assert.Equal(t, 3, len(result))
assert.Equal(t, "name:John", result[0])
assert.Equal(t, "", result[1])
assert.Equal(t, "age:30", result[2])
}
func TestSplitQueryValues(t *testing.T) {
// 测试正常分割多个值
result := SplitQueryValues("value1|value2|value3")
assert.Equal(t, 3, len(result))
assert.Equal(t, "value1", result[0])
assert.Equal(t, "value2", result[1])
assert.Equal(t, "value3", result[2])
// 测试单个值
result = SplitQueryValues("value1")
assert.Equal(t, 1, len(result))
assert.Equal(t, "value1", result[0])
// 测试空字符串
result = SplitQueryValues("")
assert.Equal(t, 1, len(result))
assert.Equal(t, "", result[0])
// 测试多个分隔符
result = SplitQueryValues("value1||value2")
assert.Equal(t, 3, len(result))
assert.Equal(t, "value1", result[0])
assert.Equal(t, "", result[1])
assert.Equal(t, "value2", result[2])
}

View File

@@ -7,14 +7,13 @@ toolchain go1.23.2
require (
github.com/go-kratos/kratos/v2 v2.8.4
github.com/stretchr/testify v1.10.0
github.com/tx7do/go-utils v1.1.28
github.com/tx7do/go-utils v1.1.29
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

26
query_parser/go.sum Normal file
View File

@@ -0,0 +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.8.4 h1:eIJLE9Qq9WSoKx+Buy2uPyrahtF/lPh+Xf4MTpxhmjs=
github.com/go-kratos/kratos/v2 v2.8.4/go.mod h1:mq62W2101a5uYyRxe+7IdWubu7gZCGYqSNKwGFiiRcw=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
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=

13
query_parser/utils.go Normal file
View File

@@ -0,0 +1,13 @@
package query_parser
import "net/url"
// EncodeSpecialCharacters 对字符串进行编码
func EncodeSpecialCharacters(input string) string {
return url.QueryEscape(input)
}
// DecodeSpecialCharacters 对字符串进行解码
func DecodeSpecialCharacters(input string) (string, error) {
return url.QueryUnescape(input)
}

80
stringcase/README.md Normal file
View File

@@ -0,0 +1,80 @@
# 命名法转换
- camelCase 驼峰式命名法(大驼峰)
- PascalCase 帕斯卡命名法(小驼峰)
- snake_case 蛇形命名法
- kebab-case 烤肉串命名法
## camelCase 驼峰式命名法(大驼峰)
驼峰式命名法Camel case是一种不使用空格将多个单词连起来形成一个标识符的命名方式其中每个单词的首字母除了第一个单词如果使用小驼峰式命名法都大写就像骆驼的驼峰一样。
驼峰式命名法分为两种首字母小写的“小驼峰式”lowerCamelCase和首字母大写的“大驼峰式”UpperCamelCase也称为帕斯卡命名法PascalCase
- **小驼峰式(lowerCamelCase)**: 第一个单词的首字母小写,后续单词的首字母大写。例如:`myVariableName`
- **大驼峰式(UpperCamelCase)**: 每个单词的首字母都大写。例如MyVariableName也称为帕斯卡命名法PascalCase
在 JavaScript、Java和C#中,驼峰式大小写常用于变量和函数的命名。
```javascript
let firstName = "John";
let lastName = "Doe";
function printFullName(firstName, lastName) {
let fullName = firstName + " " + lastName;
console.log(fullName);
}
```
## PascalCase 帕斯卡命名法(小驼峰)
`PascalCase`,也称为`UpperCamelCase`,是一种在编程中使用的命名约定。它要求每个单词(包括第一个单词)的首字母都大写,并且单词之间没有空格或分隔符(如
`_`)。例如,`ThisIsPascalCase``MyClassName` 都是使用PascalCase的例子。
PascalCase 通常用于在 C#、Java 和TypeScript等语言中命名类、接口和其他类型。
```typescript
class Person {
firstName: string;
lastName: string;
constructor(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
}
printFullName(): void {
let fullName = this.firstName + " " + this.lastName;
console.log(fullName);
}
}
```
## snake_case 蛇形命名法
蛇形命名法是一种使用下划线 (`_`) 分隔单词的命名方式。之所以叫蛇形命名法,是因为"`snake_case`"的
下划线的形状类似于蛇腹上的鳞片。蛇形命名法通常用于Python、Ruby 和JavaScript等语言的变量名和函数名。
```python
first_name = "John"
last_name = "Doe"
def print_full_name(first_name, last_name):
full_name = first_name + " " + last_name
print(full_name)
```
## kebab-case 烤肉串命名法
`kebab-case烤肉串命名法`,也被称作 `kebab case``dash-case破折号式``hyphen-case连字符式``lisp-caseLisp 式)`
kebab-case 要求短语内的各个单词或缩写之间以`-`(连字符)做间隔。 例如:"`kebab-case`"。
短横线命名法通常用于 URL、文件名和 HTML/CSS 类名。
```html
<div class="user-profile">
<p>This is a user profile.</p>
</div>
```

View File

@@ -10,9 +10,9 @@ git tag slug/v0.0.1
git tag name_generator/v0.0.1
git tag mapper/v0.0.3
git tag password/v0.0.1
git tag query_parser/v0.0.1
git tag query_parser/v0.0.2
git tag entgo/v1.1.31
git tag entgo/v1.1.32
git tag gorm/v1.1.6
git push origin --tags