From 4fd4bffb21ea75eab77d7edcc8a8901bfd98ac60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Eeden?= Date: Sun, 26 Apr 2015 12:36:43 +0200 Subject: [PATCH 1/2] 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. --- AUTHORS | 1 + packets.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/AUTHORS b/AUTHORS index 4b65bf363..35be98c97 100644 --- a/AUTHORS +++ b/AUTHORS @@ -15,6 +15,7 @@ Aaron Hopkins Arne Hormann Carlos Nieto Chris Moos +Daniƫl van Eeden DisposaBoy Frederick Mayle Gustavo Kristic diff --git a/packets.go b/packets.go index 290a3887a..558b18463 100644 --- a/packets.go +++ b/packets.go @@ -16,6 +16,10 @@ import ( "fmt" "io" "math" + "os" + "path" + "runtime" + "strconv" "time" ) @@ -214,6 +218,7 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error { clientLongPassword | clientTransactions | clientLocalFiles | + clientConnectAttrs | mc.flags&clientLongFlag if mc.cfg.clientFoundRows { @@ -228,7 +233,22 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error { // User Password scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.passwd)) + attrs := make(map[string]string) + attrs["_os"] = runtime.GOOS + attrs["_client_name"] = "Go MySQL Driver" + attrs["_pid"] = strconv.Itoa(os.Getpid()) + attrs["_platform"] = runtime.GOARCH + attrs["program_name"] = path.Base(os.Args[0]) + + attrlen := 0 + for attrname, attrvalue := range attrs { + attrlen += len(attrname) + len(attrvalue) + // one byte to store attrname length and one byte to store attrvalue length + attrlen += 2 + } + pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.user) + 1 + 1 + len(scrambleBuff) + pktLen += attrlen + 1 // one byte to store the total length of attrs // To specify a db name if n := len(mc.cfg.dbname); n > 0 { @@ -295,6 +315,19 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error { pos += copy(data[pos:], mc.cfg.dbname) data[pos] = 0x00 } + pos++ + + // Connection attributes + data[pos] = byte(attrlen) + pos++ + + for attrname, attrvalue := range attrs { + data[pos] = byte(len(attrname)) + pos += 1 + copy(data[pos+1:], attrname) + + data[pos] = byte(len(attrvalue)) + pos += 1 + copy(data[pos+1:], attrvalue) + } // Send Auth packet return mc.writePacket(data) From 58b3beb5a570351e18ba3b6f6fefe866eaee62b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Eeden?= Date: Thu, 28 May 2015 16:00:32 +0200 Subject: [PATCH 2/2] Add support for custom connection attributes Uses connattrs=(attr1=val1,attr2=val2) in the DSN Example DSN: "msandbox:msandbox@(127.0.0.1:5624)/test?connattrs=(cattr1=foo,cattr2=bar)&allowOldPasswords=true" --- README.md | 10 ++++++++++ connection.go | 1 + packets.go | 3 +++ utils.go | 11 +++++++++++ utils_test.go | 27 ++++++++++++++------------- 5 files changed, 39 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 9edb7628b..99930f73e 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,16 @@ Sets the collation used for client-server interaction on connection. In contrast A list of valid charsets for a server is retrievable with `SHOW COLLATION`. +##### `connattrs` + +``` +Type: comma seperated string of name/value pairs +Valid Values: (=,=,...) +Default: none +``` + +Sends custom connection attributes to the server. + ##### `clientFoundRows` ``` diff --git a/connection.go b/connection.go index a6d39bec9..5884ee13e 100644 --- a/connection.go +++ b/connection.go @@ -49,6 +49,7 @@ type config struct { clientFoundRows bool columnsWithAlias bool interpolateParams bool + connattrs map[string]string } // Handles parameters set in DSN after the connection is established diff --git a/packets.go b/packets.go index 558b18463..e56bb66ce 100644 --- a/packets.go +++ b/packets.go @@ -239,6 +239,9 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error { attrs["_pid"] = strconv.Itoa(os.Getpid()) attrs["_platform"] = runtime.GOARCH attrs["program_name"] = path.Base(os.Args[0]) + for cfganame, cfgaval := range mc.cfg.connattrs { + attrs[cfganame] = cfgaval + } attrlen := 0 for attrname, attrvalue := range attrs { diff --git a/utils.go b/utils.go index 6693d2970..fa39095eb 100644 --- a/utils.go +++ b/utils.go @@ -277,6 +277,17 @@ func parseDSNParams(cfg *config, params string) (err error) { return fmt.Errorf("Invalid value / unknown config name: %s", value) } } + case "connattrs": + if cfg.connattrs == nil { + cfg.connattrs = make(map[string]string) + } + for _, conn_v := range strings.Split(strings.TrimSuffix(strings.TrimPrefix(value, "("),")"), ",") { + attr := strings.SplitN(conn_v, "=", 2) + if len(attr) != 2 { + return fmt.Errorf("Invalid connection attribute: %s", conn_v) + } + cfg.connattrs[attr[0]] = attr[1] + } default: // lazy init diff --git a/utils_test.go b/utils_test.go index adb8dcbd1..78047a79c 100644 --- a/utils_test.go +++ b/utils_test.go @@ -22,19 +22,20 @@ var testDSNs = []struct { out string loc *time.Location }{ - {"username:password@protocol(address)/dbname?param=value", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC}, - {"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:true interpolateParams:false}", time.UTC}, - {"user@unix(/path/to/socket)/dbname?charset=utf8", "&{user:user passwd: net:unix addr:/path/to/socket dbname:dbname params:map[charset:utf8] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC}, - {"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] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC}, - {"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] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC}, - {"user:password@/dbname?loc=UTC&timeout=30s&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[] loc:%p tls: timeout:30000000000 collation:224 allowAllFiles:true allowOldPasswords:true clientFoundRows:true columnsWithAlias:false interpolateParams:false}", time.UTC}, - {"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[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.Local}, - {"/dbname", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC}, - {"@/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC}, - {"/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC}, - {"", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC}, - {"user:p@/ssword@/", "&{user:user passwd:p@/ssword net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC}, - {"unix/?arg=%2Fsome%2Fpath.ext", "&{user: passwd: net:unix addr:/tmp/mysql.sock dbname: params:map[arg:/some/path.ext] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false}", time.UTC}, + {"username:password@protocol(address)/dbname?param=value", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false connattrs:map[]}", time.UTC}, + {"username:password@protocol(address)/dbname?param=value&connattrs=(c1=v1)", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false connattrs:map[c1:v1]}", time.UTC}, + {"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:true interpolateParams:false connattrs:map[]}", time.UTC}, + {"user@unix(/path/to/socket)/dbname?charset=utf8", "&{user:user passwd: net:unix addr:/path/to/socket dbname:dbname params:map[charset:utf8] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false connattrs:map[]}", time.UTC}, + {"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] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false connattrs:map[]}", time.UTC}, + {"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] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false connattrs:map[]}", time.UTC}, + {"user:password@/dbname?loc=UTC&timeout=30s&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[] loc:%p tls: timeout:30000000000 collation:224 allowAllFiles:true allowOldPasswords:true clientFoundRows:true columnsWithAlias:false interpolateParams:false connattrs:map[]}", time.UTC}, + {"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[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false connattrs:map[]}", time.Local}, + {"/dbname", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false connattrs:map[]}", time.UTC}, + {"@/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false connattrs:map[]}", time.UTC}, + {"/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false connattrs:map[]}", time.UTC}, + {"", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false connattrs:map[]}", time.UTC}, + {"user:p@/ssword@/", "&{user:user passwd:p@/ssword net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false connattrs:map[]}", time.UTC}, + {"unix/?arg=%2Fsome%2Fpath.ext", "&{user: passwd: net:unix addr:/tmp/mysql.sock dbname: params:map[arg:/some/path.ext] loc:%p tls: timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false interpolateParams:false connattrs:map[]}", time.UTC}, } func TestDSNParser(t *testing.T) {