Skip to content

Commit e494163

Browse files
authored
Merge pull request #130 from Eric-Guo/condition_like_in_chinese
✨Fix api/v1/users/condition end point can not handle filter with Chinese
2 parents b9db5f1 + 4b8ed64 commit e494163

File tree

7 files changed

+132
-15
lines changed

7 files changed

+132
-15
lines changed

cmd/sponge/commands/generate/template.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ database:
488488
# mysql settings
489489
mysql:
490490
# dsn format, <username>:<password>@(<hostname>:<port>)/<db>?[k=v& ......]
491-
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
491+
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8mb4&collation=utf8mb4_general_ci"
492492
enableLog: true # whether to turn on printing of all logs
493493
maxIdleConns: 10 # set the maximum number of connections in the idle connection pool
494494
maxOpenConns: 100 # set the maximum number of open database connections
@@ -535,7 +535,7 @@ database:
535535
# mysql settings
536536
mysql:
537537
# dsn format, <username>:<password>@(<hostname>:<port>)/<db>?[k=v& ......]
538-
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
538+
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8mb4&collation=utf8mb4_general_ci"
539539
enableLog: true # whether to turn on printing of all logs
540540
maxIdleConns: 10 # set the maximum number of connections in the idle connection pool
541541
maxOpenConns: 100 # set the maximum number of open database connections

configs/serverNameExample.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ database:
8686
# mysql settings
8787
mysql:
8888
# dsn format, <username>:<password>@(<hostname>:<port>)/<db>?[k=v& ......]
89-
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
89+
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8mb4&collation=utf8mb4_general_ci"
9090
enableLog: true # whether to turn on printing of all logs
9191
maxIdleConns: 10 # set the maximum number of connections in the idle connection pool
9292
maxOpenConns: 100 # set the maximum number of open database connections

pkg/conf/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ database:
1010
# mysql settings
1111
mysql:
1212
# dsn format, <user>:<pass>@(127.0.0.1:3306)/<db>?[k=v& ......]
13-
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
13+
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8mb4&collation=utf8mb4_general_ci"
1414

1515
# redis settings
1616
redis:

