Skip to content

Commit 924f833

Browse files
Send connection attributes (#1389)
Co-authored-by: Inada Naoki <[email protected]>
1 parent 72e78ee commit 924f833

File tree

12 files changed

+173
-28
lines changed

12 files changed

+173
-28
lines changed

‎.github/workflows/test.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ jobs:
7979
; TestConcurrent fails if max_connections is too large
8080
max_connections=50
8181
local_infile=1
82+
performance_schema=on
8283
- name: setup database
8384
run: |
8485
mysql --user 'root' --host '127.0.0.1' -e 'create database gotest;'

‎README.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,15 @@ Default: 0
393393

394394
I/O write timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
395395

396+
##### `connectionAttributes`
397+
398+
```
399+
Type: comma-delimited string of user-defined "key:value" pairs
400+
Valid Values: (<name1>:<value1>,<name2>:<value2>,...)
401+
Default: none
402+
```
403+
404+
[Connection attributes](https://dev.mysql.com/doc/refman/8.0/en/performance-schema-connection-attribute-tables.html) are key-value pairs that application programs can pass to the server at connect time.
396405

397406
##### System Variables
398407

‎connection.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type mysqlConn struct{
2727
affectedRowsuint64
2828
insertIduint64
2929
cfg*Config
30+
connector*connector
3031
maxAllowedPacketint
3132
maxWriteSizeint
3233
writeTimeout time.Duration

‎connector.go‎

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,54 @@ package mysql
1111
import (
1212
"context"
1313
"database/sql/driver"
14+
"fmt"
1415
"net"
16+
"os"
17+
"strconv"
18+
"strings"
1519
)
1620

1721
typeconnectorstruct{
18-
cfg*Config// immutable private copy.
22+
cfg*Config// immutable private copy.
23+
encodedAttributesstring// Encoded connection attributes.
24+
}
25+
26+
funcencodeConnectionAttributes(textAttributesstring) string{
27+
connAttrsBuf:=make([]byte, 0, 251)
28+
29+
// default connection attributes
30+
connAttrsBuf=appendLengthEncodedString(connAttrsBuf, connAttrClientName)
31+
connAttrsBuf=appendLengthEncodedString(connAttrsBuf, connAttrClientNameValue)
32+
connAttrsBuf=appendLengthEncodedString(connAttrsBuf, connAttrOS)
33+
connAttrsBuf=appendLengthEncodedString(connAttrsBuf, connAttrOSValue)
34+
connAttrsBuf=appendLengthEncodedString(connAttrsBuf, connAttrPlatform)
35+
connAttrsBuf=appendLengthEncodedString(connAttrsBuf, connAttrPlatformValue)
36+
connAttrsBuf=appendLengthEncodedString(connAttrsBuf, connAttrPid)
37+
connAttrsBuf=appendLengthEncodedString(connAttrsBuf, strconv.Itoa(os.Getpid()))
38+
39+
// user-defined connection attributes
40+
for_, connAttr:=rangestrings.Split(textAttributes, ","){
41+
attr:=strings.SplitN(connAttr, ":", 2)
42+
iflen(attr) !=2{
43+
continue
44+
}
45+
for_, v:=rangeattr{
46+
connAttrsBuf=appendLengthEncodedString(connAttrsBuf, v)
47+
}
48+
}
49+
50+
returnstring(connAttrsBuf)
51+
}
52+
53+
funcnewConnector(cfg*Config) (*connector, error){
54+
encodedAttributes:=encodeConnectionAttributes(cfg.ConnectionAttributes)
55+
iflen(encodedAttributes) >250{
56+
returnnil, fmt.Errorf("connection attributes are longer than 250 bytes: %dbytes (%q)", len(encodedAttributes), cfg.ConnectionAttributes)
57+
}
58+
return&connector{
59+
cfg: cfg,
60+
encodedAttributes: encodedAttributes,
61+
}, nil
1962
}
2063

2164
// Connect implements driver.Connector interface.
@@ -29,6 +72,7 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error){
2972
maxWriteSize: maxPacketSize-1,
3073
closech: make(chanstruct{}),
3174
cfg: c.cfg,
75+
connector: c,
3276
}
3377
mc.parseTime=mc.cfg.ParseTime
3478

‎connector_test.go‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ import (
88
)
99

1010
funcTestConnectorReturnsTimeout(t*testing.T){
11-
connector:=&connector{&Config{
11+
connector, err:=newConnector(&Config{
1212
Net: "tcp",
1313
Addr: "1.1.1.1:1234",
1414
Timeout: 10*time.Millisecond,
15-
}}
15+
})
16+
iferr!=nil{
17+
t.Fatal(err)
18+
}
1619

17-
_, err:=connector.Connect(context.Background())
20+
_, err=connector.Connect(context.Background())
1821
iferr==nil{
1922
t.Fatal("error expected")
2023
}

‎const.go‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,24 @@
88

99
package mysql
1010

11+
import"runtime"
12+
1113
const (
1214
defaultAuthPlugin="mysql_native_password"
1315
defaultMaxAllowedPacket=64<<20// 64 MiB. See https://github.com/go-sql-driver/mysql/issues/1355
1416
minProtocolVersion=10
1517
maxPacketSize=1<<24-1
1618
timeFormat="2006-01-02 15:04:05.999999"
19+
20+
// Connection attributes
21+
// See https://dev.mysql.com/doc/refman/8.0/en/performance-schema-connection-attribute-tables.html#performance-schema-connection-attributes-available
22+
connAttrClientName="_client_name"
23+
connAttrClientNameValue="Go-MySQL-Driver"
24+
connAttrOS="_os"
25+
connAttrOSValue=runtime.GOOS
26+
connAttrPlatform="_platform"
27+
connAttrPlatformValue=runtime.GOARCH
28+
connAttrPid="_pid"
1729
)
1830

1931
// MySQL constants documentation:

‎driver.go‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,9 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error){
8585
iferr!=nil{
8686
returnnil, err
8787
}
88-
c:=&connector{
89-
cfg: cfg,
88+
c, err:=newConnector(cfg)
89+
iferr!=nil{
90+
returnnil, err
9091
}
9192
returnc.Connect(context.Background())
9293
}
@@ -103,7 +104,7 @@ func NewConnector(cfg *Config) (driver.Connector, error){
103104
iferr:=cfg.normalize(); err!=nil{
104105
returnnil, err
105106
}
106-
return&connector{cfg: cfg}, nil
107+
returnnewConnector(cfg)
107108
}
108109

109110
// OpenConnector implements driver.DriverContext.
@@ -112,7 +113,5 @@ func (d MySQLDriver) OpenConnector(dsn string) (driver.Connector, error){
112113
iferr!=nil{
113114
returnnil, err
114115
}
115-
return&connector{
116-
cfg: cfg,
117-
}, nil
116+
returnnewConnector(cfg)
118117
}

‎driver_test.go‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3214,3 +3214,50 @@ func TestConnectorTimeoutsWatchCancel(t *testing.T){
32143214
t.Errorf("connection not closed")
32153215
}
32163216
}
3217+
3218+
funcTestConnectionAttributes(t*testing.T){
3219+
if!available{
3220+
t.Skipf("MySQL server not running on %s", netAddr)
3221+
}
3222+
3223+
attr1:="attr1"
3224+
value1:="value1"
3225+
attr2:="foo"
3226+
value2:="boo"
3227+
dsn+=fmt.Sprintf("&connectionAttributes=%s:%s,%s:%s", attr1, value1, attr2, value2)
3228+
3229+
vardb*sql.DB
3230+
if_, err:=ParseDSN(dsn); err!=errInvalidDSNUnsafeCollation{
3231+
db, err=sql.Open("mysql", dsn)
3232+
iferr!=nil{
3233+
t.Fatalf("error connecting: %s", err.Error())
3234+
}
3235+
deferdb.Close()
3236+
}
3237+
3238+
dbt:=&DBTest{t, db}
3239+
3240+
varattrValuestring
3241+
queryString:="SELECT ATTR_VALUE FROM performance_schema.session_account_connect_attrs WHERE PROCESSLIST_ID = CONNECTION_ID() and ATTR_NAME = ?"
3242+
rows:=dbt.mustQuery(queryString, connAttrClientName)
3243+
ifrows.Next(){
3244+
rows.Scan(&attrValue)
3245+
ifattrValue!=connAttrClientNameValue{
3246+
dbt.Errorf("expected %q, got %q", connAttrClientNameValue, attrValue)
3247+
}
3248+
} else{
3249+
dbt.Errorf("no data")
3250+
}
3251+
rows.Close()
3252+
3253+
rows=dbt.mustQuery(queryString, attr2)
3254+
ifrows.Next(){
3255+
rows.Scan(&attrValue)
3256+
ifattrValue!=value2{
3257+
dbt.Errorf("expected %q, got %q", value2, attrValue)
3258+
}
3259+
} else{
3260+
dbt.Errorf("no data")
3261+
}
3262+
rows.Close()
3263+
}

‎dsn.go‎

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,24 @@ var (
3434
// If a new Config is created instead of being parsed from a DSN string,
3535
// the NewConfig function should be used, which sets default values.
3636
typeConfigstruct{
37-
Userstring// Username
38-
Passwdstring// Password (requires User)
39-
Netstring// Network type
40-
Addrstring// Network address (requires Net)
41-
DBNamestring// Database name
42-
Paramsmap[string]string// Connection parameters
43-
Collationstring// Connection collation
44-
Loc*time.Location// Location for time.Time values
45-
MaxAllowedPacketint// Max packet size allowed
46-
ServerPubKeystring// Server public key name
47-
pubKey*rsa.PublicKey// Server public key
48-
TLSConfigstring// TLS configuration name
49-
TLS*tls.Config// TLS configuration, its priority is higher than TLSConfig
50-
Timeout time.Duration// Dial timeout
51-
ReadTimeout time.Duration// I/O read timeout
52-
WriteTimeout time.Duration// I/O write timeout
53-
LoggerLogger// Logger
37+
Userstring// Username
38+
Passwdstring// Password (requires User)
39+
Netstring// Network type
40+
Addrstring// Network address (requires Net)
41+
DBNamestring// Database name
42+
Paramsmap[string]string// Connection parameters
43+
ConnectionAttributesstring// Connection Attributes, comma-delimited string of user-defined "key:value" pairs
44+
Collationstring// Connection collation
45+
Loc*time.Location// Location for time.Time values
46+
MaxAllowedPacketint// Max packet size allowed
47+
ServerPubKeystring// Server public key name
48+
pubKey*rsa.PublicKey// Server public key
49+
TLSConfigstring// TLS configuration name
50+
TLS*tls.Config// TLS configuration, its priority is higher than TLSConfig
51+
Timeout time.Duration// Dial timeout
52+
ReadTimeout time.Duration// I/O read timeout
53+
WriteTimeout time.Duration// I/O write timeout
54+
LoggerLogger// Logger
5455

5556
AllowAllFilesbool// Allow all files to be used with LOAD DATA LOCAL INFILE
5657
AllowCleartextPasswordsbool// Allows the cleartext client side plugin
@@ -560,6 +561,11 @@ func parseDSNParams(cfg *Config, params string) (err error){
560561
iferr!=nil{
561562
return
562563
}
564+
565+
// Connection attributes
566+
case"connectionAttributes":
567+
cfg.ConnectionAttributes=value
568+
563569
default:
564570
// lazy init
565571
ifcfg.Params==nil{

‎packets.go‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
285285
clientLocalFiles|
286286
clientPluginAuth|
287287
clientMultiResults|
288+
clientConnectAttrs|
288289
mc.flags&clientLongFlag
289290

290291
ifmc.cfg.ClientFoundRows{
@@ -318,6 +319,13 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
318319
pktLen+=n+1
319320
}
320321

322+
// 1 byte to store length of all key-values
323+
// NOTE: Actually, this is length encoded integer.
324+
// But we support only len(connAttrBuf) < 251 for now because takeSmallBuffer
325+
// doesn't support buffer size more than 4096 bytes.
326+
// TODO(methane): Rewrite buffer management.
327+
pktLen+=1+len(mc.connector.encodedAttributes)
328+
321329
// Calculate packet length and get buffer with that size
322330
data, err:=mc.buf.takeSmallBuffer(pktLen+4)
323331
iferr!=nil{
@@ -394,6 +402,11 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
394402
data[pos] =0x00
395403
pos++
396404

405+
// Connection Attributes
406+
data[pos] =byte(len(mc.connector.encodedAttributes))
407+
pos++
408+
pos+=copy(data[pos:], []byte(mc.connector.encodedAttributes))
409+
397410
// Send Auth packet
398411
returnmc.writePacket(data[:pos])
399412
}

0 commit comments

Comments
(0)