Skip to content

Commit 44d0242

Browse files
author
Pavlo
committed
Enable sending connection attributes
Summary: In order to be able track Go client usage we enable sending connection attributes as specified in https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_response.html (CLIENT_CONNECT_ATTRS). This commit is based on PR go-sql-driver#1241 which is based on PR go-sql-driver#737 (which are still open as of 11.04.2023) Test Plan: manual integration test will be added to the main repo Reviewers: amakarovych-ua, okramarenko-ua Reviewed By: okramarenko-ua Subscribers: engineering-list Differential Revision: https://grizzly.internal.memcompute.com/D61981
1 parent f5e5b2a commit 44d0242

File tree

8 files changed

+114
-1
lines changed

8 files changed

+114
-1
lines changed

.arcconfig

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"project_id" : "go-singlestore-driver",
3+
"conduit_uri" : "https:\/\/grizzly.internal.memcompute.com\/api\/"
4+
}
5+

AUTHORS

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Achille Roussel <achille.roussel at gmail.com>
1616
Alex Snast <alexsn at fb.com>
1717
Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
1818
Andrew Reid <andrew.reid at tixtrack.com>
19+
Andy Grunwald <andygrunwald at gmail.com>
1920
Animesh Ray <mail.rayanimesh at gmail.com>
2021
Arne Hormann <arnehormann at gmail.com>
2122
Ariel Mashraki <ariel at mashraki.co.il>
@@ -92,6 +93,7 @@ Tan Jinhua <312841925 at qq.com>
9293
Thomas Wodarek <wodarekwebpage at gmail.com>
9394
Tim Ruffles <timruffles at gmail.com>
9495
Tom Jenkinson <tom at tjenkinson.me>
96+
Vasily Fedoseyev <vasilyfedoseyev at gmail.com>
9597
Vladimir Kovpak <cn007b at gmail.com>
9698
Vladyslav Zhelezniak <zhvladi at gmail.com>
9799
Xiangyu Hu <xiangyu.hu at outlook.com>
@@ -114,5 +116,6 @@ Keybase Inc.
114116
Multiplay Ltd.
115117
Percona LLC
116118
Pivotal Inc.
119+
SingleStore Inc.
117120
Stripe Inc.
118121
Zendesk Inc.

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,16 @@ SELECT u.id FROM users as u
242242

243243
will return `u.id` instead of just `id` if `columnsWithAlias=true`.
244244

245+
##### `connectAttrs`
246+
247+
```
248+
Type: map
249+
Valid Values: comma-separated list of attribute:value pairs
250+
Default: empty
251+
```
252+
253+
Allows setting of connection attributes, for example `connectAttrs=program_name:YourProgramName,program_version:1.2.3`. They are sent in the [Protocol::HandshakeResponse41](https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_response.html) packet. Attributes and their values must not contain `:` and `,` symbols.
254+
245255
##### `interpolateParams`
246256