pkg/sgorm/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Support `mysql`, `postgresql`, `sqlite`.
1313
```go
1414
import "github.com/go-dev-frame/sponge/pkg/sgorm/mysql"
1515

16-
var dsn = "root:123456@(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
16+
var dsn = "root:123456@(127.0.0.1:3306)/test?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local"
1717

1818
// case 1: connect to the database using the default settings
1919
db, err := mysql.Init(dsn)

pkg/sgorm/mysql/mysql.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"github.com/go-dev-frame/sponge/pkg/sgorm/dbclose"
1818
"github.com/go-dev-frame/sponge/pkg/sgorm/glog"
19+
"github.com/go-dev-frame/sponge/pkg/utils"
1920
)
2021

2122
// Init mysql
@@ -35,7 +36,7 @@ func Init(dsn string, opts ...Option) (*gorm.DB, error) {
3536
if err != nil {
3637
return nil, err
3738
}
38-
db.Set("gorm:table_options", "CHARSET=utf8mb4") // automatic appending of table suffixes when creating tables
39+
db.Set("gorm:table_options", "CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci") // automatic appending of table suffixes when creating tables
3940

4041
// register trace plugin
4142
if o.enableTrace {
@@ -108,14 +109,14 @@ func rwSeparationPlugin(o *options) gorm.Plugin {
108109
slaves := []gorm.Dialector{}
109110
for _, dsn := range o.slavesDsn {
110111
slaves = append(slaves, mysqlDriver.New(mysqlDriver.Config{
111-
DSN: dsn,
112+
DSN: utils.AdaptiveMysqlDsn(dsn),
112113
}))
113114
}
114115

115116
masters := []gorm.Dialector{}
116117
for _, dsn := range o.mastersDsn {
117118
masters = append(masters, mysqlDriver.New(mysqlDriver.Config{
118-
DSN: dsn,
119+
DSN: utils.AdaptiveMysqlDsn(dsn),
119120
}))
120121
}
121122

pkg/sgorm/query/query_condition.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,13 @@ func (c *Column) checkExp() (string, error) {
145145
if !ok1 {
146146
return symbol, fmt.Errorf("invalid value type '%s'", c.Value)
147147
}
148-
l := len(val)
149-
if l > 2 {
150-
val2 := val[1 : l-1]
151-
val2 = strings.ReplaceAll(val2, "%", "\\%")
152-
val2 = strings.ReplaceAll(val2, "_", "\\_")
153-
val = string(val[0]) + val2 + string(val[l-1])
148+
// Use rune-safe slicing to preserve multi-byte characters
149+
r := []rune(val)
150+
if len(r) > 2 {
151+
middle := string(r[1 : len(r)-1])
152+
middle = strings.ReplaceAll(middle, "%", "\\%")
153+
middle = strings.ReplaceAll(middle, "_", "\\_")
154+
val = string(r[0]) + middle + string(r[len(r)-1])
154155
}
155156
if strings.HasPrefix(val, "%") ||
156157
strings.HasPrefix(val, "_") ||

pkg/utils/dsn.go

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,122 @@ import (
88

99
// AdaptiveMysqlDsn adaptation of various mysql format dsn address
1010
func AdaptiveMysqlDsn(dsn string) string {
11-
return strings.ReplaceAll(dsn, "mysql://", "")
11+
// remove optional scheme prefix
12+
dsn = strings.ReplaceAll(dsn, "mysql://", "")
13+
14+
dsn = ensureNetworkAddress(dsn)
15+
return ensureCharsetAndCollation(dsn)
16+
}
17+
18+
// helper: ensure network/address section is valid for go-sql-driver/mysql
19+
func ensureNetworkAddress(dsn string) string {
20+
at := strings.Index(dsn, "@")
21+
if at == -1 {
22+
return dsn
23+
}
24+
25+
afterAt := dsn[at+1:]
26+
slashIdx := strings.Index(afterAt, "/")
27+
if slashIdx == -1 {
28+
return dsn
29+
}
30+
31+
addrPart := afterAt[:slashIdx]
32+
if addrPart == "" {
33+
return dsn
34+
}
35+
36+
if strings.HasPrefix(addrPart, "(") {
37+
// missing protocol, add tcp
38+
return strings.Replace(dsn, "@(", "@tcp(", 1)
39+
}
40+
41+
if strings.HasPrefix(addrPart, "tcp(") || strings.HasPrefix(addrPart, "unix(") {
42+
return dsn
43+
}
44+
45+
// no parentheses and no protocol → wrap with tcp()
46+
return strings.Replace(dsn, "@"+addrPart, "@tcp("+addrPart+")", 1)
47+
}
48+
49+
// helper: ensure charset utf8mb4 and a reasonable collation are present
50+
func ensureCharsetAndCollation(dsn string) string {
51+
qIdx := strings.Index(dsn, "?")
52+
if qIdx == -1 {
53+
return dsn + "?charset=utf8mb4"
54+
}
55+
56+
prefix := dsn[:qIdx]
57+
queryStr := dsn[qIdx+1:]
58+
parts := strings.Split(queryStr, "&")
59+
60+
hasCharset := false
61+
hasCollation := false
62+
for i, p := range parts {
63+
if strings.HasPrefix(p, "charset=") {
64+
hasCharset = true
65+
parts[i] = "charset=" + normalizeCharsets(strings.TrimPrefix(p, "charset="))
66+
break
67+
}
68+
if strings.HasPrefix(p, "collation=") {
69+
hasCollation = true
70+
}
71+
}
72+
73+
if !hasCharset {
74+
parts = append(parts, "charset=utf8mb4")
75+
}
76+
if !hasCollation {
77+
parts = append(parts, "collation=utf8mb4_general_ci")
78+
}
79+
80+
return prefix + "?" + strings.Join(parts, "&")
81+
}
82+
83+
// normalizeCharsets deduplicates a comma-separated charset list and ensures utf8mb4 is first
84+
func normalizeCharsets(val string) string {
85+
pieces := strings.Split(val, ",")
86+
seen := map[string]bool{}
87+
ordered := []string{}
88+
for _, cs := range pieces {
89+
cs = strings.TrimSpace(cs)
90+
if cs == "" {
91+
continue
92+
}
93+
lower := strings.ToLower(cs)
94+
if seen[lower] {
95+
continue
96+
}
97+
seen[lower] = true
98+
ordered = append(ordered, cs)
99+
}
100+
101+
// ensure utf8mb4 is present and at the front (case-insensitive)
102+
found := -1
103+
for i, cs := range ordered {
104+
if strings.EqualFold(cs, "utf8mb4") {
105+
found = i
106+
break
107+
}
108+
}
109+
if found == -1 {
110+
ordered = append([]string{"utf8mb4"}, ordered...)
111+
} else if found != 0 {
112+
// move to front
113+
front := []string{"utf8mb4"}
114+
for i, cs := range ordered {
115+
if i == found {
116+
continue
117+
}
118+
if strings.EqualFold(cs, "utf8mb4") {
119+
continue
120+
}
121+
front = append(front, cs)
122+
}
123+
ordered = front
124+
}
125+
126+
return strings.Join(ordered, ",")
12127
}
13128

14129
// AdaptivePostgresqlDsn convert postgres dsn to kv string

0 commit comments

Comments
 (0)