Skip to content

Commit 53c3b91

Browse files
committed
Switch DSN style for driver, but still allow legacy form
Address feedback from review. The old style of DSN uses a `?` as path separator. This made parsing the DSN more complicated than necessary especially when adding in parsing of url parameters. By switching to the more standard `/` as a path separator we can use `url.Parse` to more easily split up the DSN and pull out the query strings. However... for compatibility we should still allow the old style. So what this commit does is: 1. Use a regex to identify whether a legacy or standard DSN style. We are assuming that if a "/" occurs after an "@" and no more "@" or "/" occur after that then it's the new/standard DSN style 2. Prefix the DSN with a scheme so we can use `url.Parse` 3. Only supports the url parameters (`ssl=X`) for the newer style DSN I.e. this shouldn't break anything for existing use cases, but does also allow people to start using TLS in the driver. There are probably edge cases in parsing of DSNs, but there would have been in the old way of doing it as well.
1 parent a962bc8 commit 53c3b91

File tree

1 file changed

+45
-59
lines changed

1 file changed

+45
-59
lines changed

driver/driver.go

+45-59
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"fmt"
1010
"io"
1111
"net/url"
12-
"strings"
12+
"regexp"
1313

1414
"github.com/go-mysql-org/go-mysql/client"
1515
"github.com/go-mysql-org/go-mysql/mysql"
@@ -23,12 +23,22 @@ var customTLSConfigMap = make(map[string]*tls.Config)
2323
type driver struct {
2424
}
2525

26-
// Open: DSN user:password@addr[?db]
26+
// Open: DSN
27+
// Support both legacy style DSNs: user:password@addr[?db]
28+
// And more standard DSNs: user:password@addr/db?param=value
29+
// Optional parameters are supported in the standard DSN
2730
func (d driver) Open(dsn string) (sqldriver.Conn, error) {
28-
lastIndex := strings.LastIndex(dsn, "@")
29-
seps := []string{dsn[:lastIndex], dsn[lastIndex+1:]}
30-
if len(seps) != 2 {
31-
return nil, errors.Errorf("invalid dsn, must user:password@addr[[?db[&param=X]]")
31+
// If a "/" occurs after "@" and then no more "@" or "/" occur after that
32+
standardDSN, matchErr := regexp.MatchString("@[^@]+/[^@/]+", dsn)
33+
if matchErr != nil {
34+
return nil, errors.Errorf("invalid dsn, must be user:password@addr[/db[?param=X]]")
35+
}
36+
37+
// Add a prefix so we can parse with url.Parse
38+
dsn = "mysql://" + dsn
39+
parsedDSN, parseErr := url.Parse(dsn)
40+
if parseErr != nil {
41+
return nil, errors.Errorf("invalid dsn, must be user:password@addr[/db[?param=X]]")
3242
}
3343

3444
var user string
@@ -38,62 +48,38 @@ func (d driver) Open(dsn string) (sqldriver.Conn, error) {
3848
var err error
3949
var c *client.Conn
4050

41-
if ss := strings.Split(seps[0], ":"); len(ss) == 2 {
42-
user, password = ss[0], ss[1]
43-
} else if len(ss) == 1 {
44-
user = ss[0]
45-
} else {
46-
return nil, errors.Errorf("invalid dsn, must user:password@addr[[?db[&param=X]]")
47-
}
48-
49-
params := make(map[string]string)
50-
if ss := strings.Split(seps[1], "?"); len(ss) == 2 {
51-
// If the dsn used a `/` for the path separator this would be easier to parse
52-
// with `url.Parse` and we could use `.Path` to get the db and then use
53-
// `Query()` to get the parameters and values.
54-
// But for consistency with the current way of doing things...
55-
addr = ss[0]
56-
dbAndParams := ss[1]
57-
if ss := strings.Split(dbAndParams, "&"); len(ss) == 1 {
58-
db = ss[0]
59-
} else {
60-
// We have to assume the first is the db
61-
// Then need to handle possible multiple parameters / query strings
62-
for i, p := range ss {
63-
if i == 0 {
64-
db = p
65-
} else {
66-
// Build key value pairs
67-
kv := strings.Split(p, "=")
68-
params[kv[0]] = kv[1]
69-
}
51+
user = parsedDSN.User.Username()
52+
// What does the below return for _. It's not err / bool
53+
password, _ = parsedDSN.User.Password()
54+
addr = parsedDSN.Host
55+
if standardDSN {
56+
// Remove slash
57+
db = parsedDSN.Path[1:]
58+
params := parsedDSN.Query()
59+
if params["ssl"] != nil {
60+
tlsConfigName := params.Get("ssl")
61+
switch tlsConfigName {
62+
case "true":
63+
// This actually does insecureSkipVerify
64+
// But not even sure if it makes sense to handle false? According to
65+
// client_test.go it doesn't - it'd result in an error
66+
c, err = client.Connect(addr, user, password, db, func(c *client.Conn) { c.UseSSL(true) })
67+
case "custom":
68+
// I was too concerned about mimicking what go-sql-driver/mysql does which will
69+
// allow any name for a custom tls profile and maps the query parameter value to
70+
// that TLSConfig variable... there is no need to be that clever.
71+
// Instead of doing that, let's store required custom TLSConfigs in a map that
72+
// uses the DSN address as the key
73+
c, err = client.Connect(addr, user, password, db, func(c *client.Conn) { c.SetTLSConfig(customTLSConfigMap[addr]) })
74+
default:
75+
return nil, errors.Errorf("Supported options are ssl=true or ssl=custom")
7076
}
71-
}
72-
} else if len(ss) == 1 {
73-
addr = ss[0]
74-
} else {
75-
return nil, errors.Errorf("invalid dsn, must user:password@addr[[?db[&param=X]]")
76-
}
77-
78-
tlsConfigName, tls := params["ssl"]
79-
if tls {
80-
switch tlsConfigName {
81-
case "true":
82-
// This actually does insecureSkipVerify
83-
// But not even sure if it makes sense to handle false? According to
84-
// client_test.go it doesn't - it'd result in an error
85-
c, err = client.Connect(addr, user, password, db, func(c *client.Conn) { c.UseSSL(true) })
86-
case "custom":
87-
// I was too concerned about mimicking what go-sql-driver/mysql does which will
88-
// allow any name for a custom tls profile and maps the query parameter value to
89-
// that TLSConfig variable... there is no need to be that clever.
90-
// Instead of doing that, let's store required custom TLSConfigs in a map that
91-
// uses the DSN address as the key
92-
c, err = client.Connect(addr, user, password, db, func(c *client.Conn) { c.SetTLSConfig(customTLSConfigMap[addr]) })
93-
default:
94-
return nil, errors.Errorf("Supported options are ssl=true or ssl=custom")
77+
} else {
78+
c, err = client.Connect(addr, user, password, db)
9579
}
9680
} else {
81+
// No more processing here. Let's only support url parameters with the newer style DSN
82+
db = parsedDSN.RawQuery
9783
c, err = client.Connect(addr, user, password, db)
9884
}
9985
if err != nil {

0 commit comments

Comments
 (0)