Skip to content

Commit 82cddef

Browse files
committedMar 15, 2016
Add support for connection attributes.
This sets attribute _client_name with the value "Go MySQL Driver" Also sets _os, _platform, _pid and program_name by default. This also decodes the uppper two bytes of the capability flags. The dsn_test.go only tests for one attribute because there is no guaranteed sort order for a map and Printf %+v as used by TestDSNParser().
1 parent 66312f7 commit 82cddef

File tree

6 files changed

+143
-30
lines changed

6 files changed

+143
-30
lines changed
 

‎README.md

+10
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,16 @@ Sets the collation used for client-server interaction on connection. In contrast
169169

170170
A list of valid charsets for a server is retrievable with `SHOW COLLATION`.
171171

172+
##### `connattrs`
173+
174+
```
175+
Type: comma seperated string of name/value pairs
176+
Valid Values: (<name1>=<value1>,<name2>=<value2>,...)
177+
Default: none
178+
```
179+
180+
Sends custom connection attributes to the server.
181+
172182
##### `clientFoundRows`
173183

174184
```

‎driver.go

+13
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ import (
2020
"database/sql"
2121
"database/sql/driver"
2222
"net"
23+
"os"
24+
"os/user"
25+
"strconv"
2326
)
2427

2528
// MySQLDriver is exported to make the driver directly accessible.
@@ -30,6 +33,10 @@ type MySQLDriver struct{}
3033
// Custom dial functions must be registered with RegisterDial
3134
type DialFunc func(addr string) (net.Conn, error)
3235

36+
var pid string
37+
var os_user string
38+
var os_user_full string
39+
3340
var dials map[string]DialFunc
3441

3542
// RegisterDial registers a custom dial function. It can then be used by the
@@ -163,5 +170,11 @@ func handleAuthResult(mc *mysqlConn, cipher []byte) error {
163170
}
164171

165172
func init() {
173+
pid = strconv.Itoa(os.Getpid())
174+
os_user_entry, err := user.Current()
175+
if err == nil {
176+
os_user_full = os_user_entry.Name
177+
os_user = os_user_entry.Username
178+
}
166179
sql.Register("mysql", &MySQLDriver{})
167180
}

‎driver_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
)
2929

3030
var (
31-
user string
31+
dbuser string
3232
pass string
3333
prot string
3434
addr string
@@ -57,13 +57,13 @@ func init() {
5757
}
5858
return defaultValue
5959
}
60-
user = env("MYSQL_TEST_USER", "root")
60+
dbuser = env("MYSQL_TEST_USER", "root")
6161
pass = env("MYSQL_TEST_PASS", "")
6262
prot = env("MYSQL_TEST_PROT", "tcp")
6363
addr = env("MYSQL_TEST_ADDR", "localhost:3306")
6464
dbname = env("MYSQL_TEST_DBNAME", "gotest")
6565
netAddr = fmt.Sprintf("%s(%s)", prot, addr)
66-
dsn = fmt.Sprintf("%s:%s@%s/%s?timeout=30s&strict=true", user, pass, netAddr, dbname)
66+
dsn = fmt.Sprintf("%s:%s@%s/%s?timeout=30s&strict=true", dbuser, pass, netAddr, dbname)
6767
c, err := net.Dial(prot, addr)
6868
if err == nil {
6969
available = true
@@ -1693,7 +1693,7 @@ func TestCustomDial(t *testing.T) {
16931693
return net.Dial(prot, addr)
16941694
})
16951695

1696-
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@mydial(%s)/%s?timeout=30s&strict=true", user, pass, addr, dbname))
1696+
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@mydial(%s)/%s?timeout=30s&strict=true", dbuser, pass, addr, dbname))
16971697
if err != nil {
16981698
t.Fatalf("error connecting: %s", err.Error())
16991699
}
@@ -1790,7 +1790,7 @@ func TestUnixSocketAuthFail(t *testing.T) {
17901790
}
17911791
}
17921792
t.Logf("socket: %s", socket)
1793-
badDSN := fmt.Sprintf("%s:%s@unix(%s)/%s?timeout=30s&strict=true", user, badPass, socket, dbname)
1793+
badDSN := fmt.Sprintf("%s:%s@unix(%s)/%s?timeout=30s&strict=true", dbuser, badPass, socket, dbname)
17941794
db, err := sql.Open("mysql", badDSN)
17951795
if err != nil {
17961796
t.Fatalf("error connecting: %s", err.Error())

‎dsn.go

+42-10
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,16 @@ type Config struct {
4242
ReadTimeout time.Duration // I/O read timeout
4343
WriteTimeout time.Duration // I/O write timeout
4444

45-
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
46-
AllowCleartextPasswords bool // Allows the cleartext client side plugin
47-
AllowOldPasswords bool // Allows the old insecure password method
48-
ClientFoundRows bool // Return number of matching rows instead of rows changed
49-
ColumnsWithAlias bool // Prepend table alias to column names
50-
InterpolateParams bool // Interpolate placeholders into query string
51-
MultiStatements bool // Allow multiple statements in one query
52-
ParseTime bool // Parse time values to time.Time
53-
Strict bool // Return warnings as errors
45+
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
46+
AllowCleartextPasswords bool // Allows the cleartext client side plugin
47+
AllowOldPasswords bool // Allows the old insecure password method
48+
ClientFoundRows bool // Return number of matching rows instead of rows changed
49+
ColumnsWithAlias bool // Prepend table alias to column names
50+
InterpolateParams bool // Interpolate placeholders into query string
51+
MultiStatements bool // Allow multiple statements in one query
52+
ParseTime bool // Parse time values to time.Time
53+
Strict bool // Return warnings as errors
54+
ConnAttrs map[string]string // Connection Attributes
5455
}
5556

5657
// FormatDSN formats the given Config into a DSN string which can be passed to
@@ -222,6 +223,27 @@ func (cfg *Config) FormatDSN() string {
222223
buf.WriteString(cfg.WriteTimeout.String())
223224
}
224225

226+
if len(cfg.ConnAttrs) != 0 {
227+
if hasParam {
228+
buf.WriteString("&connattrs=(")
229+
} else {
230+
hasParam = true
231+
buf.WriteString("?connattrs=(")
232+
}
233+
firstAttr := true
234+
for attrname, attrvalue := range cfg.ConnAttrs {
235+
if firstAttr {
236+
firstAttr = false
237+
} else {
238+
buf.WriteString(",")
239+
}
240+
buf.WriteString(attrname)
241+
buf.WriteString("=")
242+
buf.WriteString(attrvalue)
243+
}
244+
buf.WriteString(")")
245+
}
246+
225247
// other params
226248
if cfg.Params != nil {
227249
for param, value := range cfg.Params {
@@ -496,7 +518,17 @@ func parseDSNParams(cfg *Config, params string) (err error) {
496518
if err != nil {
497519
return
498520
}
499-
521+
case "connattrs":
522+
if cfg.ConnAttrs == nil {
523+
cfg.ConnAttrs = make(map[string]string)
524+
}
525+
for _, conn_v := range strings.Split(strings.Trim(value, "()"), ",") {
526+
attr := strings.SplitN(conn_v, "=", 2)
527+
if len(attr) != 2 {
528+
return fmt.Errorf("Invalid connection attribute: %s", conn_v)
529+
}
530+
cfg.ConnAttrs[attr[0]] = attr[1]
531+
}
500532
default:
501533
// lazy init
502534
if cfg.Params == nil {

‎dsn_test.go

+15-14
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,21 @@ var testDSNs = []struct {
1919
in string
2020
out string
2121
}{
22-
{"username:password@protocol(address)/dbname?param=value", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
23-
{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:true InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
24-
{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:true InterpolateParams:false MultiStatements:true ParseTime:false Strict:false}"},
25-
{"user@unix(/path/to/socket)/dbname?charset=utf8", "&{User:user Passwd: Net:unix Addr:/path/to/socket DBName:dbname Params:map[charset:utf8] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
26-
{"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true", "&{User:user Passwd:password Net:tcp Addr:localhost:5555 DBName:dbname Params:map[charset:utf8] Collation:utf8_general_ci Loc:UTC TLSConfig:true tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
27-
{"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify", "&{User:user Passwd:password Net:tcp Addr:localhost:5555 DBName:dbname Params:map[charset:utf8mb4,utf8] Collation:utf8_general_ci Loc:UTC TLSConfig:skip-verify tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
28-
{"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci", "&{User:user Passwd:password Net:tcp Addr:127.0.0.1:3306 DBName:dbname Params:map[] Collation:utf8mb4_unicode_ci Loc:UTC TLSConfig: tls:<nil> Timeout:30s ReadTimeout:1s WriteTimeout:1s AllowAllFiles:true AllowCleartextPasswords:false AllowOldPasswords:true ClientFoundRows:true ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
29-
{"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", "&{User:user Passwd:p@ss(word) Net:tcp Addr:[de:ad:be:ef::ca:fe]:80 DBName:dbname Params:map[] Collation:utf8_general_ci Loc:Local TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
30-
{"/dbname", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName:dbname Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
31-
{"@/", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
32-
{"/", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
33-
{"", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
34-
{"user:p@/ssword@/", "&{User:user Passwd:p@/ssword Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
35-
{"unix/?arg=%2Fsome%2Fpath.ext", "&{User: Passwd: Net:unix Addr:/tmp/mysql.sock DBName: Params:map[arg:/some/path.ext] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
22+
{"username:password@protocol(address)/dbname?param=value", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
23+
{"username:password@protocol(address)/dbname?param=value&connattrs=(foo=bar)", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[foo:bar]}"},
24+
{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:true InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
25+
{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:true InterpolateParams:false MultiStatements:true ParseTime:false Strict:false ConnAttrs:map[]}"},
26+
{"user@unix(/path/to/socket)/dbname?charset=utf8", "&{User:user Passwd: Net:unix Addr:/path/to/socket DBName:dbname Params:map[charset:utf8] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
27+
{"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true", "&{User:user Passwd:password Net:tcp Addr:localhost:5555 DBName:dbname Params:map[charset:utf8] Collation:utf8_general_ci Loc:UTC TLSConfig:true tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
28+
{"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify", "&{User:user Passwd:password Net:tcp Addr:localhost:5555 DBName:dbname Params:map[charset:utf8mb4,utf8] Collation:utf8_general_ci Loc:UTC TLSConfig:skip-verify tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
29+
{"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci", "&{User:user Passwd:password Net:tcp Addr:127.0.0.1:3306 DBName:dbname Params:map[] Collation:utf8mb4_unicode_ci Loc:UTC TLSConfig: tls:<nil> Timeout:30s ReadTimeout:1s WriteTimeout:1s AllowAllFiles:true AllowCleartextPasswords:false AllowOldPasswords:true ClientFoundRows:true ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
30+
{"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", "&{User:user Passwd:p@ss(word) Net:tcp Addr:[de:ad:be:ef::ca:fe]:80 DBName:dbname Params:map[] Collation:utf8_general_ci Loc:Local TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
31+
{"/dbname", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName:dbname Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
32+
{"@/", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
33+
{"/", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
34+
{"", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
35+
{"user:p@/ssword@/", "&{User:user Passwd:p@/ssword Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
36+
{"unix/?arg=%2Fsome%2Fpath.ext", "&{User: Passwd: Net:unix Addr:/tmp/mysql.sock DBName: Params:map[arg:/some/path.ext] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
3637
}
3738

3839
func TestDSNParser(t *testing.T) {

0 commit comments

Comments
 (0)
Please sign in to comment.