Skip to content

Commit 00dc21a

Browse files
authored
allow unknown collation name (#1604)
Fix #1603
1 parent 2f15276 commit 00dc21a

File tree

6 files changed

+51
-50
lines changed

6 files changed

+51
-50
lines changed

Diff for: collations.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
package mysql
1010

11-
const defaultCollation = "utf8mb4_general_ci"
11+
const defaultCollationID = 45 // utf8mb4_general_ci
1212
const binaryCollationID = 63
1313

1414
// A list of available collations mapped to the internal ID.

Diff for: connection.go

+9-34
Original file line numberDiff line numberDiff line change
@@ -67,45 +67,20 @@ func (mc *mysqlConn) handleParams() (err error) {
6767
var cmdSet strings.Builder
6868

6969
for param, val := range mc.cfg.Params {
70-
switch param {
71-
// Charset: character_set_connection, character_set_client, character_set_results
72-
case "charset":
73-
charsets := strings.Split(val, ",")
74-
for _, cs := range charsets {
75-
// ignore errors here - a charset may not exist
76-
if mc.cfg.Collation != "" {
77-
err = mc.exec("SET NAMES " + cs + " COLLATE " + mc.cfg.Collation)
78-
} else {
79-
err = mc.exec("SET NAMES " + cs)
80-
}
81-
if err == nil {
82-
break
83-
}
84-
}
85-
if err != nil {
86-
return
87-
}
88-
89-
// Other system vars accumulated in a single SET command
90-
default:
91-
if cmdSet.Len() == 0 {
92-
// Heuristic: 29 chars for each other key=value to reduce reallocations
93-
cmdSet.Grow(4 + len(param) + 3 + len(val) + 30*(len(mc.cfg.Params)-1))
94-
cmdSet.WriteString("SET ")
95-
} else {
96-
cmdSet.WriteString(", ")
97-
}
98-
cmdSet.WriteString(param)
99-
cmdSet.WriteString(" = ")
100-
cmdSet.WriteString(val)
70+
if cmdSet.Len() == 0 {
71+
// Heuristic: 29 chars for each other key=value to reduce reallocations
72+
cmdSet.Grow(4 + len(param) + 3 + len(val) + 30*(len(mc.cfg.Params)-1))
73+
cmdSet.WriteString("SET ")
74+
} else {
75+
cmdSet.WriteString(", ")
10176
}
77+
cmdSet.WriteString(param)
78+
cmdSet.WriteString(" = ")
79+
cmdSet.WriteString(val)
10280
}
10381

10482
if cmdSet.Len() > 0 {
10583
err = mc.exec(cmdSet.String())
106-
if err != nil {
107-
return
108-
}
10984
}
11085

11186
return

Diff for: connector.go

+19
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,25 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
180180
mc.maxWriteSize = mc.maxAllowedPacket
181181
}
182182

183+
// Charset: character_set_connection, character_set_client, character_set_results
184+
if len(mc.cfg.charsets) > 0 {
185+
for _, cs := range mc.cfg.charsets {
186+
// ignore errors here - a charset may not exist
187+
if mc.cfg.Collation != "" {
188+
err = mc.exec("SET NAMES " + cs + " COLLATE " + mc.cfg.Collation)
189+
} else {
190+
err = mc.exec("SET NAMES " + cs)
191+
}
192+
if err == nil {
193+
break
194+
}
195+
}
196+
if err != nil {
197+
mc.Close()
198+
return nil, err
199+
}
200+
}
201+
183202
// Handle DSN Params
184203
err = mc.handleParams()
185204
if err != nil {

Diff for: dsn.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ type Config struct {
4444
DBName string // Database name
4545
Params map[string]string // Connection parameters
4646
ConnectionAttributes string // Connection Attributes, comma-delimited string of user-defined "key:value" pairs
47-
Collation string // Connection collation
47+
charsets []string // Connection charset. When set, this will be set in SET NAMES <charset> query
48+
Collation string // Connection collation. When set, this will be set in SET NAMES <charset> COLLATE <collation> query
4849
Loc *time.Location // Location for time.Time values
4950
MaxAllowedPacket int // Max packet size allowed
5051
ServerPubKey string // Server public key name
@@ -282,6 +283,10 @@ func (cfg *Config) FormatDSN() string {
282283
writeDSNParam(&buf, &hasParam, "clientFoundRows", "true")
283284
}
284285

286+
if charsets := cfg.charsets; len(charsets) > 0 {
287+
writeDSNParam(&buf, &hasParam, "charset", strings.Join(charsets, ","))
288+
}
289+
285290
if col := cfg.Collation; col != "" {
286291
writeDSNParam(&buf, &hasParam, "collation", col)
287292
}
@@ -501,6 +506,10 @@ func parseDSNParams(cfg *Config, params string) (err error) {
501506
return errors.New("invalid bool value: " + value)
502507
}
503508

509+
// charset
510+
case "charset":
511+
cfg.charsets = strings.Split(value, ",")
512+
504513
// Collation
505514
case "collation":
506515
cfg.Collation = value

Diff for: dsn_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ var testDSNs = []struct {
3131
&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, ColumnsWithAlias: true, MultiStatements: true},
3232
}, {
3333
"user@unix(/path/to/socket)/dbname?charset=utf8",
34-
&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
34+
&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", charsets: []string{"utf8"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
3535
}, {
3636
"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true",
37-
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "true"},
37+
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", charsets: []string{"utf8"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "true"},
3838
}, {
3939
"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify",
40-
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "skip-verify"},
40+
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", charsets: []string{"utf8mb4", "utf8"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "skip-verify"},
4141
}, {
4242
"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216&tls=false&allowCleartextPasswords=true&parseTime=true&rejectReadOnly=true",
4343
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, TLSConfig: "false", AllowCleartextPasswords: true, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, Logger: defaultLogger, AllowAllFiles: true, AllowOldPasswords: true, CheckConnLiveness: true, ClientFoundRows: true, MaxAllowedPacket: 16777216, ParseTime: true, RejectReadOnly: true},

Diff for: packets.go

+9-11
Original file line numberDiff line numberDiff line change
@@ -322,17 +322,15 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
322322
data[11] = 0x00
323323

324324
// Collation ID [1 byte]
325-
cname := mc.cfg.Collation
326-
if cname == "" {
327-
cname = defaultCollation
328-
}
329-
var found bool
330-
data[12], found = collations[cname]
331-
if !found {
332-
// Note possibility for false negatives:
333-
// could be triggered although the collation is valid if the
334-
// collations map does not contain entries the server supports.
335-
return fmt.Errorf("unknown collation: %q", cname)
325+
data[12] = defaultCollationID
326+
if cname := mc.cfg.Collation; cname != "" {
327+
colID, ok := collations[cname]
328+
if ok {
329+
data[12] = colID
330+
} else if len(mc.cfg.charsets) > 0 {
331+
// When cfg.charset is set, the collation is set by `SET NAMES <charset> COLLATE <collation>`.
332+
return fmt.Errorf("unknown collation: %q", cname)
333+
}
336334
}
337335

338336
// Filler [23 bytes] (all 0x00)

0 commit comments

Comments
 (0)