Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/sponge/commands/generate/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ database:
# mysql settings
mysql:
# dsn format, <username>:<password>@(<hostname>:<port>)/<db>?[k=v& ......]
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8mb4&collation=utf8mb4_general_ci"
enableLog: true # whether to turn on printing of all logs
maxIdleConns: 10 # set the maximum number of connections in the idle connection pool
maxOpenConns: 100 # set the maximum number of open database connections
Expand Down Expand Up @@ -535,7 +535,7 @@ database:
# mysql settings
mysql:
# dsn format, <username>:<password>@(<hostname>:<port>)/<db>?[k=v& ......]
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8mb4&collation=utf8mb4_general_ci"
enableLog: true # whether to turn on printing of all logs
maxIdleConns: 10 # set the maximum number of connections in the idle connection pool
maxOpenConns: 100 # set the maximum number of open database connections
Expand Down
2 changes: 1 addition & 1 deletion configs/serverNameExample.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ database:
# mysql settings
mysql:
# dsn format, <username>:<password>@(<hostname>:<port>)/<db>?[k=v& ......]
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8mb4&collation=utf8mb4_general_ci"
enableLog: true # whether to turn on printing of all logs
maxIdleConns: 10 # set the maximum number of connections in the idle connection pool
maxOpenConns: 100 # set the maximum number of open database connections
Expand Down
2 changes: 1 addition & 1 deletion pkg/conf/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ database:
# mysql settings
mysql:
# dsn format, <user>:<pass>@(127.0.0.1:3306)/<db>?[k=v& ......]
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8mb4&collation=utf8mb4_general_ci"

# redis settings
redis:
Expand Down
2 changes: 1 addition & 1 deletion pkg/sgorm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Support `mysql`, `postgresql`, `sqlite`.
```go
import "github.com/go-dev-frame/sponge/pkg/sgorm/mysql"

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

// case 1: connect to the database using the default settings
db, err := mysql.Init(dsn)
Expand Down
7 changes: 4 additions & 3 deletions pkg/sgorm/mysql/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

"github.com/go-dev-frame/sponge/pkg/sgorm/dbclose"
"github.com/go-dev-frame/sponge/pkg/sgorm/glog"
"github.com/go-dev-frame/sponge/pkg/utils"
)

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

// register trace plugin
if o.enableTrace {
Expand Down Expand Up @@ -108,14 +109,14 @@ func rwSeparationPlugin(o *options) gorm.Plugin {
slaves := []gorm.Dialector{}
for _, dsn := range o.slavesDsn {
slaves = append(slaves, mysqlDriver.New(mysqlDriver.Config{
DSN: dsn,
DSN: utils.AdaptiveMysqlDsn(dsn),
}))
}

masters := []gorm.Dialector{}
for _, dsn := range o.mastersDsn {
masters = append(masters, mysqlDriver.New(mysqlDriver.Config{
DSN: dsn,
DSN: utils.AdaptiveMysqlDsn(dsn),
}))
}

Expand Down
13 changes: 7 additions & 6 deletions pkg/sgorm/query/query_condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,13 @@ func (c *Column) checkExp() (string, error) {
if !ok1 {
return symbol, fmt.Errorf("invalid value type '%s'", c.Value)
}
l := len(val)
if l > 2 {
val2 := val[1 : l-1]
val2 = strings.ReplaceAll(val2, "%", "\\%")
val2 = strings.ReplaceAll(val2, "_", "\\_")
val = string(val[0]) + val2 + string(val[l-1])
// Use rune-safe slicing to preserve multi-byte characters
r := []rune(val)
if len(r) > 2 {
middle := string(r[1 : len(r)-1])
middle = strings.ReplaceAll(middle, "%", "\\%")
middle = strings.ReplaceAll(middle, "_", "\\_")
val = string(r[0]) + middle + string(r[len(r)-1])
}
if strings.HasPrefix(val, "%") ||
strings.HasPrefix(val, "_") ||
Expand Down
117 changes: 116 additions & 1 deletion pkg/utils/dsn.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,122 @@ import (

// AdaptiveMysqlDsn adaptation of various mysql format dsn address
func AdaptiveMysqlDsn(dsn string) string {
return strings.ReplaceAll(dsn, "mysql://", "")
// remove optional scheme prefix
dsn = strings.ReplaceAll(dsn, "mysql://", "")

dsn = ensureNetworkAddress(dsn)
return ensureCharsetAndCollation(dsn)
}

// helper: ensure network/address section is valid for go-sql-driver/mysql
func ensureNetworkAddress(dsn string) string {
at := strings.Index(dsn, "@")
if at == -1 {
return dsn
}

afterAt := dsn[at+1:]
slashIdx := strings.Index(afterAt, "/")
if slashIdx == -1 {
return dsn
}

addrPart := afterAt[:slashIdx]
if addrPart == "" {
return dsn
}

if strings.HasPrefix(addrPart, "(") {
// missing protocol, add tcp
return strings.Replace(dsn, "@(", "@tcp(", 1)
}

if strings.HasPrefix(addrPart, "tcp(") || strings.HasPrefix(addrPart, "unix(") {
return dsn
}

// no parentheses and no protocol → wrap with tcp()
return strings.Replace(dsn, "@"+addrPart, "@tcp("+addrPart+")", 1)
}

// helper: ensure charset utf8mb4 and a reasonable collation are present
func ensureCharsetAndCollation(dsn string) string {
qIdx := strings.Index(dsn, "?")
if qIdx == -1 {
return dsn + "?charset=utf8mb4"
}

prefix := dsn[:qIdx]
queryStr := dsn[qIdx+1:]
parts := strings.Split(queryStr, "&")

hasCharset := false
hasCollation := false
for i, p := range parts {
if strings.HasPrefix(p, "charset=") {
hasCharset = true
parts[i] = "charset=" + normalizeCharsets(strings.TrimPrefix(p, "charset="))
break
}
if strings.HasPrefix(p, "collation=") {
hasCollation = true
}
}

if !hasCharset {
parts = append(parts, "charset=utf8mb4")
}
if !hasCollation {
parts = append(parts, "collation=utf8mb4_general_ci")
}

return prefix + "?" + strings.Join(parts, "&")
}

// normalizeCharsets deduplicates a comma-separated charset list and ensures utf8mb4 is first
func normalizeCharsets(val string) string {
pieces := strings.Split(val, ",")
seen := map[string]bool{}
ordered := []string{}
for _, cs := range pieces {
cs = strings.TrimSpace(cs)
if cs == "" {
continue
}
lower := strings.ToLower(cs)
if seen[lower] {
continue
}
seen[lower] = true
ordered = append(ordered, cs)
}

// ensure utf8mb4 is present and at the front (case-insensitive)
found := -1
for i, cs := range ordered {
if strings.EqualFold(cs, "utf8mb4") {
found = i
break
}
}
if found == -1 {
ordered = append([]string{"utf8mb4"}, ordered...)
} else if found != 0 {
// move to front
front := []string{"utf8mb4"}
for i, cs := range ordered {
if i == found {
continue
}
if strings.EqualFold(cs, "utf8mb4") {
continue
}
front = append(front, cs)
}
ordered = front
}

return strings.Join(ordered, ",")
}

// AdaptivePostgresqlDsn convert postgres dsn to kv string
Expand Down