247257
```

driver_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,11 @@ func init() {
7777
dsn = fmt.Sprintf("%s:%s@%s/%s?timeout=30s", user, pass, netAddr, dbname)
7878
c, err := net.Dial(prot, addr)
7979
if err == nil {
80+
fmt.Println("Running driver tests")
8081
available = true
8182
c.Close()
83+
} else {
84+
fmt.Printf("Not running driver tests. %s is not connectable\n", addr)
8285
}
8386
}
8487

dsn.go

+43
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type Config struct {
4141
DBName string // Database name
4242
Params map[string]string // Connection parameters
4343
Collation string // Connection collation
44+
ConnectAttrs map[string]string // Connection attributes
4445
Loc *time.Location // Location for time.Time values
4546
MaxAllowedPacket int // Max packet size allowed
4647
ServerPubKey string // Server public key name
@@ -272,6 +273,30 @@ func (cfg *Config) FormatDSN() string {
272273
writeDSNParam(&buf, &hasParam, "maxAllowedPacket", strconv.Itoa(cfg.MaxAllowedPacket))
273274
}
274275

276+
if len(cfg.ConnectAttrs) > 0 {
277+
// connectAttrs=program_name:Login Server,other_name:other
278+
if hasParam {
279+
buf.WriteString("&connectAttrs=")
280+
} else {
281+
hasParam = true
282+
buf.WriteString("?connectAttrs=")
283+
}
284+
285+
var attr_names []string
286+
for attr_name := range cfg.ConnectAttrs {
287+
attr_names = append(attr_names, attr_name)
288+
}
289+
sort.Strings(attr_names)
290+
for index, attr_name := range attr_names {
291+
if index > 0 {
292+
buf.WriteByte(',')
293+
}
294+
buf.WriteString(url.QueryEscape(attr_name))
295+
buf.WriteByte(':')
296+
buf.WriteString(url.QueryEscape(cfg.ConnectAttrs[attr_name]))
297+
}
298+
}
299+
275300
// other params
276301
if cfg.Params != nil {
277302
var params []string
@@ -537,6 +562,24 @@ func parseDSNParams(cfg *Config, params string) (err error) {
537562
if err != nil {
538563
return
539564
}
565+
case "connectAttrs":
566+
if cfg.ConnectAttrs == nil {
567+
cfg.ConnectAttrs = make(map[string]string)
568+
}
569+
570+
var ConnectAttrs string
571+
if ConnectAttrs, err = url.QueryUnescape(value); err != nil {
572+
return
573+
}
574+
575+
// program_name:Name,foo:bar
576+
for _, attr_str := range strings.Split(ConnectAttrs, ",") {
577+
attr := strings.SplitN(attr_str, ":", 2)
578+
if len(attr) != 2 {
579+
continue
580+
}
581+
cfg.ConnectAttrs[attr[0]] = attr[1]
582+
}
540583
default:
541584
// lazy init
542585
if cfg.Params == nil {

dsn_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ var testDSNs = []struct {
7171
}, {
7272
"tcp(de:ad:be:ef::ca:fe)/dbname",
7373
&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
74+
}, {
75+
"tcp(127.0.0.1)/dbname?connectAttrs=program_name:SomeService",
76+
&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", ConnectAttrs: map[string]string{"program_name": "SomeService"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
7477
},
7578
}
7679

packets.go

+41-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ import (
1818
"fmt"
1919
"io"
2020
"math"
21+
"os"
22+
"runtime"
23+
"strconv"
2124
"time"
2225
)
2326

@@ -235,10 +238,15 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro
235238
if len(data) > pos {
236239
// character set [1 byte]
237240
// status flags [2 bytes]
241+
// ignore character set and status
242+
pos += 1 + 2
238243
// capability flags (upper 2 bytes) [2 bytes]
244+
mc.flags |= clientFlag(uint32(binary.LittleEndian.Uint16(data[pos:pos+2])) << 16)
245+
pos += 2
246+
239247
// length of auth-plugin-data [1 byte]
240248
// reserved (all [00]) [10 bytes]
241-
pos += 1 + 2 + 2 + 1 + 10
249+
pos += 1 + 10
242250

243251
// second part of the password cipher [mininum 13 bytes],
244252
// where len=MAX(13, length of auth-plugin-data - 8)
@@ -312,6 +320,34 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
312320
}
313321

314322
pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + len(authRespLEI) + len(authResp) + 21 + 1
323+
const MAX_CONN_ATTRS_LEN = 2000
324+
325+
connectAttrsBuf := make([]byte, 0, MAX_CONN_ATTRS_LEN)
326+
if mc.flags&clientConnectAttrs != 0 {
327+
clientFlags |= clientConnectAttrs
328+
329+
// Set default connection attributes
330+
// See https://dev.mysql.com/doc/refman/8.0/en/performance-schema-connection-attribute-tables.html#performance-schema-connection-attributes-available
331+
connectAttrsBuf = appendLengthEncodedString(connectAttrsBuf, []byte("_client_name"))
332+
connectAttrsBuf = appendLengthEncodedString(connectAttrsBuf, []byte("memsql/go-singlestore-driver"))
333+
334+
connectAttrsBuf = appendLengthEncodedString(connectAttrsBuf, []byte("_os"))
335+
connectAttrsBuf = appendLengthEncodedString(connectAttrsBuf, []byte(runtime.GOOS))
336+
337+
connectAttrsBuf = appendLengthEncodedString(connectAttrsBuf, []byte("_platform"))
338+
connectAttrsBuf = appendLengthEncodedString(connectAttrsBuf, []byte(runtime.GOARCH))
339+
340+
connectAttrsBuf = appendLengthEncodedString(connectAttrsBuf, []byte("_pid"))
341+
connectAttrsBuf = appendLengthEncodedString(connectAttrsBuf, []byte(strconv.Itoa(os.Getpid())))
342+
343+
for k, v := range mc.cfg.ConnectAttrs {
344+
connectAttrsBuf = appendLengthEncodedString(connectAttrsBuf, []byte(k))
345+
connectAttrsBuf = appendLengthEncodedString(connectAttrsBuf, []byte(v))
346+
}
347+
// reserve additional 8 bytes to write connectAttrsBuf length
348+
connectAttrsBuf = appendLengthEncodedString(make([]byte, 0, 8 + MAX_CONN_ATTRS_LEN), connectAttrsBuf)
349+
pktLen += len(connectAttrsBuf)
350+
}
315351

316352
// To specify a db name
317353
if n := len(mc.cfg.DBName); n > 0 {
@@ -395,6 +431,10 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
395431
data[pos] = 0x00
396432
pos++
397433

434+
if clientFlags&clientConnectAttrs != 0 {
435+
pos += copy(data[pos:], connectAttrsBuf)
436+
}
437+
398438
// Send Auth packet
399439
return mc.writePacket(data[:pos])
400440
}

utils.go

+6
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,12 @@ func appendLengthEncodedInteger(b []byte, n uint64) []byte {
626626
byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56))
627627
}
628628

629+
// encodes a bytes slice with prepended length-encoded size and appends it to the given bytes slice
630+
func appendLengthEncodedString(b []byte, str []byte) []byte {
631+
b = appendLengthEncodedInteger(b, uint64(len(str)))
632+
return append(b, str...)
633+
}
634+
629635
// reserveBuffer checks cap(buf) and expand buffer to len(buf) + appendSize.
630636
// If cap(buf) is not enough, reallocate new buffer.
631637
func reserveBuffer(buf []byte, appendSize int) []byte {

0 commit comments

Comments
 (0)