diff --git a/go/binlog/gomysql_reader.go b/go/binlog/gomysql_reader.go
index d2797bb..106f73d 100644
--- a/go/binlog/gomysql_reader.go
+++ b/go/binlog/gomysql_reader.go
@@ -40,12 +40,13 @@ func NewGoMySQLReader(migrationContext *base.MigrationContext) (binlogReader *Go
serverId := uint32(migrationContext.ReplicaServerId)
binlogSyncerConfig := replication.BinlogSyncerConfig{
- ServerID: serverId,
- Flavor: "mysql",
- Host: binlogReader.connectionConfig.Key.Hostname,
- Port: uint16(binlogReader.connectionConfig.Key.Port),
- User: binlogReader.connectionConfig.User,
- Password: binlogReader.connectionConfig.Password,
+ ServerID: serverId,
+ Flavor: "mysql",
+ Host: binlogReader.connectionConfig.Key.Hostname,
+ Port: uint16(binlogReader.connectionConfig.Key.Port),
+ User: binlogReader.connectionConfig.User,
+ Password: binlogReader.connectionConfig.Password,
+ UseDecimal: true,
}
binlogReader.binlogSyncer = replication.NewBinlogSyncer(binlogSyncerConfig)
diff --git a/localtests/decimal/create.sql b/localtests/decimal/create.sql
new file mode 100644
index 0000000..248c86a
--- /dev/null
+++ b/localtests/decimal/create.sql
@@ -0,0 +1,23 @@
+drop table if exists gh_ost_test;
+create table gh_ost_test (
+ id int auto_increment,
+ dec0 decimal(65,30) unsigned NOT NULL DEFAULT '0.000000000000000000000000000000',
+ dec1 decimal(65,30) unsigned NOT NULL DEFAULT '1.000000000000000000000000000000',
+ primary key(id)
+) auto_increment=1;
+
+drop event if exists gh_ost_test;
+delimiter ;;
+create event gh_ost_test
+ on schedule every 1 second
+ starts current_timestamp
+ ends current_timestamp + interval 60 second
+ on completion not preserve
+ enable
+ do
+begin
+ insert into gh_ost_test values (null, 0.0, 0.0);
+ insert into gh_ost_test values (null, 2.0, 4.0);
+ insert into gh_ost_test values (null, 99999999999999999999999999999999999.000, 6.0);
+ update gh_ost_test set dec1=4.5 where dec2=4.0 order by id desc limit 1;
+end ;;
diff --git a/vendor/github.com/siddontang/go-mysql/.travis.yml b/vendor/github.com/siddontang/go-mysql/.travis.yml
index cc0db3c..8f8eafd 100644
--- a/vendor/github.com/siddontang/go-mysql/.travis.yml
+++ b/vendor/github.com/siddontang/go-mysql/.travis.yml
@@ -1,32 +1,34 @@
language: go
go:
- - 1.6
- - 1.7
+ - "1.9"
+ - "1.10"
-dist: trusty
-sudo: required
addons:
apt:
+ sources:
+ - mysql-5.7-trusty
packages:
- - mysql-server-5.6
- - mysql-client-core-5.6
- - mysql-client-5.6
+ - mysql-server
+ - mysql-client
+
+before_install:
+ - sudo mysql -e "use mysql; update user set authentication_string=PASSWORD('') where User='root'; update user set plugin='mysql_native_password';FLUSH PRIVILEGES;"
+ - sudo mysql_upgrade
-before_script:
# stop mysql and use row-based format binlog
- - "sudo /etc/init.d/mysql stop || true"
+ - "sudo service mysql stop || true"
- "echo '[mysqld]' | sudo tee /etc/mysql/conf.d/replication.cnf"
- "echo 'server-id=1' | sudo tee -a /etc/mysql/conf.d/replication.cnf"
- - "echo 'log-bin=mysql' | sudo tee -a /etc/mysql/conf.d/replication.cnf"
+ - "echo 'log-bin=mysql' | sudo tee -a /etc/mysql/conf.d/replication.cnf"
- "echo 'binlog-format = row' | sudo tee -a /etc/mysql/conf.d/replication.cnf"
# Start mysql (avoid errors to have logs)
- - "sudo /etc/init.d/mysql start || true"
+ - "sudo service mysql start || true"
- "sudo tail -1000 /var/log/syslog"
- mysql -e "CREATE DATABASE IF NOT EXISTS test;" -uroot
script:
- - make test
\ No newline at end of file
+ - make test
diff --git a/vendor/github.com/siddontang/go-mysql/Gopkg.lock b/vendor/github.com/siddontang/go-mysql/Gopkg.lock
new file mode 100644
index 0000000..ae65b1d
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/Gopkg.lock
@@ -0,0 +1,78 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+ name = "github.com/BurntSushi/toml"
+ packages = ["."]
+ revision = "b26d9c308763d68093482582cea63d69be07a0f0"
+ version = "v0.3.0"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/go-sql-driver/mysql"
+ packages = ["."]
+ revision = "99ff426eb706cffe92ff3d058e168b278cabf7c7"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/jmoiron/sqlx"
+ packages = [
+ ".",
+ "reflectx"
+ ]
+ revision = "2aeb6a910c2b94f2d5eb53d9895d80e27264ec41"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/juju/errors"
+ packages = ["."]
+ revision = "c7d06af17c68cd34c835053720b21f6549d9b0ee"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/pingcap/check"
+ packages = ["."]
+ revision = "1c287c953996ab3a0bf535dba9d53d809d3dc0b6"
+
+[[projects]]
+ name = "github.com/satori/go.uuid"
+ packages = ["."]
+ revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3"
+ version = "v1.2.0"
+
+[[projects]]
+ name = "github.com/shopspring/decimal"
+ packages = ["."]
+ revision = "cd690d0c9e2447b1ef2a129a6b7b49077da89b8e"
+ version = "1.1.0"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/siddontang/go"
+ packages = [
+ "hack",
+ "sync2"
+ ]
+ revision = "2b7082d296ba89ae7ead0f977816bddefb65df9d"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/siddontang/go-log"
+ packages = [
+ "log",
+ "loggers"
+ ]
+ revision = "a4d157e46fa3e08b7e7ff329af341fa3ff86c02c"
+
+[[projects]]
+ name = "google.golang.org/appengine"
+ packages = ["cloudsql"]
+ revision = "b1f26356af11148e710935ed1ac8a7f5702c7612"
+ version = "v1.1.0"
+
+[solve-meta]
+ analyzer-name = "dep"
+ analyzer-version = 1
+ inputs-digest = "a1f9939938a58551bbb3f19411c9d1386995d36296de6f6fb5d858f5923db85e"
+ solver-name = "gps-cdcl"
+ solver-version = 1
diff --git a/vendor/github.com/siddontang/go-mysql/Gopkg.toml b/vendor/github.com/siddontang/go-mysql/Gopkg.toml
new file mode 100644
index 0000000..71df4b3
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/Gopkg.toml
@@ -0,0 +1,56 @@
+# Gopkg.toml example
+#
+# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+# name = "github.com/user/project"
+# version = "1.0.0"
+#
+# [[constraint]]
+# name = "github.com/user/project2"
+# branch = "dev"
+# source = "github.com/myfork/project2"
+#
+# [[override]]
+# name = "github.com/x/y"
+# version = "2.4.0"
+#
+# [prune]
+# non-go = false
+# go-tests = true
+# unused-packages = true
+
+
+[[constraint]]
+ name = "github.com/BurntSushi/toml"
+ version = "v0.3.0"
+
+[[constraint]]
+ name = "github.com/go-sql-driver/mysql"
+ branch = "master"
+
+[[constraint]]
+ branch = "master"
+ name = "github.com/juju/errors"
+
+[[constraint]]
+ name = "github.com/satori/go.uuid"
+ version = "v1.2.0"
+
+[[constraint]]
+ name = "github.com/shopspring/decimal"
+ version = "v1.1.0"
+
+[[constraint]]
+ branch = "master"
+ name = "github.com/siddontang/go"
+
+[prune]
+ go-tests = true
+ unused-packages = true
+ non-go = true
+
diff --git a/vendor/github.com/siddontang/go-mysql/Makefile b/vendor/github.com/siddontang/go-mysql/Makefile
index 92744b1..3decd6c 100644
--- a/vendor/github.com/siddontang/go-mysql/Makefile
+++ b/vendor/github.com/siddontang/go-mysql/Makefile
@@ -1,33 +1,14 @@
all: build
build:
- rm -rf vendor && ln -s _vendor/vendor vendor
go build -o bin/go-mysqlbinlog cmd/go-mysqlbinlog/main.go
go build -o bin/go-mysqldump cmd/go-mysqldump/main.go
go build -o bin/go-canal cmd/go-canal/main.go
go build -o bin/go-binlogparser cmd/go-binlogparser/main.go
- rm -rf vendor
-
+
test:
- rm -rf vendor && ln -s _vendor/vendor vendor
go test --race -timeout 2m ./...
- rm -rf vendor
clean:
go clean -i ./...
- @rm -rf ./bin
-
-update_vendor:
- which glide >/dev/null || curl https://glide.sh/get | sh
- which glide-vc || go get -v -u github.com/sgotti/glide-vc
- rm -r vendor && mv _vendor/vendor vendor || true
- rm -rf _vendor
-ifdef PKG
- glide get --strip-vendor --skip-test ${PKG}
-else
- glide update --strip-vendor --skip-test
-endif
- @echo "removing test files"
- glide vc --only-code --no-tests
- mkdir -p _vendor
- mv vendor _vendor/vendor
+ @rm -rf ./bin
\ No newline at end of file
diff --git a/vendor/github.com/siddontang/go-mysql/README.md b/vendor/github.com/siddontang/go-mysql/README.md
index b30f6fd..0b958c7 100644
--- a/vendor/github.com/siddontang/go-mysql/README.md
+++ b/vendor/github.com/siddontang/go-mysql/README.md
@@ -15,7 +15,7 @@ import (
"github.com/siddontang/go-mysql/replication"
"os"
)
-// Create a binlog syncer with a unique server id, the server id must be different from other MySQL's.
+// Create a binlog syncer with a unique server id, the server id must be different from other MySQL's.
// flavor is mysql or mariadb
cfg := replication.BinlogSyncerConfig {
ServerID: 100,
@@ -27,7 +27,7 @@ cfg := replication.BinlogSyncerConfig {
}
syncer := replication.NewBinlogSyncer(cfg)
-// Start sync with sepcified binlog file and position
+// Start sync with specified binlog file and position
streamer, _ := syncer.StartSync(mysql.Position{binlogFile, binlogPos})
// or you can start a gtid replication like
@@ -44,7 +44,7 @@ for {
// or we can use a timeout context
for {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
- e, _ := s.GetEvent(ctx)
+ ev, err := s.GetEvent(ctx)
cancel()
if err == context.DeadlineExceeded {
@@ -85,13 +85,13 @@ Schema: test
Query: DROP TABLE IF EXISTS `test_replication` /* generated by server */
```
-## Canal
+## Canal
-Canal is a package that can sync your MySQL into everywhere, like Redis, Elasticsearch.
+Canal is a package that can sync your MySQL into everywhere, like Redis, Elasticsearch.
-First, canal will dump your MySQL data then sync changed data using binlog incrementally.
+First, canal will dump your MySQL data then sync changed data using binlog incrementally.
-You must use ROW format for binlog, full binlog row image is preferred, because we may meet some errors when primary key changed in update for minimal or noblob row image.
+You must use ROW format for binlog, full binlog row image is preferred, because we may meet some errors when primary key changed in update for minimal or noblob row image.
A simple example:
@@ -105,30 +105,31 @@ cfg.Dump.Tables = []string{"canal_test"}
c, err := NewCanal(cfg)
-type myRowsEventHandler struct {
+type MyEventHandler struct {
+ DummyEventHandler
}
-func (h *myRowsEventHandler) Do(e *RowsEvent) error {
+func (h *MyEventHandler) OnRow(e *RowsEvent) error {
log.Infof("%s %v\n", e.Action, e.Rows)
return nil
}
-func (h *myRowsEventHandler) String() string {
- return "myRowsEventHandler"
+func (h *MyEventHandler) String() string {
+ return "MyEventHandler"
}
// Register a handler to handle RowsEvent
-c.RegRowsEventHandler(&MyRowsEventHandler{})
+c.SetEventHandler(&MyEventHandler{})
// Start canal
c.Start()
```
-You can see [go-mysql-elasticsearch](https://github.com/siddontang/go-mysql-elasticsearch) for how to sync MySQL data into Elasticsearch.
+You can see [go-mysql-elasticsearch](https://github.com/siddontang/go-mysql-elasticsearch) for how to sync MySQL data into Elasticsearch.
## Client
-Client package supports a simple MySQL connection driver which you can use it to communicate with MySQL server.
+Client package supports a simple MySQL connection driver which you can use it to communicate with MySQL server.
### Example
@@ -137,9 +138,16 @@ import (
"github.com/siddontang/go-mysql/client"
)
-// Connect MySQL at 127.0.0.1:3306, with user root, an empty passowrd and database test
+// Connect MySQL at 127.0.0.1:3306, with user root, an empty password and database test
conn, _ := client.Connect("127.0.0.1:3306", "root", "", "test")
+// Or to use SSL/TLS connection if MySQL server supports TLS
+//conn, _ := client.Connect("127.0.0.1:3306", "root", "", "test", func(c *Conn) {c.UseSSL(true)})
+
+// or to set your own client-side certificates for identity verification for security
+//tlsConfig := NewClientTLSConfig(caPem, certPem, keyPem, false, "your-server-name")
+//conn, _ := client.Connect("127.0.0.1:3306", "root", "", "test", func(c *Conn) {c.SetTLSConfig(tlsConfig)})
+
conn.Ping()
// Insert
@@ -153,13 +161,20 @@ r, _ := conn.Execute(`select id, name from table where id = 1`)
// Handle resultset
v, _ := r.GetInt(0, 0)
-v, _ = r.GetIntByName(0, "id")
+v, _ = r.GetIntByName(0, "id")
```
+Tested MySQL versions for the client include:
+- 5.5.x
+- 5.6.x
+- 5.7.x
+- 8.0.x
+
## Server
-Server package supplies a framework to implement a simple MySQL server which can handle the packets from the MySQL client.
-You can use it to build your own MySQL proxy.
+Server package supplies a framework to implement a simple MySQL server which can handle the packets from the MySQL client.
+You can use it to build your own MySQL proxy. The server connection is compatible with MySQL 5.5, 5.6, 5.7, and 8.0 versions,
+so that most MySQL clients should be able to connect to the Server without modifications.
### Example
@@ -173,42 +188,53 @@ l, _ := net.Listen("tcp", "127.0.0.1:4000")
c, _ := l.Accept()
-// Create a connection with user root and an empty passowrd
-// We only an empty handler to handle command too
+// Create a connection with user root and an empty password.
+// You can use your own handler to handle command here.
conn, _ := server.NewConn(c, "root", "", server.EmptyHandler{})
for {
conn.HandleCommand()
}
-```
+```
Another shell
```
-mysql -h127.0.0.1 -P4000 -uroot -p
-//Becuase empty handler does nothing, so here the MySQL client can only connect the proxy server. :-)
+mysql -h127.0.0.1 -P4000 -uroot -p
+//Becuase empty handler does nothing, so here the MySQL client can only connect the proxy server. :-)
```
+> ```NewConn()``` will use default server configurations:
+> 1. automatically generate default server certificates and enable TLS/SSL support.
+> 2. support three mainstream authentication methods **'mysql_native_password'**, **'caching_sha2_password'**, and **'sha256_password'**
+> and use **'mysql_native_password'** as default.
+> 3. use an in-memory user credential provider to store user and password.
+>
+> To customize server configurations, use ```NewServer()``` and create connection via ```NewCustomizedConn()```.
+
+
## Failover
Failover supports to promote a new master and let other slaves replicate from it automatically when the old master was down.
Failover supports MySQL >= 5.6.9 with GTID mode, if you use lower version, e.g, MySQL 5.0 - 5.5, please use [MHA](http://code.google.com/p/mysql-master-ha/) or [orchestrator](https://github.com/outbrain/orchestrator).
-At the same time, Failover supports MariaDB >= 10.0.9 with GTID mode too.
+At the same time, Failover supports MariaDB >= 10.0.9 with GTID mode too.
-Why only GTID? Supporting failover with no GTID mode is very hard, because slave can not find the proper binlog filename and position with the new master.
-Although there are many companies use MySQL 5.0 - 5.5, I think upgrade MySQL to 5.6 or higher is easy.
+Why only GTID? Supporting failover with no GTID mode is very hard, because slave can not find the proper binlog filename and position with the new master.
+Although there are many companies use MySQL 5.0 - 5.5, I think upgrade MySQL to 5.6 or higher is easy.
## Driver
Driver is the package that you can use go-mysql with go database/sql like other drivers. A simple example:
```
+package main
+
import (
"database/sql"
- - "github.com/siddontang/go-mysql/driver"
+ _ "github.com/siddontang/go-mysql/driver"
)
func main() {
@@ -221,9 +247,17 @@ func main() {
We pass all tests in https://github.com/bradfitz/go-sql-test using go-mysql driver. :-)
+## Donate
+
+If you like the project and want to buy me a cola, you can through:
+
+|PayPal|微信|
+|------|---|
+|[![](https://www.paypalobjects.com/webstatic/paypalme/images/pp_logo_small.png)](https://paypal.me/siddontang)|[![](https://github.com/siddontang/blog/blob/master/donate/weixin.png)|
+
## Feedback
-go-mysql is still in development, your feedback is very welcome.
+go-mysql is still in development, your feedback is very welcome.
Gmail: siddontang@gmail.com
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/rows.go b/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/rows.go
deleted file mode 100644
index c08255e..0000000
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/rows.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
-//
-// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this file,
-// You can obtain one at http://mozilla.org/MPL/2.0/.
-
-package mysql
-
-import (
- "database/sql/driver"
- "io"
-)
-
-type mysqlField struct {
- tableName string
- name string
- flags fieldFlag
- fieldType byte
- decimals byte
-}
-
-type mysqlRows struct {
- mc *mysqlConn
- columns []mysqlField
-}
-
-type binaryRows struct {
- mysqlRows
-}
-
-type textRows struct {
- mysqlRows
-}
-
-type emptyRows struct{}
-
-func (rows *mysqlRows) Columns() []string {
- columns := make([]string, len(rows.columns))
- if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias {
- for i := range columns {
- if tableName := rows.columns[i].tableName; len(tableName) > 0 {
- columns[i] = tableName + "." + rows.columns[i].name
- } else {
- columns[i] = rows.columns[i].name
- }
- }
- } else {
- for i := range columns {
- columns[i] = rows.columns[i].name
- }
- }
- return columns
-}
-
-func (rows *mysqlRows) Close() error {
- mc := rows.mc
- if mc == nil {
- return nil
- }
- if mc.netConn == nil {
- return ErrInvalidConn
- }
-
- // Remove unread packets from stream
- err := mc.readUntilEOF()
- if err == nil {
- if err = mc.discardResults(); err != nil {
- return err
- }
- }
-
- rows.mc = nil
- return err
-}
-
-func (rows *binaryRows) Next(dest []driver.Value) error {
- if mc := rows.mc; mc != nil {
- if mc.netConn == nil {
- return ErrInvalidConn
- }
-
- // Fetch next row from stream
- return rows.readRow(dest)
- }
- return io.EOF
-}
-
-func (rows *textRows) Next(dest []driver.Value) error {
- if mc := rows.mc; mc != nil {
- if mc.netConn == nil {
- return ErrInvalidConn
- }
-
- // Fetch next row from stream
- return rows.readRow(dest)
- }
- return io.EOF
-}
-
-func (rows emptyRows) Columns() []string {
- return nil
-}
-
-func (rows emptyRows) Close() error {
- return nil
-}
-
-func (rows emptyRows) Next(dest []driver.Value) error {
- return io.EOF
-}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/statement.go b/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/statement.go
deleted file mode 100644
index ead9a6b..0000000
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/statement.go
+++ /dev/null
@@ -1,150 +0,0 @@
-// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
-//
-// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this file,
-// You can obtain one at http://mozilla.org/MPL/2.0/.
-
-package mysql
-
-import (
- "database/sql/driver"
- "fmt"
- "reflect"
- "strconv"
-)
-
-type mysqlStmt struct {
- mc *mysqlConn
- id uint32
- paramCount int
- columns []mysqlField // cached from the first query
-}
-
-func (stmt *mysqlStmt) Close() error {
- if stmt.mc == nil || stmt.mc.netConn == nil {
- errLog.Print(ErrInvalidConn)
- return driver.ErrBadConn
- }
-
- err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id)
- stmt.mc = nil
- return err
-}
-
-func (stmt *mysqlStmt) NumInput() int {
- return stmt.paramCount
-}
-
-func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
- return converter{}
-}
-
-func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
- if stmt.mc.netConn == nil {
- errLog.Print(ErrInvalidConn)
- return nil, driver.ErrBadConn
- }
- // Send command
- err := stmt.writeExecutePacket(args)
- if err != nil {
- return nil, err
- }
-
- mc := stmt.mc
-
- mc.affectedRows = 0
- mc.insertId = 0
-
- // Read Result
- resLen, err := mc.readResultSetHeaderPacket()
- if err == nil {
- if resLen > 0 {
- // Columns
- err = mc.readUntilEOF()
- if err != nil {
- return nil, err
- }
-
- // Rows
- err = mc.readUntilEOF()
- }
- if err == nil {
- return &mysqlResult{
- affectedRows: int64(mc.affectedRows),
- insertId: int64(mc.insertId),
- }, nil
- }
- }
-
- return nil, err
-}
-
-func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
- if stmt.mc.netConn == nil {
- errLog.Print(ErrInvalidConn)
- return nil, driver.ErrBadConn
- }
- // Send command
- err := stmt.writeExecutePacket(args)
- if err != nil {
- return nil, err
- }
-
- mc := stmt.mc
-
- // Read Result
- resLen, err := mc.readResultSetHeaderPacket()
- if err != nil {
- return nil, err
- }
-
- rows := new(binaryRows)
-
- if resLen > 0 {
- rows.mc = mc
- // Columns
- // If not cached, read them and cache them
- if stmt.columns == nil {
- rows.columns, err = mc.readColumns(resLen)
- stmt.columns = rows.columns
- } else {
- rows.columns = stmt.columns
- err = mc.readUntilEOF()
- }
- }
-
- return rows, err
-}
-
-type converter struct{}
-
-func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
- if driver.IsValue(v) {
- return v, nil
- }
-
- rv := reflect.ValueOf(v)
- switch rv.Kind() {
- case reflect.Ptr:
- // indirect pointers
- if rv.IsNil() {
- return nil, nil
- }
- return c.ConvertValue(rv.Elem().Interface())
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return rv.Int(), nil
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
- return int64(rv.Uint()), nil
- case reflect.Uint64:
- u64 := rv.Uint()
- if u64 >= 1<<63 {
- return strconv.FormatUint(u64, 10), nil
- }
- return int64(u64), nil
- case reflect.Float32, reflect.Float64:
- return rv.Float(), nil
- }
- return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
-}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/ngaut/log/LICENSE b/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/ngaut/log/LICENSE
deleted file mode 100644
index 6600f1c..0000000
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/ngaut/log/LICENSE
+++ /dev/null
@@ -1,165 +0,0 @@
-GNU LESSER GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc.
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-
- This version of the GNU Lesser General Public License incorporates
-the terms and conditions of version 3 of the GNU General Public
-License, supplemented by the additional permissions listed below.
-
- 0. Additional Definitions.
-
- As used herein, "this License" refers to version 3 of the GNU Lesser
-General Public License, and the "GNU GPL" refers to version 3 of the GNU
-General Public License.
-
- "The Library" refers to a covered work governed by this License,
-other than an Application or a Combined Work as defined below.
-
- An "Application" is any work that makes use of an interface provided
-by the Library, but which is not otherwise based on the Library.
-Defining a subclass of a class defined by the Library is deemed a mode
-of using an interface provided by the Library.
-
- A "Combined Work" is a work produced by combining or linking an
-Application with the Library. The particular version of the Library
-with which the Combined Work was made is also called the "Linked
-Version".
-
- The "Minimal Corresponding Source" for a Combined Work means the
-Corresponding Source for the Combined Work, excluding any source code
-for portions of the Combined Work that, considered in isolation, are
-based on the Application, and not on the Linked Version.
-
- The "Corresponding Application Code" for a Combined Work means the
-object code and/or source code for the Application, including any data
-and utility programs needed for reproducing the Combined Work from the
-Application, but excluding the System Libraries of the Combined Work.
-
- 1. Exception to Section 3 of the GNU GPL.
-
- You may convey a covered work under sections 3 and 4 of this License
-without being bound by section 3 of the GNU GPL.
-
- 2. Conveying Modified Versions.
-
- If you modify a copy of the Library, and, in your modifications, a
-facility refers to a function or data to be supplied by an Application
-that uses the facility (other than as an argument passed when the
-facility is invoked), then you may convey a copy of the modified
-version:
-
- a) under this License, provided that you make a good faith effort to
- ensure that, in the event an Application does not supply the
- function or data, the facility still operates, and performs
- whatever part of its purpose remains meaningful, or
-
- b) under the GNU GPL, with none of the additional permissions of
- this License applicable to that copy.
-
- 3. Object Code Incorporating Material from Library Header Files.
-
- The object code form of an Application may incorporate material from
-a header file that is part of the Library. You may convey such object
-code under terms of your choice, provided that, if the incorporated
-material is not limited to numerical parameters, data structure
-layouts and accessors, or small macros, inline functions and templates
-(ten or fewer lines in length), you do both of the following:
-
- a) Give prominent notice with each copy of the object code that the
- Library is used in it and that the Library and its use are
- covered by this License.
-
- b) Accompany the object code with a copy of the GNU GPL and this license
- document.
-
- 4. Combined Works.
-
- You may convey a Combined Work under terms of your choice that,
-taken together, effectively do not restrict modification of the
-portions of the Library contained in the Combined Work and reverse
-engineering for debugging such modifications, if you also do each of
-the following:
-
- a) Give prominent notice with each copy of the Combined Work that
- the Library is used in it and that the Library and its use are
- covered by this License.
-
- b) Accompany the Combined Work with a copy of the GNU GPL and this license
- document.
-
- c) For a Combined Work that displays copyright notices during
- execution, include the copyright notice for the Library among
- these notices, as well as a reference directing the user to the
- copies of the GNU GPL and this license document.
-
- d) Do one of the following:
-
- 0) Convey the Minimal Corresponding Source under the terms of this
- License, and the Corresponding Application Code in a form
- suitable for, and under terms that permit, the user to
- recombine or relink the Application with a modified version of
- the Linked Version to produce a modified Combined Work, in the
- manner specified by section 6 of the GNU GPL for conveying
- Corresponding Source.
-
- 1) Use a suitable shared library mechanism for linking with the
- Library. A suitable mechanism is one that (a) uses at run time
- a copy of the Library already present on the user's computer
- system, and (b) will operate properly with a modified version
- of the Library that is interface-compatible with the Linked
- Version.
-
- e) Provide Installation Information, but only if you would otherwise
- be required to provide such information under section 6 of the
- GNU GPL, and only to the extent that such information is
- necessary to install and execute a modified version of the
- Combined Work produced by recombining or relinking the
- Application with a modified version of the Linked Version. (If
- you use option 4d0, the Installation Information must accompany
- the Minimal Corresponding Source and Corresponding Application
- Code. If you use option 4d1, you must provide the Installation
- Information in the manner specified by section 6 of the GNU GPL
- for conveying Corresponding Source.)
-
- 5. Combined Libraries.
-
- You may place library facilities that are a work based on the
-Library side by side in a single library together with other library
-facilities that are not Applications and are not covered by this
-License, and convey such a combined library under terms of your
-choice, if you do both of the following:
-
- a) Accompany the combined library with a copy of the same work based
- on the Library, uncombined with any other library facilities,
- conveyed under the terms of this License.
-
- b) Give prominent notice with the combined library that part of it
- is a work based on the Library, and explaining where to find the
- accompanying uncombined form of the same work.
-
- 6. Revised Versions of the GNU Lesser General Public License.
-
- The Free Software Foundation may publish revised and/or new versions
-of the GNU Lesser General Public License from time to time. Such new
-versions will be similar in spirit to the present version, but may
-differ in detail to address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Library as you received it specifies that a certain numbered version
-of the GNU Lesser General Public License "or any later version"
-applies to it, you have the option of following the terms and
-conditions either of that published version or of any later version
-published by the Free Software Foundation. If the Library as you
-received it does not specify a version number of the GNU Lesser
-General Public License, you may choose any version of the GNU Lesser
-General Public License ever published by the Free Software Foundation.
-
- If the Library as you received it specifies that a proxy can decide
-whether future versions of the GNU Lesser General Public License shall
-apply, that proxy's public statement of acceptance of any version is
-permanent authorization for you to choose that version for the
-Library.
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/ngaut/log/crash_unix.go b/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/ngaut/log/crash_unix.go
deleted file mode 100644
index 37f407d..0000000
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/ngaut/log/crash_unix.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// +build freebsd openbsd netbsd dragonfly darwin linux
-
-package log
-
-import (
- "log"
- "os"
- "syscall"
-)
-
-func CrashLog(file string) {
- f, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
- if err != nil {
- log.Println(err.Error())
- } else {
- syscall.Dup2(int(f.Fd()), 2)
- }
-}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/ngaut/log/crash_win.go b/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/ngaut/log/crash_win.go
deleted file mode 100644
index 7d612ee..0000000
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/ngaut/log/crash_win.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// +build windows
-
-package log
-
-import (
- "log"
- "os"
- "syscall"
-)
-
-var (
- kernel32 = syscall.MustLoadDLL("kernel32.dll")
- procSetStdHandle = kernel32.MustFindProc("SetStdHandle")
-)
-
-func setStdHandle(stdhandle int32, handle syscall.Handle) error {
- r0, _, e1 := syscall.Syscall(procSetStdHandle.Addr(), 2, uintptr(stdhandle), uintptr(handle), 0)
- if r0 == 0 {
- if e1 != 0 {
- return error(e1)
- }
- return syscall.EINVAL
- }
- return nil
-}
-
-func CrashLog(file string) {
- f, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
- if err != nil {
- log.Println(err.Error())
- } else {
- err = setStdHandle(syscall.STD_ERROR_HANDLE, syscall.Handle(f.Fd()))
- if err != nil {
- log.Println(err.Error())
- }
- }
-}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/ngaut/log/log.go b/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/ngaut/log/log.go
deleted file mode 100644
index 896b393..0000000
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/ngaut/log/log.go
+++ /dev/null
@@ -1,380 +0,0 @@
-//high level log wrapper, so it can output different log based on level
-package log
-
-import (
- "fmt"
- "io"
- "log"
- "os"
- "runtime"
- "sync"
- "time"
-)
-
-const (
- Ldate = log.Ldate
- Llongfile = log.Llongfile
- Lmicroseconds = log.Lmicroseconds
- Lshortfile = log.Lshortfile
- LstdFlags = log.LstdFlags
- Ltime = log.Ltime
-)
-
-type (
- LogLevel int
- LogType int
-)
-
-const (
- LOG_FATAL = LogType(0x1)
- LOG_ERROR = LogType(0x2)
- LOG_WARNING = LogType(0x4)
- LOG_INFO = LogType(0x8)
- LOG_DEBUG = LogType(0x10)
-)
-
-const (
- LOG_LEVEL_NONE = LogLevel(0x0)
- LOG_LEVEL_FATAL = LOG_LEVEL_NONE | LogLevel(LOG_FATAL)
- LOG_LEVEL_ERROR = LOG_LEVEL_FATAL | LogLevel(LOG_ERROR)
- LOG_LEVEL_WARN = LOG_LEVEL_ERROR | LogLevel(LOG_WARNING)
- LOG_LEVEL_INFO = LOG_LEVEL_WARN | LogLevel(LOG_INFO)
- LOG_LEVEL_DEBUG = LOG_LEVEL_INFO | LogLevel(LOG_DEBUG)
- LOG_LEVEL_ALL = LOG_LEVEL_DEBUG
-)
-
-const FORMAT_TIME_DAY string = "20060102"
-const FORMAT_TIME_HOUR string = "2006010215"
-
-var _log *logger = New()
-
-func init() {
- SetFlags(Ldate | Ltime | Lshortfile)
- SetHighlighting(runtime.GOOS != "windows")
-}
-
-func Logger() *log.Logger {
- return _log._log
-}
-
-func SetLevel(level LogLevel) {
- _log.SetLevel(level)
-}
-func GetLogLevel() LogLevel {
- return _log.level
-}
-
-func SetOutput(out io.Writer) {
- _log.SetOutput(out)
-}
-
-func SetOutputByName(path string) error {
- return _log.SetOutputByName(path)
-}
-
-func SetFlags(flags int) {
- _log._log.SetFlags(flags)
-}
-
-func Info(v ...interface{}) {
- _log.Info(v...)
-}
-
-func Infof(format string, v ...interface{}) {
- _log.Infof(format, v...)
-}
-
-func Debug(v ...interface{}) {
- _log.Debug(v...)
-}
-
-func Debugf(format string, v ...interface{}) {
- _log.Debugf(format, v...)
-}
-
-func Warn(v ...interface{}) {
- _log.Warning(v...)
-}
-
-func Warnf(format string, v ...interface{}) {
- _log.Warningf(format, v...)
-}
-
-func Warning(v ...interface{}) {
- _log.Warning(v...)
-}
-
-func Warningf(format string, v ...interface{}) {
- _log.Warningf(format, v...)
-}
-
-func Error(v ...interface{}) {
- _log.Error(v...)
-}
-
-func Errorf(format string, v ...interface{}) {
- _log.Errorf(format, v...)
-}
-
-func Fatal(v ...interface{}) {
- _log.Fatal(v...)
-}
-
-func Fatalf(format string, v ...interface{}) {
- _log.Fatalf(format, v...)
-}
-
-func SetLevelByString(level string) {
- _log.SetLevelByString(level)
-}
-
-func SetHighlighting(highlighting bool) {
- _log.SetHighlighting(highlighting)
-}
-
-func SetRotateByDay() {
- _log.SetRotateByDay()
-}
-
-func SetRotateByHour() {
- _log.SetRotateByHour()
-}
-
-type logger struct {
- _log *log.Logger
- level LogLevel
- highlighting bool
-
- dailyRolling bool
- hourRolling bool
-
- fileName string
- logSuffix string
- fd *os.File
-
- lock sync.Mutex
-}
-
-func (l *logger) SetHighlighting(highlighting bool) {
- l.highlighting = highlighting
-}
-
-func (l *logger) SetLevel(level LogLevel) {
- l.level = level
-}
-
-func (l *logger) SetLevelByString(level string) {
- l.level = StringToLogLevel(level)
-}
-
-func (l *logger) SetRotateByDay() {
- l.dailyRolling = true
- l.logSuffix = genDayTime(time.Now())
-}
-
-func (l *logger) SetRotateByHour() {
- l.hourRolling = true
- l.logSuffix = genHourTime(time.Now())
-}
-
-func (l *logger) rotate() error {
- l.lock.Lock()
- defer l.lock.Unlock()
-
- var suffix string
- if l.dailyRolling {
- suffix = genDayTime(time.Now())
- } else if l.hourRolling {
- suffix = genHourTime(time.Now())
- } else {
- return nil
- }
-
- // Notice: if suffix is not equal to l.LogSuffix, then rotate
- if suffix != l.logSuffix {
- err := l.doRotate(suffix)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (l *logger) doRotate(suffix string) error {
- // Notice: Not check error, is this ok?
- l.fd.Close()
-
- lastFileName := l.fileName + "." + l.logSuffix
- err := os.Rename(l.fileName, lastFileName)
- if err != nil {
- return err
- }
-
- err = l.SetOutputByName(l.fileName)
- if err != nil {
- return err
- }
-
- l.logSuffix = suffix
-
- return nil
-}
-
-func (l *logger) SetOutput(out io.Writer) {
- l._log = log.New(out, l._log.Prefix(), l._log.Flags())
-}
-
-func (l *logger) SetOutputByName(path string) error {
- f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
- if err != nil {
- log.Fatal(err)
- }
-
- l.SetOutput(f)
-
- l.fileName = path
- l.fd = f
-
- return err
-}
-
-func (l *logger) log(t LogType, v ...interface{}) {
- if l.level|LogLevel(t) != l.level {
- return
- }
-
- err := l.rotate()
- if err != nil {
- fmt.Fprintf(os.Stderr, "%s\n", err.Error())
- return
- }
-
- v1 := make([]interface{}, len(v)+2)
- logStr, logColor := LogTypeToString(t)
- if l.highlighting {
- v1[0] = "\033" + logColor + "m[" + logStr + "]"
- copy(v1[1:], v)
- v1[len(v)+1] = "\033[0m"
- } else {
- v1[0] = "[" + logStr + "]"
- copy(v1[1:], v)
- v1[len(v)+1] = ""
- }
-
- s := fmt.Sprintln(v1...)
- l._log.Output(4, s)
-}
-
-func (l *logger) logf(t LogType, format string, v ...interface{}) {
- if l.level|LogLevel(t) != l.level {
- return
- }
-
- err := l.rotate()
- if err != nil {
- fmt.Fprintf(os.Stderr, "%s\n", err.Error())
- return
- }
-
- logStr, logColor := LogTypeToString(t)
- var s string
- if l.highlighting {
- s = "\033" + logColor + "m[" + logStr + "] " + fmt.Sprintf(format, v...) + "\033[0m"
- } else {
- s = "[" + logStr + "] " + fmt.Sprintf(format, v...)
- }
- l._log.Output(4, s)
-}
-
-func (l *logger) Fatal(v ...interface{}) {
- l.log(LOG_FATAL, v...)
- os.Exit(-1)
-}
-
-func (l *logger) Fatalf(format string, v ...interface{}) {
- l.logf(LOG_FATAL, format, v...)
- os.Exit(-1)
-}
-
-func (l *logger) Error(v ...interface{}) {
- l.log(LOG_ERROR, v...)
-}
-
-func (l *logger) Errorf(format string, v ...interface{}) {
- l.logf(LOG_ERROR, format, v...)
-}
-
-func (l *logger) Warning(v ...interface{}) {
- l.log(LOG_WARNING, v...)
-}
-
-func (l *logger) Warningf(format string, v ...interface{}) {
- l.logf(LOG_WARNING, format, v...)
-}
-
-func (l *logger) Debug(v ...interface{}) {
- l.log(LOG_DEBUG, v...)
-}
-
-func (l *logger) Debugf(format string, v ...interface{}) {
- l.logf(LOG_DEBUG, format, v...)
-}
-
-func (l *logger) Info(v ...interface{}) {
- l.log(LOG_INFO, v...)
-}
-
-func (l *logger) Infof(format string, v ...interface{}) {
- l.logf(LOG_INFO, format, v...)
-}
-
-func StringToLogLevel(level string) LogLevel {
- switch level {
- case "fatal":
- return LOG_LEVEL_FATAL
- case "error":
- return LOG_LEVEL_ERROR
- case "warn":
- return LOG_LEVEL_WARN
- case "warning":
- return LOG_LEVEL_WARN
- case "debug":
- return LOG_LEVEL_DEBUG
- case "info":
- return LOG_LEVEL_INFO
- }
- return LOG_LEVEL_ALL
-}
-
-func LogTypeToString(t LogType) (string, string) {
- switch t {
- case LOG_FATAL:
- return "fatal", "[0;31"
- case LOG_ERROR:
- return "error", "[0;31"
- case LOG_WARNING:
- return "warning", "[0;33"
- case LOG_DEBUG:
- return "debug", "[0;36"
- case LOG_INFO:
- return "info", "[0;37"
- }
- return "unknown", "[0;37"
-}
-
-func genDayTime(t time.Time) string {
- return t.Format(FORMAT_TIME_DAY)
-}
-
-func genHourTime(t time.Time) string {
- return t.Format(FORMAT_TIME_HOUR)
-}
-
-func New() *logger {
- return Newlogger(os.Stderr, "")
-}
-
-func Newlogger(w io.Writer, prefix string) *logger {
- return &logger{_log: log.New(w, prefix, LstdFlags), level: LOG_LEVEL_ALL, highlighting: true}
-}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/satori/go.uuid/uuid.go b/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/satori/go.uuid/uuid.go
deleted file mode 100644
index 9c7fbaa..0000000
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/satori/go.uuid/uuid.go
+++ /dev/null
@@ -1,488 +0,0 @@
-// Copyright (C) 2013-2015 by Maxim Bublis
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-// Package uuid provides implementation of Universally Unique Identifier (UUID).
-// Supported versions are 1, 3, 4 and 5 (as specified in RFC 4122) and
-// version 2 (as specified in DCE 1.1).
-package uuid
-
-import (
- "bytes"
- "crypto/md5"
- "crypto/rand"
- "crypto/sha1"
- "database/sql/driver"
- "encoding/binary"
- "encoding/hex"
- "fmt"
- "hash"
- "net"
- "os"
- "sync"
- "time"
-)
-
-// UUID layout variants.
-const (
- VariantNCS = iota
- VariantRFC4122
- VariantMicrosoft
- VariantFuture
-)
-
-// UUID DCE domains.
-const (
- DomainPerson = iota
- DomainGroup
- DomainOrg
-)
-
-// Difference in 100-nanosecond intervals between
-// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970).
-const epochStart = 122192928000000000
-
-// Used in string method conversion
-const dash byte = '-'
-
-// UUID v1/v2 storage.
-var (
- storageMutex sync.Mutex
- storageOnce sync.Once
- epochFunc = unixTimeFunc
- clockSequence uint16
- lastTime uint64
- hardwareAddr [6]byte
- posixUID = uint32(os.Getuid())
- posixGID = uint32(os.Getgid())
-)
-
-// String parse helpers.
-var (
- urnPrefix = []byte("urn:uuid:")
- byteGroups = []int{8, 4, 4, 4, 12}
-)
-
-func initClockSequence() {
- buf := make([]byte, 2)
- safeRandom(buf)
- clockSequence = binary.BigEndian.Uint16(buf)
-}
-
-func initHardwareAddr() {
- interfaces, err := net.Interfaces()
- if err == nil {
- for _, iface := range interfaces {
- if len(iface.HardwareAddr) >= 6 {
- copy(hardwareAddr[:], iface.HardwareAddr)
- return
- }
- }
- }
-
- // Initialize hardwareAddr randomly in case
- // of real network interfaces absence
- safeRandom(hardwareAddr[:])
-
- // Set multicast bit as recommended in RFC 4122
- hardwareAddr[0] |= 0x01
-}
-
-func initStorage() {
- initClockSequence()
- initHardwareAddr()
-}
-
-func safeRandom(dest []byte) {
- if _, err := rand.Read(dest); err != nil {
- panic(err)
- }
-}
-
-// Returns difference in 100-nanosecond intervals between
-// UUID epoch (October 15, 1582) and current time.
-// This is default epoch calculation function.
-func unixTimeFunc() uint64 {
- return epochStart + uint64(time.Now().UnixNano()/100)
-}
-
-// UUID representation compliant with specification
-// described in RFC 4122.
-type UUID [16]byte
-
-// NullUUID can be used with the standard sql package to represent a
-// UUID value that can be NULL in the database
-type NullUUID struct {
- UUID UUID
- Valid bool
-}
-
-// The nil UUID is special form of UUID that is specified to have all
-// 128 bits set to zero.
-var Nil = UUID{}
-
-// Predefined namespace UUIDs.
-var (
- NamespaceDNS, _ = FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
- NamespaceURL, _ = FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
- NamespaceOID, _ = FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
- NamespaceX500, _ = FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
-)
-
-// And returns result of binary AND of two UUIDs.
-func And(u1 UUID, u2 UUID) UUID {
- u := UUID{}
- for i := 0; i < 16; i++ {
- u[i] = u1[i] & u2[i]
- }
- return u
-}
-
-// Or returns result of binary OR of two UUIDs.
-func Or(u1 UUID, u2 UUID) UUID {
- u := UUID{}
- for i := 0; i < 16; i++ {
- u[i] = u1[i] | u2[i]
- }
- return u
-}
-
-// Equal returns true if u1 and u2 equals, otherwise returns false.
-func Equal(u1 UUID, u2 UUID) bool {
- return bytes.Equal(u1[:], u2[:])
-}
-
-// Version returns algorithm version used to generate UUID.
-func (u UUID) Version() uint {
- return uint(u[6] >> 4)
-}
-
-// Variant returns UUID layout variant.
-func (u UUID) Variant() uint {
- switch {
- case (u[8] & 0x80) == 0x00:
- return VariantNCS
- case (u[8]&0xc0)|0x80 == 0x80:
- return VariantRFC4122
- case (u[8]&0xe0)|0xc0 == 0xc0:
- return VariantMicrosoft
- }
- return VariantFuture
-}
-
-// Bytes returns bytes slice representation of UUID.
-func (u UUID) Bytes() []byte {
- return u[:]
-}
-
-// Returns canonical string representation of UUID:
-// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
-func (u UUID) String() string {
- buf := make([]byte, 36)
-
- hex.Encode(buf[0:8], u[0:4])
- buf[8] = dash
- hex.Encode(buf[9:13], u[4:6])
- buf[13] = dash
- hex.Encode(buf[14:18], u[6:8])
- buf[18] = dash
- hex.Encode(buf[19:23], u[8:10])
- buf[23] = dash
- hex.Encode(buf[24:], u[10:])
-
- return string(buf)
-}
-
-// SetVersion sets version bits.
-func (u *UUID) SetVersion(v byte) {
- u[6] = (u[6] & 0x0f) | (v << 4)
-}
-
-// SetVariant sets variant bits as described in RFC 4122.
-func (u *UUID) SetVariant() {
- u[8] = (u[8] & 0xbf) | 0x80
-}
-
-// MarshalText implements the encoding.TextMarshaler interface.
-// The encoding is the same as returned by String.
-func (u UUID) MarshalText() (text []byte, err error) {
- text = []byte(u.String())
- return
-}
-
-// UnmarshalText implements the encoding.TextUnmarshaler interface.
-// Following formats are supported:
-// "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
-// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}",
-// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
-func (u *UUID) UnmarshalText(text []byte) (err error) {
- if len(text) < 32 {
- err = fmt.Errorf("uuid: UUID string too short: %s", text)
- return
- }
-
- t := text[:]
- braced := false
-
- if bytes.Equal(t[:9], urnPrefix) {
- t = t[9:]
- } else if t[0] == '{' {
- braced = true
- t = t[1:]
- }
-
- b := u[:]
-
- for i, byteGroup := range byteGroups {
- if i > 0 && t[0] == '-' {
- t = t[1:]
- } else if i > 0 && t[0] != '-' {
- err = fmt.Errorf("uuid: invalid string format")
- return
- }
-
- if i == 2 {
- if !bytes.Contains([]byte("012345"), []byte{t[0]}) {
- err = fmt.Errorf("uuid: invalid version number: %s", t[0])
- return
- }
- }
-
- if len(t) < byteGroup {
- err = fmt.Errorf("uuid: UUID string too short: %s", text)
- return
- }
-
- if i == 4 && len(t) > byteGroup &&
- ((braced && t[byteGroup] != '}') || len(t[byteGroup:]) > 1 || !braced) {
- err = fmt.Errorf("uuid: UUID string too long: %s", t)
- return
- }
-
- _, err = hex.Decode(b[:byteGroup/2], t[:byteGroup])
-
- if err != nil {
- return
- }
-
- t = t[byteGroup:]
- b = b[byteGroup/2:]
- }
-
- return
-}
-
-// MarshalBinary implements the encoding.BinaryMarshaler interface.
-func (u UUID) MarshalBinary() (data []byte, err error) {
- data = u.Bytes()
- return
-}
-
-// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
-// It will return error if the slice isn't 16 bytes long.
-func (u *UUID) UnmarshalBinary(data []byte) (err error) {
- if len(data) != 16 {
- err = fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data))
- return
- }
- copy(u[:], data)
-
- return
-}
-
-// Value implements the driver.Valuer interface.
-func (u UUID) Value() (driver.Value, error) {
- return u.String(), nil
-}
-
-// Scan implements the sql.Scanner interface.
-// A 16-byte slice is handled by UnmarshalBinary, while
-// a longer byte slice or a string is handled by UnmarshalText.
-func (u *UUID) Scan(src interface{}) error {
- switch src := src.(type) {
- case []byte:
- if len(src) == 16 {
- return u.UnmarshalBinary(src)
- }
- return u.UnmarshalText(src)
-
- case string:
- return u.UnmarshalText([]byte(src))
- }
-
- return fmt.Errorf("uuid: cannot convert %T to UUID", src)
-}
-
-// Value implements the driver.Valuer interface.
-func (u NullUUID) Value() (driver.Value, error) {
- if !u.Valid {
- return nil, nil
- }
- // Delegate to UUID Value function
- return u.UUID.Value()
-}
-
-// Scan implements the sql.Scanner interface.
-func (u *NullUUID) Scan(src interface{}) error {
- if src == nil {
- u.UUID, u.Valid = Nil, false
- return nil
- }
-
- // Delegate to UUID Scan function
- u.Valid = true
- return u.UUID.Scan(src)
-}
-
-// FromBytes returns UUID converted from raw byte slice input.
-// It will return error if the slice isn't 16 bytes long.
-func FromBytes(input []byte) (u UUID, err error) {
- err = u.UnmarshalBinary(input)
- return
-}
-
-// FromBytesOrNil returns UUID converted from raw byte slice input.
-// Same behavior as FromBytes, but returns a Nil UUID on error.
-func FromBytesOrNil(input []byte) UUID {
- uuid, err := FromBytes(input)
- if err != nil {
- return Nil
- }
- return uuid
-}
-
-// FromString returns UUID parsed from string input.
-// Input is expected in a form accepted by UnmarshalText.
-func FromString(input string) (u UUID, err error) {
- err = u.UnmarshalText([]byte(input))
- return
-}
-
-// FromStringOrNil returns UUID parsed from string input.
-// Same behavior as FromString, but returns a Nil UUID on error.
-func FromStringOrNil(input string) UUID {
- uuid, err := FromString(input)
- if err != nil {
- return Nil
- }
- return uuid
-}
-
-// Returns UUID v1/v2 storage state.
-// Returns epoch timestamp, clock sequence, and hardware address.
-func getStorage() (uint64, uint16, []byte) {
- storageOnce.Do(initStorage)
-
- storageMutex.Lock()
- defer storageMutex.Unlock()
-
- timeNow := epochFunc()
- // Clock changed backwards since last UUID generation.
- // Should increase clock sequence.
- if timeNow <= lastTime {
- clockSequence++
- }
- lastTime = timeNow
-
- return timeNow, clockSequence, hardwareAddr[:]
-}
-
-// NewV1 returns UUID based on current timestamp and MAC address.
-func NewV1() UUID {
- u := UUID{}
-
- timeNow, clockSeq, hardwareAddr := getStorage()
-
- binary.BigEndian.PutUint32(u[0:], uint32(timeNow))
- binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32))
- binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48))
- binary.BigEndian.PutUint16(u[8:], clockSeq)
-
- copy(u[10:], hardwareAddr)
-
- u.SetVersion(1)
- u.SetVariant()
-
- return u
-}
-
-// NewV2 returns DCE Security UUID based on POSIX UID/GID.
-func NewV2(domain byte) UUID {
- u := UUID{}
-
- timeNow, clockSeq, hardwareAddr := getStorage()
-
- switch domain {
- case DomainPerson:
- binary.BigEndian.PutUint32(u[0:], posixUID)
- case DomainGroup:
- binary.BigEndian.PutUint32(u[0:], posixGID)
- }
-
- binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32))
- binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48))
- binary.BigEndian.PutUint16(u[8:], clockSeq)
- u[9] = domain
-
- copy(u[10:], hardwareAddr)
-
- u.SetVersion(2)
- u.SetVariant()
-
- return u
-}
-
-// NewV3 returns UUID based on MD5 hash of namespace UUID and name.
-func NewV3(ns UUID, name string) UUID {
- u := newFromHash(md5.New(), ns, name)
- u.SetVersion(3)
- u.SetVariant()
-
- return u
-}
-
-// NewV4 returns random generated UUID.
-func NewV4() UUID {
- u := UUID{}
- safeRandom(u[:])
- u.SetVersion(4)
- u.SetVariant()
-
- return u
-}
-
-// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name.
-func NewV5(ns UUID, name string) UUID {
- u := newFromHash(sha1.New(), ns, name)
- u.SetVersion(5)
- u.SetVariant()
-
- return u
-}
-
-// Returns UUID based on hashing of namespace UUID and name.
-func newFromHash(h hash.Hash, ns UUID, name string) UUID {
- u := UUID{}
- h.Write(ns[:])
- h.Write([]byte(name))
- copy(u[:], h.Sum(nil))
-
- return u
-}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/siddontang/go/ioutil2/ioutil.go b/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/siddontang/go/ioutil2/ioutil.go
deleted file mode 100644
index c99c987..0000000
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/siddontang/go/ioutil2/ioutil.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2012, Google Inc. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package ioutil2
-
-import (
- "io"
- "io/ioutil"
- "os"
- "path"
-)
-
-// Write file to temp and atomically move when everything else succeeds.
-func WriteFileAtomic(filename string, data []byte, perm os.FileMode) error {
- dir, name := path.Split(filename)
- f, err := ioutil.TempFile(dir, name)
- if err != nil {
- return err
- }
- n, err := f.Write(data)
- f.Close()
- if err == nil && n < len(data) {
- err = io.ErrShortWrite
- } else {
- err = os.Chmod(f.Name(), perm)
- }
- if err != nil {
- os.Remove(f.Name())
- return err
- }
- return os.Rename(f.Name(), filename)
-}
-
-// Check file exists or not
-func FileExists(name string) bool {
- _, err := os.Stat(name)
- return !os.IsNotExist(err)
-}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/siddontang/go/ioutil2/sectionwriter.go b/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/siddontang/go/ioutil2/sectionwriter.go
deleted file mode 100644
index c02ab0d..0000000
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/siddontang/go/ioutil2/sectionwriter.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package ioutil2
-
-import (
- "errors"
- "io"
-)
-
-var ErrExceedLimit = errors.New("write exceed limit")
-
-func NewSectionWriter(w io.WriterAt, off int64, n int64) *SectionWriter {
- return &SectionWriter{w, off, off, off + n}
-}
-
-type SectionWriter struct {
- w io.WriterAt
- base int64
- off int64
- limit int64
-}
-
-func (s *SectionWriter) Write(p []byte) (n int, err error) {
- if s.off >= s.limit {
- return 0, ErrExceedLimit
- }
-
- if max := s.limit - s.off; int64(len(p)) > max {
- return 0, ErrExceedLimit
- }
-
- n, err = s.w.WriteAt(p, s.off)
- s.off += int64(n)
- return
-}
-
-var errWhence = errors.New("Seek: invalid whence")
-var errOffset = errors.New("Seek: invalid offset")
-
-func (s *SectionWriter) Seek(offset int64, whence int) (int64, error) {
- switch whence {
- default:
- return 0, errWhence
- case 0:
- offset += s.base
- case 1:
- offset += s.off
- case 2:
- offset += s.limit
- }
- if offset < s.base {
- return 0, errOffset
- }
- s.off = offset
- return offset - s.base, nil
-}
-
-func (s *SectionWriter) WriteAt(p []byte, off int64) (n int, err error) {
- if off < 0 || off >= s.limit-s.base {
- return 0, errOffset
- }
- off += s.base
- if max := s.limit - off; int64(len(p)) > max {
- return 0, ErrExceedLimit
- }
-
- return s.w.WriteAt(p, off)
-}
-
-// Size returns the size of the section in bytes.
-func (s *SectionWriter) Size() int64 { return s.limit - s.base }
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/golang.org/x/net/PATENTS b/vendor/github.com/siddontang/go-mysql/_vendor/vendor/golang.org/x/net/PATENTS
deleted file mode 100644
index 7330990..0000000
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/golang.org/x/net/PATENTS
+++ /dev/null
@@ -1,22 +0,0 @@
-Additional IP Rights Grant (Patents)
-
-"This implementation" means the copyrightable works distributed by
-Google as part of the Go project.
-
-Google hereby grants to You a perpetual, worldwide, non-exclusive,
-no-charge, royalty-free, irrevocable (except as stated in this section)
-patent license to make, have made, use, offer to sell, sell, import,
-transfer and otherwise run, modify and propagate the contents of this
-implementation of Go, where such license applies only to those patent
-claims, both currently owned or controlled by Google and acquired in
-the future, licensable by Google that are necessarily infringed by this
-implementation of Go. This grant does not include claims that would be
-infringed only as a consequence of further modification of this
-implementation. If you or your agent or exclusive licensee institute or
-order or agree to the institution of patent litigation against any
-entity (including a cross-claim or counterclaim in a lawsuit) alleging
-that this implementation of Go or any code incorporated within this
-implementation of Go constitutes direct or contributory patent
-infringement, or inducement of patent infringement, then any patent
-rights granted to you under this License for this implementation of Go
-shall terminate as of the date such litigation is filed.
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/golang.org/x/net/context/context.go b/vendor/github.com/siddontang/go-mysql/_vendor/vendor/golang.org/x/net/context/context.go
deleted file mode 100644
index 77b64d0..0000000
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/golang.org/x/net/context/context.go
+++ /dev/null
@@ -1,447 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package context defines the Context type, which carries deadlines,
-// cancelation signals, and other request-scoped values across API boundaries
-// and between processes.
-//
-// Incoming requests to a server should create a Context, and outgoing calls to
-// servers should accept a Context. The chain of function calls between must
-// propagate the Context, optionally replacing it with a modified copy created
-// using WithDeadline, WithTimeout, WithCancel, or WithValue.
-//
-// Programs that use Contexts should follow these rules to keep interfaces
-// consistent across packages and enable static analysis tools to check context
-// propagation:
-//
-// Do not store Contexts inside a struct type; instead, pass a Context
-// explicitly to each function that needs it. The Context should be the first
-// parameter, typically named ctx:
-//
-// func DoSomething(ctx context.Context, arg Arg) error {
-// // ... use ctx ...
-// }
-//
-// Do not pass a nil Context, even if a function permits it. Pass context.TODO
-// if you are unsure about which Context to use.
-//
-// Use context Values only for request-scoped data that transits processes and
-// APIs, not for passing optional parameters to functions.
-//
-// The same Context may be passed to functions running in different goroutines;
-// Contexts are safe for simultaneous use by multiple goroutines.
-//
-// See http://blog.golang.org/context for example code for a server that uses
-// Contexts.
-package context // import "golang.org/x/net/context"
-
-import (
- "errors"
- "fmt"
- "sync"
- "time"
-)
-
-// A Context carries a deadline, a cancelation signal, and other values across
-// API boundaries.
-//
-// Context's methods may be called by multiple goroutines simultaneously.
-type Context interface {
- // Deadline returns the time when work done on behalf of this context
- // should be canceled. Deadline returns ok==false when no deadline is
- // set. Successive calls to Deadline return the same results.
- Deadline() (deadline time.Time, ok bool)
-
- // Done returns a channel that's closed when work done on behalf of this
- // context should be canceled. Done may return nil if this context can
- // never be canceled. Successive calls to Done return the same value.
- //
- // WithCancel arranges for Done to be closed when cancel is called;
- // WithDeadline arranges for Done to be closed when the deadline
- // expires; WithTimeout arranges for Done to be closed when the timeout
- // elapses.
- //
- // Done is provided for use in select statements:
- //
- // // Stream generates values with DoSomething and sends them to out
- // // until DoSomething returns an error or ctx.Done is closed.
- // func Stream(ctx context.Context, out <-chan Value) error {
- // for {
- // v, err := DoSomething(ctx)
- // if err != nil {
- // return err
- // }
- // select {
- // case <-ctx.Done():
- // return ctx.Err()
- // case out <- v:
- // }
- // }
- // }
- //
- // See http://blog.golang.org/pipelines for more examples of how to use
- // a Done channel for cancelation.
- Done() <-chan struct{}
-
- // Err returns a non-nil error value after Done is closed. Err returns
- // Canceled if the context was canceled or DeadlineExceeded if the
- // context's deadline passed. No other values for Err are defined.
- // After Done is closed, successive calls to Err return the same value.
- Err() error
-
- // Value returns the value associated with this context for key, or nil
- // if no value is associated with key. Successive calls to Value with
- // the same key returns the same result.
- //
- // Use context values only for request-scoped data that transits
- // processes and API boundaries, not for passing optional parameters to
- // functions.
- //
- // A key identifies a specific value in a Context. Functions that wish
- // to store values in Context typically allocate a key in a global
- // variable then use that key as the argument to context.WithValue and
- // Context.Value. A key can be any type that supports equality;
- // packages should define keys as an unexported type to avoid
- // collisions.
- //
- // Packages that define a Context key should provide type-safe accessors
- // for the values stores using that key:
- //
- // // Package user defines a User type that's stored in Contexts.
- // package user
- //
- // import "golang.org/x/net/context"
- //
- // // User is the type of value stored in the Contexts.
- // type User struct {...}
- //
- // // key is an unexported type for keys defined in this package.
- // // This prevents collisions with keys defined in other packages.
- // type key int
- //
- // // userKey is the key for user.User values in Contexts. It is
- // // unexported; clients use user.NewContext and user.FromContext
- // // instead of using this key directly.
- // var userKey key = 0
- //
- // // NewContext returns a new Context that carries value u.
- // func NewContext(ctx context.Context, u *User) context.Context {
- // return context.WithValue(ctx, userKey, u)
- // }
- //
- // // FromContext returns the User value stored in ctx, if any.
- // func FromContext(ctx context.Context) (*User, bool) {
- // u, ok := ctx.Value(userKey).(*User)
- // return u, ok
- // }
- Value(key interface{}) interface{}
-}
-
-// Canceled is the error returned by Context.Err when the context is canceled.
-var Canceled = errors.New("context canceled")
-
-// DeadlineExceeded is the error returned by Context.Err when the context's
-// deadline passes.
-var DeadlineExceeded = errors.New("context deadline exceeded")
-
-// An emptyCtx is never canceled, has no values, and has no deadline. It is not
-// struct{}, since vars of this type must have distinct addresses.
-type emptyCtx int
-
-func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
- return
-}
-
-func (*emptyCtx) Done() <-chan struct{} {
- return nil
-}
-
-func (*emptyCtx) Err() error {
- return nil
-}
-
-func (*emptyCtx) Value(key interface{}) interface{} {
- return nil
-}
-
-func (e *emptyCtx) String() string {
- switch e {
- case background:
- return "context.Background"
- case todo:
- return "context.TODO"
- }
- return "unknown empty Context"
-}
-
-var (
- background = new(emptyCtx)
- todo = new(emptyCtx)
-)
-
-// Background returns a non-nil, empty Context. It is never canceled, has no
-// values, and has no deadline. It is typically used by the main function,
-// initialization, and tests, and as the top-level Context for incoming
-// requests.
-func Background() Context {
- return background
-}
-
-// TODO returns a non-nil, empty Context. Code should use context.TODO when
-// it's unclear which Context to use or it is not yet available (because the
-// surrounding function has not yet been extended to accept a Context
-// parameter). TODO is recognized by static analysis tools that determine
-// whether Contexts are propagated correctly in a program.
-func TODO() Context {
- return todo
-}
-
-// A CancelFunc tells an operation to abandon its work.
-// A CancelFunc does not wait for the work to stop.
-// After the first call, subsequent calls to a CancelFunc do nothing.
-type CancelFunc func()
-
-// WithCancel returns a copy of parent with a new Done channel. The returned
-// context's Done channel is closed when the returned cancel function is called
-// or when the parent context's Done channel is closed, whichever happens first.
-//
-// Canceling this context releases resources associated with it, so code should
-// call cancel as soon as the operations running in this Context complete.
-func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
- c := newCancelCtx(parent)
- propagateCancel(parent, &c)
- return &c, func() { c.cancel(true, Canceled) }
-}
-
-// newCancelCtx returns an initialized cancelCtx.
-func newCancelCtx(parent Context) cancelCtx {
- return cancelCtx{
- Context: parent,
- done: make(chan struct{}),
- }
-}
-
-// propagateCancel arranges for child to be canceled when parent is.
-func propagateCancel(parent Context, child canceler) {
- if parent.Done() == nil {
- return // parent is never canceled
- }
- if p, ok := parentCancelCtx(parent); ok {
- p.mu.Lock()
- if p.err != nil {
- // parent has already been canceled
- child.cancel(false, p.err)
- } else {
- if p.children == nil {
- p.children = make(map[canceler]bool)
- }
- p.children[child] = true
- }
- p.mu.Unlock()
- } else {
- go func() {
- select {
- case <-parent.Done():
- child.cancel(false, parent.Err())
- case <-child.Done():
- }
- }()
- }
-}
-
-// parentCancelCtx follows a chain of parent references until it finds a
-// *cancelCtx. This function understands how each of the concrete types in this
-// package represents its parent.
-func parentCancelCtx(parent Context) (*cancelCtx, bool) {
- for {
- switch c := parent.(type) {
- case *cancelCtx:
- return c, true
- case *timerCtx:
- return &c.cancelCtx, true
- case *valueCtx:
- parent = c.Context
- default:
- return nil, false
- }
- }
-}
-
-// removeChild removes a context from its parent.
-func removeChild(parent Context, child canceler) {
- p, ok := parentCancelCtx(parent)
- if !ok {
- return
- }
- p.mu.Lock()
- if p.children != nil {
- delete(p.children, child)
- }
- p.mu.Unlock()
-}
-
-// A canceler is a context type that can be canceled directly. The
-// implementations are *cancelCtx and *timerCtx.
-type canceler interface {
- cancel(removeFromParent bool, err error)
- Done() <-chan struct{}
-}
-
-// A cancelCtx can be canceled. When canceled, it also cancels any children
-// that implement canceler.
-type cancelCtx struct {
- Context
-
- done chan struct{} // closed by the first cancel call.
-
- mu sync.Mutex
- children map[canceler]bool // set to nil by the first cancel call
- err error // set to non-nil by the first cancel call
-}
-
-func (c *cancelCtx) Done() <-chan struct{} {
- return c.done
-}
-
-func (c *cancelCtx) Err() error {
- c.mu.Lock()
- defer c.mu.Unlock()
- return c.err
-}
-
-func (c *cancelCtx) String() string {
- return fmt.Sprintf("%v.WithCancel", c.Context)
-}
-
-// cancel closes c.done, cancels each of c's children, and, if
-// removeFromParent is true, removes c from its parent's children.
-func (c *cancelCtx) cancel(removeFromParent bool, err error) {
- if err == nil {
- panic("context: internal error: missing cancel error")
- }
- c.mu.Lock()
- if c.err != nil {
- c.mu.Unlock()
- return // already canceled
- }
- c.err = err
- close(c.done)
- for child := range c.children {
- // NOTE: acquiring the child's lock while holding parent's lock.
- child.cancel(false, err)
- }
- c.children = nil
- c.mu.Unlock()
-
- if removeFromParent {
- removeChild(c.Context, c)
- }
-}
-
-// WithDeadline returns a copy of the parent context with the deadline adjusted
-// to be no later than d. If the parent's deadline is already earlier than d,
-// WithDeadline(parent, d) is semantically equivalent to parent. The returned
-// context's Done channel is closed when the deadline expires, when the returned
-// cancel function is called, or when the parent context's Done channel is
-// closed, whichever happens first.
-//
-// Canceling this context releases resources associated with it, so code should
-// call cancel as soon as the operations running in this Context complete.
-func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
- if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
- // The current deadline is already sooner than the new one.
- return WithCancel(parent)
- }
- c := &timerCtx{
- cancelCtx: newCancelCtx(parent),
- deadline: deadline,
- }
- propagateCancel(parent, c)
- d := deadline.Sub(time.Now())
- if d <= 0 {
- c.cancel(true, DeadlineExceeded) // deadline has already passed
- return c, func() { c.cancel(true, Canceled) }
- }
- c.mu.Lock()
- defer c.mu.Unlock()
- if c.err == nil {
- c.timer = time.AfterFunc(d, func() {
- c.cancel(true, DeadlineExceeded)
- })
- }
- return c, func() { c.cancel(true, Canceled) }
-}
-
-// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
-// implement Done and Err. It implements cancel by stopping its timer then
-// delegating to cancelCtx.cancel.
-type timerCtx struct {
- cancelCtx
- timer *time.Timer // Under cancelCtx.mu.
-
- deadline time.Time
-}
-
-func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
- return c.deadline, true
-}
-
-func (c *timerCtx) String() string {
- return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
-}
-
-func (c *timerCtx) cancel(removeFromParent bool, err error) {
- c.cancelCtx.cancel(false, err)
- if removeFromParent {
- // Remove this timerCtx from its parent cancelCtx's children.
- removeChild(c.cancelCtx.Context, c)
- }
- c.mu.Lock()
- if c.timer != nil {
- c.timer.Stop()
- c.timer = nil
- }
- c.mu.Unlock()
-}
-
-// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
-//
-// Canceling this context releases resources associated with it, so code should
-// call cancel as soon as the operations running in this Context complete:
-//
-// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
-// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
-// defer cancel() // releases resources if slowOperation completes before timeout elapses
-// return slowOperation(ctx)
-// }
-func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
- return WithDeadline(parent, time.Now().Add(timeout))
-}
-
-// WithValue returns a copy of parent in which the value associated with key is
-// val.
-//
-// Use context Values only for request-scoped data that transits processes and
-// APIs, not for passing optional parameters to functions.
-func WithValue(parent Context, key interface{}, val interface{}) Context {
- return &valueCtx{parent, key, val}
-}
-
-// A valueCtx carries a key-value pair. It implements Value for that key and
-// delegates all other calls to the embedded Context.
-type valueCtx struct {
- Context
- key, val interface{}
-}
-
-func (c *valueCtx) String() string {
- return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
-}
-
-func (c *valueCtx) Value(key interface{}) interface{} {
- if c.key == key {
- return c.val
- }
- return c.Context.Value(key)
-}
diff --git a/vendor/github.com/siddontang/go-mysql/canal/canal.go b/vendor/github.com/siddontang/go-mysql/canal/canal.go
index 0b225ff..64d9aec 100644
--- a/vendor/github.com/siddontang/go-mysql/canal/canal.go
+++ b/vendor/github.com/siddontang/go-mysql/canal/canal.go
@@ -1,26 +1,25 @@
package canal
import (
+ "context"
"fmt"
"io/ioutil"
"os"
- "path"
+ "regexp"
"strconv"
"strings"
"sync"
+ "time"
"github.com/juju/errors"
- "github.com/ngaut/log"
+ "github.com/siddontang/go-log/log"
"github.com/siddontang/go-mysql/client"
"github.com/siddontang/go-mysql/dump"
"github.com/siddontang/go-mysql/mysql"
"github.com/siddontang/go-mysql/replication"
"github.com/siddontang/go-mysql/schema"
- "github.com/siddontang/go/sync2"
)
-var errCanalClosed = errors.New("canal was closed")
-
// Canal can sync your MySQL data into everywhere, like Elasticsearch, Redis, etc...
// MySQL must open row format for binlog
type Canal struct {
@@ -30,48 +29,49 @@ type Canal struct {
master *masterInfo
dumper *dump.Dumper
+ dumped bool
dumpDoneCh chan struct{}
syncer *replication.BinlogSyncer
- rsLock sync.Mutex
- rsHandlers []RowsEventHandler
+ eventHandler EventHandler
connLock sync.Mutex
conn *client.Conn
- wg sync.WaitGroup
+ tableLock sync.RWMutex
+ tables map[string]*schema.Table
+ errorTablesGetTime map[string]time.Time
- tableLock sync.Mutex
- tables map[string]*schema.Table
+ tableMatchCache map[string]bool
+ includeTableRegex []*regexp.Regexp
+ excludeTableRegex []*regexp.Regexp
- quit chan struct{}
- closed sync2.AtomicBool
+ ctx context.Context
+ cancel context.CancelFunc
}
+// canal will retry fetching unknown table's meta after UnknownTableRetryPeriod
+var UnknownTableRetryPeriod = time.Second * time.Duration(10)
+var ErrExcludedTable = errors.New("excluded table meta")
+
func NewCanal(cfg *Config) (*Canal, error) {
c := new(Canal)
c.cfg = cfg
- c.closed.Set(false)
- c.quit = make(chan struct{})
- os.MkdirAll(cfg.DataDir, 0755)
+ c.ctx, c.cancel = context.WithCancel(context.Background())
c.dumpDoneCh = make(chan struct{})
- c.rsHandlers = make([]RowsEventHandler, 0, 4)
+ c.eventHandler = &DummyEventHandler{}
+
c.tables = make(map[string]*schema.Table)
+ if c.cfg.DiscardNoMetaRowEvent {
+ c.errorTablesGetTime = make(map[string]time.Time)
+ }
+ c.master = &masterInfo{}
var err error
- if c.master, err = loadMasterInfo(c.masterInfoPath()); err != nil {
- return nil, errors.Trace(err)
- } else if len(c.master.Addr) != 0 && c.master.Addr != c.cfg.Addr {
- log.Infof("MySQL addr %s in old master.info, but new %s, reset", c.master.Addr, c.cfg.Addr)
- // may use another MySQL, reset
- c.master = &masterInfo{}
- }
- c.master.Addr = c.cfg.Addr
-
- if err := c.prepareDumper(); err != nil {
+ if err = c.prepareDumper(); err != nil {
return nil, errors.Trace(err)
}
@@ -83,6 +83,33 @@ func NewCanal(cfg *Config) (*Canal, error) {
return nil, errors.Trace(err)
}
+ // init table filter
+ if n := len(c.cfg.IncludeTableRegex); n > 0 {
+ c.includeTableRegex = make([]*regexp.Regexp, n)
+ for i, val := range c.cfg.IncludeTableRegex {
+ reg, err := regexp.Compile(val)
+ if err != nil {
+ return nil, errors.Trace(err)
+ }
+ c.includeTableRegex[i] = reg
+ }
+ }
+
+ if n := len(c.cfg.ExcludeTableRegex); n > 0 {
+ c.excludeTableRegex = make([]*regexp.Regexp, n)
+ for i, val := range c.cfg.ExcludeTableRegex {
+ reg, err := regexp.Compile(val)
+ if err != nil {
+ return nil, errors.Trace(err)
+ }
+ c.excludeTableRegex[i] = reg
+ }
+ }
+
+ if c.includeTableRegex != nil || c.excludeTableRegex != nil {
+ c.tableMatchCache = make(map[string]bool)
+ }
+
return c, nil
}
@@ -114,6 +141,15 @@ func (c *Canal) prepareDumper() error {
c.dumper.AddTables(tableDB, tables...)
}
+ charset := c.cfg.Charset
+ c.dumper.SetCharset(charset)
+
+ c.dumper.SetWhere(c.cfg.Dump.Where)
+ c.dumper.SkipMasterData(c.cfg.Dump.SkipMasterData)
+ c.dumper.SetMaxAllowedPacket(c.cfg.Dump.MaxAllowedPacketMB)
+ // Use hex blob for mysqldump
+ c.dumper.SetHexBlob(true)
+
for _, ignoreTable := range c.cfg.Dump.IgnoreTables {
if seps := strings.Split(ignoreTable, ","); len(seps) == 2 {
c.dumper.AddIgnoreTables(seps[0], seps[1])
@@ -129,92 +165,208 @@ func (c *Canal) prepareDumper() error {
return nil
}
-func (c *Canal) Start() error {
- c.wg.Add(1)
- go c.run()
+// Run will first try to dump all data from MySQL master `mysqldump`,
+// then sync from the binlog position in the dump data.
+// It will run forever until meeting an error or Canal closed.
+func (c *Canal) Run() error {
+ return c.run()
+}
- return nil
+// RunFrom will sync from the binlog position directly, ignore mysqldump.
+func (c *Canal) RunFrom(pos mysql.Position) error {
+ c.master.Update(pos)
+
+ return c.Run()
+}
+
+func (c *Canal) StartFromGTID(set mysql.GTIDSet) error {
+ c.master.UpdateGTIDSet(set)
+
+ return c.Run()
+}
+
+// Dump all data from MySQL master `mysqldump`, ignore sync binlog.
+func (c *Canal) Dump() error {
+ if c.dumped {
+ return errors.New("the method Dump can't be called twice")
+ }
+ c.dumped = true
+ defer close(c.dumpDoneCh)
+ return c.dump()
}
func (c *Canal) run() error {
- defer c.wg.Done()
+ defer func() {
+ c.cancel()
+ }()
- if err := c.tryDump(); err != nil {
- log.Errorf("canal dump mysql err: %v", err)
- return errors.Trace(err)
+ c.master.UpdateTimestamp(uint32(time.Now().Unix()))
+
+ if !c.dumped {
+ c.dumped = true
+
+ err := c.tryDump()
+ close(c.dumpDoneCh)
+
+ if err != nil {
+ log.Errorf("canal dump mysql err: %v", err)
+ return errors.Trace(err)
+ }
}
- close(c.dumpDoneCh)
-
- if err := c.startSyncBinlog(); err != nil {
- if !c.isClosed() {
- log.Errorf("canal start sync binlog err: %v", err)
- }
+ if err := c.runSyncBinlog(); err != nil {
+ log.Errorf("canal start sync binlog err: %v", err)
return errors.Trace(err)
}
return nil
}
-func (c *Canal) isClosed() bool {
- return c.closed.Get()
-}
-
func (c *Canal) Close() {
- log.Infof("close canal")
+ log.Infof("closing canal")
c.m.Lock()
defer c.m.Unlock()
- if c.isClosed() {
- return
- }
-
- c.closed.Set(true)
-
- close(c.quit)
-
+ c.cancel()
c.connLock.Lock()
c.conn.Close()
c.conn = nil
c.connLock.Unlock()
+ c.syncer.Close()
- if c.syncer != nil {
- c.syncer.Close()
- c.syncer = nil
- }
-
- c.master.Close()
-
- c.wg.Wait()
+ c.eventHandler.OnPosSynced(c.master.Position(), true)
}
func (c *Canal) WaitDumpDone() <-chan struct{} {
return c.dumpDoneCh
}
+func (c *Canal) Ctx() context.Context {
+ return c.ctx
+}
+
+func (c *Canal) checkTableMatch(key string) bool {
+ // no filter, return true
+ if c.tableMatchCache == nil {
+ return true
+ }
+
+ c.tableLock.RLock()
+ rst, ok := c.tableMatchCache[key]
+ c.tableLock.RUnlock()
+ if ok {
+ // cache hit
+ return rst
+ }
+ matchFlag := false
+ // check include
+ if c.includeTableRegex != nil {
+ for _, reg := range c.includeTableRegex {
+ if reg.MatchString(key) {
+ matchFlag = true
+ break
+ }
+ }
+ }
+ // check exclude
+ if matchFlag && c.excludeTableRegex != nil {
+ for _, reg := range c.excludeTableRegex {
+ if reg.MatchString(key) {
+ matchFlag = false
+ break
+ }
+ }
+ }
+ c.tableLock.Lock()
+ c.tableMatchCache[key] = matchFlag
+ c.tableLock.Unlock()
+ return matchFlag
+}
+
func (c *Canal) GetTable(db string, table string) (*schema.Table, error) {
key := fmt.Sprintf("%s.%s", db, table)
- c.tableLock.Lock()
+ // if table is excluded, return error and skip parsing event or dump
+ if !c.checkTableMatch(key) {
+ return nil, ErrExcludedTable
+ }
+ c.tableLock.RLock()
t, ok := c.tables[key]
- c.tableLock.Unlock()
+ c.tableLock.RUnlock()
if ok {
return t, nil
}
+ if c.cfg.DiscardNoMetaRowEvent {
+ c.tableLock.RLock()
+ lastTime, ok := c.errorTablesGetTime[key]
+ c.tableLock.RUnlock()
+ if ok && time.Now().Sub(lastTime) < UnknownTableRetryPeriod {
+ return nil, schema.ErrMissingTableMeta
+ }
+ }
+
t, err := schema.NewTable(c, db, table)
if err != nil {
- return nil, errors.Trace(err)
+ // check table not exists
+ if ok, err1 := schema.IsTableExist(c, db, table); err1 == nil && !ok {
+ return nil, schema.ErrTableNotExist
+ }
+ // work around : RDS HAHeartBeat
+ // ref : https://github.com/alibaba/canal/blob/master/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java#L385
+ // issue : https://github.com/alibaba/canal/issues/222
+ // This is a common error in RDS that canal can't get HAHealthCheckSchema's meta, so we mock a table meta.
+ // If canal just skip and log error, as RDS HA heartbeat interval is very short, so too many HAHeartBeat errors will be logged.
+ if key == schema.HAHealthCheckSchema {
+ // mock ha_health_check meta
+ ta := &schema.Table{
+ Schema: db,
+ Name: table,
+ Columns: make([]schema.TableColumn, 0, 2),
+ Indexes: make([]*schema.Index, 0),
+ }
+ ta.AddColumn("id", "bigint(20)", "", "")
+ ta.AddColumn("type", "char(1)", "", "")
+ c.tableLock.Lock()
+ c.tables[key] = ta
+ c.tableLock.Unlock()
+ return ta, nil
+ }
+ // if DiscardNoMetaRowEvent is true, we just log this error
+ if c.cfg.DiscardNoMetaRowEvent {
+ c.tableLock.Lock()
+ c.errorTablesGetTime[key] = time.Now()
+ c.tableLock.Unlock()
+ // log error and return ErrMissingTableMeta
+ log.Errorf("canal get table meta err: %v", errors.Trace(err))
+ return nil, schema.ErrMissingTableMeta
+ }
+ return nil, err
}
c.tableLock.Lock()
c.tables[key] = t
+ if c.cfg.DiscardNoMetaRowEvent {
+ // if get table info success, delete this key from errorTablesGetTime
+ delete(c.errorTablesGetTime, key)
+ }
c.tableLock.Unlock()
return t, nil
}
+// ClearTableCache clear table cache
+func (c *Canal) ClearTableCache(db []byte, table []byte) {
+ key := fmt.Sprintf("%s.%s", db, table)
+ c.tableLock.Lock()
+ delete(c.tables, key)
+ if c.cfg.DiscardNoMetaRowEvent {
+ delete(c.errorTablesGetTime, key)
+ }
+ c.tableLock.Unlock()
+}
+
// Check MySQL binlog row image, must be in FULL, MINIMAL, NOBLOB
func (c *Canal) CheckBinlogRowImage(image string) error {
// need to check MySQL binlog row image? full, minimal or noblob?
@@ -246,23 +398,34 @@ func (c *Canal) checkBinlogRowFormat() error {
}
func (c *Canal) prepareSyncer() error {
- seps := strings.Split(c.cfg.Addr, ":")
- if len(seps) != 2 {
- return errors.Errorf("invalid mysql addr format %s, must host:port", c.cfg.Addr)
- }
-
- port, err := strconv.ParseUint(seps[1], 10, 16)
- if err != nil {
- return errors.Trace(err)
- }
-
cfg := replication.BinlogSyncerConfig{
- ServerID: c.cfg.ServerID,
- Flavor: c.cfg.Flavor,
- Host: seps[0],
- Port: uint16(port),
- User: c.cfg.User,
- Password: c.cfg.Password,
+ ServerID: c.cfg.ServerID,
+ Flavor: c.cfg.Flavor,
+ User: c.cfg.User,
+ Password: c.cfg.Password,
+ Charset: c.cfg.Charset,
+ HeartbeatPeriod: c.cfg.HeartbeatPeriod,
+ ReadTimeout: c.cfg.ReadTimeout,
+ UseDecimal: c.cfg.UseDecimal,
+ ParseTime: c.cfg.ParseTime,
+ SemiSyncEnabled: c.cfg.SemiSyncEnabled,
+ }
+
+ if strings.Contains(c.cfg.Addr, "/") {
+ cfg.Host = c.cfg.Addr
+ } else {
+ seps := strings.Split(c.cfg.Addr, ":")
+ if len(seps) != 2 {
+ return errors.Errorf("invalid mysql addr format %s, must host:port", c.cfg.Addr)
+ }
+
+ port, err := strconv.ParseUint(seps[1], 10, 16)
+ if err != nil {
+ return errors.Trace(err)
+ }
+
+ cfg.Host = seps[0]
+ cfg.Port = uint16(port)
}
c.syncer = replication.NewBinlogSyncer(cfg)
@@ -270,10 +433,6 @@ func (c *Canal) prepareSyncer() error {
return nil
}
-func (c *Canal) masterInfoPath() string {
- return path.Join(c.cfg.DataDir, "master.info")
-}
-
// Execute a SQL
func (c *Canal) Execute(cmd string, args ...interface{}) (rr *mysql.Result, err error) {
c.connLock.Lock()
@@ -303,5 +462,13 @@ func (c *Canal) Execute(cmd string, args ...interface{}) (rr *mysql.Result, err
}
func (c *Canal) SyncedPosition() mysql.Position {
- return c.master.Pos()
+ return c.master.Position()
+}
+
+func (c *Canal) SyncedTimestamp() uint32 {
+ return c.master.timestamp
+}
+
+func (c *Canal) SyncedGTIDSet() mysql.GTIDSet {
+ return c.master.GTIDSet()
}
diff --git a/vendor/github.com/siddontang/go-mysql/canal/canal_test.go b/vendor/github.com/siddontang/go-mysql/canal/canal_test.go
old mode 100644
new mode 100755
index b83c79e..bd16bd2
--- a/vendor/github.com/siddontang/go-mysql/canal/canal_test.go
+++ b/vendor/github.com/siddontang/go-mysql/canal/canal_test.go
@@ -1,13 +1,15 @@
package canal
import (
+ "bytes"
"flag"
"fmt"
- "os"
"testing"
+ "time"
- "github.com/ngaut/log"
+ "github.com/juju/errors"
. "github.com/pingcap/check"
+ "github.com/siddontang/go-log/log"
"github.com/siddontang/go-mysql/mysql"
)
@@ -27,19 +29,28 @@ func (s *canalTestSuite) SetUpSuite(c *C) {
cfg := NewDefaultConfig()
cfg.Addr = fmt.Sprintf("%s:3306", *testHost)
cfg.User = "root"
+ cfg.HeartbeatPeriod = 200 * time.Millisecond
+ cfg.ReadTimeout = 300 * time.Millisecond
cfg.Dump.ExecutionPath = "mysqldump"
cfg.Dump.TableDB = "test"
cfg.Dump.Tables = []string{"canal_test"}
+ cfg.Dump.Where = "id>0"
- os.RemoveAll(cfg.DataDir)
+ // include & exclude config
+ cfg.IncludeTableRegex = make([]string, 1)
+ cfg.IncludeTableRegex[0] = ".*\\.canal_test"
+ cfg.ExcludeTableRegex = make([]string, 2)
+ cfg.ExcludeTableRegex[0] = "mysql\\..*"
+ cfg.ExcludeTableRegex[1] = ".*\\..*_inner"
var err error
s.c, err = NewCanal(cfg)
c.Assert(err, IsNil)
-
+ s.execute(c, "DROP TABLE IF EXISTS test.canal_test")
sql := `
CREATE TABLE IF NOT EXISTS test.canal_test (
- id int AUTO_INCREMENT,
+ id int AUTO_INCREMENT,
+ content blob DEFAULT NULL,
name varchar(100),
PRIMARY KEY(id)
)ENGINE=innodb;
@@ -48,16 +59,22 @@ func (s *canalTestSuite) SetUpSuite(c *C) {
s.execute(c, sql)
s.execute(c, "DELETE FROM test.canal_test")
- s.execute(c, "INSERT INTO test.canal_test (name) VALUES (?), (?), (?)", "a", "b", "c")
+ s.execute(c, "INSERT INTO test.canal_test (content, name) VALUES (?, ?), (?, ?), (?, ?)", "1", "a", `\0\ndsfasdf`, "b", "", "c")
s.execute(c, "SET GLOBAL binlog_format = 'ROW'")
- s.c.RegRowsEventHandler(&testRowsEventHandler{})
- err = s.c.Start()
- c.Assert(err, IsNil)
+ s.c.SetEventHandler(&testEventHandler{c: c})
+ go func() {
+ err = s.c.Run()
+ c.Assert(err, IsNil)
+ }()
}
func (s *canalTestSuite) TearDownSuite(c *C) {
+ // To test the heartbeat and read timeout,so need to sleep 1 seconds without data transmission
+ c.Logf("Start testing the heartbeat and read timeout")
+ time.Sleep(time.Second)
+
if s.c != nil {
s.c.Close()
s.c = nil
@@ -70,16 +87,19 @@ func (s *canalTestSuite) execute(c *C, query string, args ...interface{}) *mysql
return r
}
-type testRowsEventHandler struct {
+type testEventHandler struct {
+ DummyEventHandler
+
+ c *C
}
-func (h *testRowsEventHandler) Do(e *RowsEvent) error {
- log.Infof("%s %v\n", e.Action, e.Rows)
+func (h *testEventHandler) OnRow(e *RowsEvent) error {
+ log.Infof("OnRow %s %v\n", e.Action, e.Rows)
return nil
}
-func (h *testRowsEventHandler) String() string {
- return "testRowsEventHandler"
+func (h *testEventHandler) String() string {
+ return "testEventHandler"
}
func (s *canalTestSuite) TestCanal(c *C) {
@@ -88,7 +108,126 @@ func (s *canalTestSuite) TestCanal(c *C) {
for i := 1; i < 10; i++ {
s.execute(c, "INSERT INTO test.canal_test (name) VALUES (?)", fmt.Sprintf("%d", i))
}
+ s.execute(c, "ALTER TABLE test.canal_test ADD `age` INT(5) NOT NULL AFTER `name`")
+ s.execute(c, "INSERT INTO test.canal_test (name,age) VALUES (?,?)", "d", "18")
- err := s.c.CatchMasterPos(100)
+ err := s.c.CatchMasterPos(10 * time.Second)
c.Assert(err, IsNil)
}
+
+func (s *canalTestSuite) TestCanalFilter(c *C) {
+ // included
+ sch, err := s.c.GetTable("test", "canal_test")
+ c.Assert(err, IsNil)
+ c.Assert(sch, NotNil)
+ _, err = s.c.GetTable("not_exist_db", "canal_test")
+ c.Assert(errors.Trace(err), Not(Equals), ErrExcludedTable)
+ // excluded
+ sch, err = s.c.GetTable("test", "canal_test_inner")
+ c.Assert(errors.Cause(err), Equals, ErrExcludedTable)
+ c.Assert(sch, IsNil)
+ sch, err = s.c.GetTable("mysql", "canal_test")
+ c.Assert(errors.Cause(err), Equals, ErrExcludedTable)
+ c.Assert(sch, IsNil)
+ sch, err = s.c.GetTable("not_exist_db", "not_canal_test")
+ c.Assert(errors.Cause(err), Equals, ErrExcludedTable)
+ c.Assert(sch, IsNil)
+}
+
+func TestCreateTableExp(t *testing.T) {
+ cases := []string{
+ "CREATE TABLE `mydb.mytable` (`id` int(10)) ENGINE=InnoDB",
+ "CREATE TABLE `mytable` (`id` int(10)) ENGINE=InnoDB",
+ "CREATE TABLE IF NOT EXISTS `mytable` (`id` int(10)) ENGINE=InnoDB",
+ "CREATE TABLE IF NOT EXISTS mytable (`id` int(10)) ENGINE=InnoDB",
+ }
+ table := []byte("mytable")
+ db := []byte("mydb")
+ for _, s := range cases {
+ m := expCreateTable.FindSubmatch([]byte(s))
+ mLen := len(m)
+ if m == nil || !bytes.Equal(m[mLen-1], table) || (len(m[mLen-2]) > 0 && !bytes.Equal(m[mLen-2], db)) {
+ t.Fatalf("TestCreateTableExp: case %s failed\n", s)
+ }
+ }
+}
+
+func TestAlterTableExp(t *testing.T) {
+ cases := []string{
+ "ALTER TABLE `mydb`.`mytable` ADD `field2` DATE NULL AFTER `field1`;",
+ "ALTER TABLE `mytable` ADD `field2` DATE NULL AFTER `field1`;",
+ "ALTER TABLE mydb.mytable ADD `field2` DATE NULL AFTER `field1`;",
+ "ALTER TABLE mytable ADD `field2` DATE NULL AFTER `field1`;",
+ "ALTER TABLE mydb.mytable ADD field2 DATE NULL AFTER `field1`;",
+ }
+
+ table := []byte("mytable")
+ db := []byte("mydb")
+ for _, s := range cases {
+ m := expAlterTable.FindSubmatch([]byte(s))
+ mLen := len(m)
+ if m == nil || !bytes.Equal(m[mLen-1], table) || (len(m[mLen-2]) > 0 && !bytes.Equal(m[mLen-2], db)) {
+ t.Fatalf("TestAlterTableExp: case %s failed\n", s)
+ }
+ }
+}
+
+func TestRenameTableExp(t *testing.T) {
+ cases := []string{
+ "rename table `mydb`.`mytable` to `mydb`.`mytable1`",
+ "rename table `mytable` to `mytable1`",
+ "rename table mydb.mytable to mydb.mytable1",
+ "rename table mytable to mytable1",
+
+ "rename table `mydb`.`mytable` to `mydb`.`mytable2`, `mydb`.`mytable3` to `mydb`.`mytable1`",
+ "rename table `mytable` to `mytable2`, `mytable3` to `mytable1`",
+ "rename table mydb.mytable to mydb.mytable2, mydb.mytable3 to mydb.mytable1",
+ "rename table mytable to mytable2, mytable3 to mytable1",
+ }
+ table := []byte("mytable")
+ db := []byte("mydb")
+ for _, s := range cases {
+ m := expRenameTable.FindSubmatch([]byte(s))
+ mLen := len(m)
+ if m == nil || !bytes.Equal(m[mLen-1], table) || (len(m[mLen-2]) > 0 && !bytes.Equal(m[mLen-2], db)) {
+ t.Fatalf("TestRenameTableExp: case %s failed\n", s)
+ }
+ }
+}
+
+func TestDropTableExp(t *testing.T) {
+ cases := []string{
+ "drop table test1",
+ "DROP TABLE test1",
+ "DROP TABLE test1",
+ "DROP table IF EXISTS test.test1",
+ "drop table `test1`",
+ "DROP TABLE `test1`",
+ "DROP table IF EXISTS `test`.`test1`",
+ "DROP TABLE `test1` /* generated by server */",
+ "DROP table if exists test1",
+ "DROP table if exists `test1`",
+ "DROP table if exists test.test1",
+ "DROP table if exists `test`.test1",
+ "DROP table if exists `test`.`test1`",
+ "DROP table if exists test.`test1`",
+ "DROP table if exists test.`test1`",
+ }
+
+ table := []byte("test1")
+ for _, s := range cases {
+ m := expDropTable.FindSubmatch([]byte(s))
+ mLen := len(m)
+ if m == nil {
+ t.Fatalf("TestDropTableExp: case %s failed\n", s)
+ return
+ }
+ if mLen < 4 {
+ t.Fatalf("TestDropTableExp: case %s failed\n", s)
+ return
+ }
+ if !bytes.Equal(m[mLen-1], table) {
+ t.Fatalf("TestDropTableExp: case %s failed\n", s)
+ }
+ }
+}
diff --git a/vendor/github.com/siddontang/go-mysql/canal/config.go b/vendor/github.com/siddontang/go-mysql/canal/config.go
index f9bb262..d10513c 100644
--- a/vendor/github.com/siddontang/go-mysql/canal/config.go
+++ b/vendor/github.com/siddontang/go-mysql/canal/config.go
@@ -7,6 +7,7 @@ import (
"github.com/BurntSushi/toml"
"github.com/juju/errors"
+ "github.com/siddontang/go-mysql/mysql"
)
type DumpConfig struct {
@@ -23,8 +24,18 @@ type DumpConfig struct {
// Ignore table format is db.table
IgnoreTables []string `toml:"ignore_tables"`
+ // Dump only selected records. Quotes are mandatory
+ Where string `toml:"where"`
+
// If true, discard error msg, else, output to stderr
DiscardErr bool `toml:"discard_err"`
+
+ // Set true to skip --master-data if we have no privilege to do
+ // 'FLUSH TABLES WITH READ LOCK'
+ SkipMasterData bool `toml:"skip_master_data"`
+
+ // Set to change the default max_allowed_packet size
+ MaxAllowedPacketMB int `toml:"max_allowed_packet_mb"`
}
type Config struct {
@@ -32,11 +43,30 @@ type Config struct {
User string `toml:"user"`
Password string `toml:"password"`
- ServerID uint32 `toml:"server_id"`
- Flavor string `toml:"flavor"`
- DataDir string `toml:"data_dir"`
+ Charset string `toml:"charset"`
+ ServerID uint32 `toml:"server_id"`
+ Flavor string `toml:"flavor"`
+ HeartbeatPeriod time.Duration `toml:"heartbeat_period"`
+ ReadTimeout time.Duration `toml:"read_timeout"`
+
+ // IncludeTableRegex or ExcludeTableRegex should contain database name
+ // Only a table which matches IncludeTableRegex and dismatches ExcludeTableRegex will be processed
+ // eg, IncludeTableRegex : [".*\\.canal"], ExcludeTableRegex : ["mysql\\..*"]
+ // this will include all database's 'canal' table, except database 'mysql'
+ // Default IncludeTableRegex and ExcludeTableRegex are empty, this will include all tables
+ IncludeTableRegex []string `toml:"include_table_regex"`
+ ExcludeTableRegex []string `toml:"exclude_table_regex"`
+
+ // discard row event without table meta
+ DiscardNoMetaRowEvent bool `toml:"discard_no_meta_row_event"`
Dump DumpConfig `toml:"dump"`
+
+ UseDecimal bool `toml:"use_decimal"`
+ ParseTime bool `toml:"parse_time"`
+
+ // SemiSyncEnabled enables semi-sync or not.
+ SemiSyncEnabled bool `toml:"semi_sync_enabled"`
}
func NewConfigWithFile(name string) (*Config, error) {
@@ -66,14 +96,14 @@ func NewDefaultConfig() *Config {
c.User = "root"
c.Password = ""
- rand.Seed(time.Now().Unix())
- c.ServerID = uint32(rand.Intn(1000)) + 1001
+ c.Charset = mysql.DEFAULT_CHARSET
+ c.ServerID = uint32(rand.New(rand.NewSource(time.Now().Unix())).Intn(1000)) + 1001
c.Flavor = "mysql"
- c.DataDir = "./var"
c.Dump.ExecutionPath = "mysqldump"
c.Dump.DiscardErr = true
+ c.Dump.SkipMasterData = false
return c
}
diff --git a/vendor/github.com/siddontang/go-mysql/canal/dump.go b/vendor/github.com/siddontang/go-mysql/canal/dump.go
index 4adec2a..8dcac2b 100644
--- a/vendor/github.com/siddontang/go-mysql/canal/dump.go
+++ b/vendor/github.com/siddontang/go-mysql/canal/dump.go
@@ -1,12 +1,16 @@
package canal
import (
+ "encoding/hex"
+ "fmt"
"strconv"
+ "strings"
"time"
"github.com/juju/errors"
- "github.com/ngaut/log"
- "github.com/siddontang/go-mysql/dump"
+ "github.com/shopspring/decimal"
+ "github.com/siddontang/go-log/log"
+ "github.com/siddontang/go-mysql/mysql"
"github.com/siddontang/go-mysql/schema"
)
@@ -14,6 +18,7 @@ type dumpParseHandler struct {
c *Canal
name string
pos uint64
+ gset mysql.GTIDSet
}
func (h *dumpParseHandler) BinLog(name string, pos uint64) error {
@@ -23,12 +28,18 @@ func (h *dumpParseHandler) BinLog(name string, pos uint64) error {
}
func (h *dumpParseHandler) Data(db string, table string, values []string) error {
- if h.c.isClosed() {
- return errCanalClosed
+ if err := h.c.ctx.Err(); err != nil {
+ return err
}
tableInfo, err := h.c.GetTable(db, table)
if err != nil {
+ e := errors.Cause(err)
+ if e == ErrExcludedTable ||
+ e == schema.ErrTableNotExist ||
+ e == schema.ErrMissingTableMeta {
+ return nil
+ }
log.Errorf("get %s.%s information err: %v", db, table, err)
return errors.Trace(err)
}
@@ -38,32 +49,51 @@ func (h *dumpParseHandler) Data(db string, table string, values []string) error
for i, v := range values {
if v == "NULL" {
vs[i] = nil
+ } else if v == "_binary ''" {
+ vs[i] = []byte{}
} else if v[0] != '\'' {
if tableInfo.Columns[i].Type == schema.TYPE_NUMBER {
n, err := strconv.ParseInt(v, 10, 64)
if err != nil {
- log.Errorf("parse row %v at %d error %v, skip", values, i, err)
- return dump.ErrSkip
+ return fmt.Errorf("parse row %v at %d error %v, int expected", values, i, err)
}
vs[i] = n
} else if tableInfo.Columns[i].Type == schema.TYPE_FLOAT {
f, err := strconv.ParseFloat(v, 64)
if err != nil {
- log.Errorf("parse row %v at %d error %v, skip", values, i, err)
- return dump.ErrSkip
+ return fmt.Errorf("parse row %v at %d error %v, float expected", values, i, err)
}
vs[i] = f
+ } else if tableInfo.Columns[i].Type == schema.TYPE_DECIMAL {
+ if h.c.cfg.UseDecimal {
+ d, err := decimal.NewFromString(v)
+ if err != nil {
+ return fmt.Errorf("parse row %v at %d error %v, decimal expected", values, i, err)
+ }
+ vs[i] = d
+ } else {
+ f, err := strconv.ParseFloat(v, 64)
+ if err != nil {
+ return fmt.Errorf("parse row %v at %d error %v, float expected", values, i, err)
+ }
+ vs[i] = f
+ }
+ } else if strings.HasPrefix(v, "0x") {
+ buf, err := hex.DecodeString(v[2:])
+ if err != nil {
+ return fmt.Errorf("parse row %v at %d error %v, hex literal expected", values, i, err)
+ }
+ vs[i] = string(buf)
} else {
- log.Errorf("parse row %v error, invalid type at %d, skip", values, i)
- return dump.ErrSkip
+ return fmt.Errorf("parse row %v error, invalid type at %d", values, i)
}
} else {
vs[i] = v[1 : len(v)-1]
}
}
- events := newRowsEvent(tableInfo, InsertAction, [][]interface{}{vs})
- return h.c.travelRowsEventHandler(events)
+ events := newRowsEvent(tableInfo, InsertAction, [][]interface{}{vs}, nil)
+ return h.c.eventHandler.OnRow(events)
}
func (c *Canal) AddDumpDatabases(dbs ...string) {
@@ -90,10 +120,64 @@ func (c *Canal) AddDumpIgnoreTables(db string, tables ...string) {
c.dumper.AddIgnoreTables(db, tables...)
}
+func (c *Canal) dump() error {
+ if c.dumper == nil {
+ return errors.New("mysqldump does not exist")
+ }
+
+ c.master.UpdateTimestamp(uint32(time.Now().Unix()))
+
+ h := &dumpParseHandler{c: c}
+ // If users call StartFromGTID with empty position to start dumping with gtid,
+ // we record the current gtid position before dump starts.
+ //
+ // See tryDump() to see when dump is skipped.
+ if c.master.GTIDSet() != nil {
+ gset, err := c.GetMasterGTIDSet()
+ if err != nil {
+ return errors.Trace(err)
+ }
+ h.gset = gset
+ }
+
+ if c.cfg.Dump.SkipMasterData {
+ pos, err := c.GetMasterPos()
+ if err != nil {
+ return errors.Trace(err)
+ }
+ log.Infof("skip master data, get current binlog position %v", pos)
+ h.name = pos.Name
+ h.pos = uint64(pos.Pos)
+ }
+
+ start := time.Now()
+ log.Info("try dump MySQL and parse")
+ if err := c.dumper.DumpAndParse(h); err != nil {
+ return errors.Trace(err)
+ }
+
+ pos := mysql.Position{Name: h.name, Pos: uint32(h.pos)}
+ c.master.Update(pos)
+ if err := c.eventHandler.OnPosSynced(pos, true); err != nil {
+ return errors.Trace(err)
+ }
+ var startPos fmt.Stringer = pos
+ if h.gset != nil {
+ c.master.UpdateGTIDSet(h.gset)
+ startPos = h.gset
+ }
+ log.Infof("dump MySQL and parse OK, use %0.2f seconds, start binlog replication at %s",
+ time.Now().Sub(start).Seconds(), startPos)
+ return nil
+}
+
func (c *Canal) tryDump() error {
- if len(c.master.Name) > 0 && c.master.Position > 0 {
+ pos := c.master.Position()
+ gset := c.master.GTIDSet()
+ if (len(pos.Name) > 0 && pos.Pos > 0) ||
+ (gset != nil && gset.String() != "") {
// we will sync with binlog name and position
- log.Infof("skip dump, use last binlog replication pos (%s, %d)", c.master.Name, c.master.Position)
+ log.Infof("skip dump, use last binlog replication pos %s or GTID set %s", pos, gset)
return nil
}
@@ -102,19 +186,5 @@ func (c *Canal) tryDump() error {
return nil
}
- h := &dumpParseHandler{c: c}
-
- start := time.Now()
- log.Info("try dump MySQL and parse")
- if err := c.dumper.DumpAndParse(h); err != nil {
- return errors.Trace(err)
- }
-
- log.Infof("dump MySQL and parse OK, use %0.2f seconds, start binlog replication at (%s, %d)",
- time.Now().Sub(start).Seconds(), h.name, h.pos)
-
- c.master.Update(h.name, uint32(h.pos))
- c.master.Save(true)
-
- return nil
+ return c.dump()
}
diff --git a/vendor/github.com/siddontang/go-mysql/canal/handler.go b/vendor/github.com/siddontang/go-mysql/canal/handler.go
index e361181..4e47cb9 100644
--- a/vendor/github.com/siddontang/go-mysql/canal/handler.go
+++ b/vendor/github.com/siddontang/go-mysql/canal/handler.go
@@ -1,41 +1,41 @@
package canal
import (
- "github.com/juju/errors"
- "github.com/ngaut/log"
"github.com/siddontang/go-mysql/mysql"
+ "github.com/siddontang/go-mysql/replication"
)
-var (
- ErrHandleInterrupted = errors.New("do handler error, interrupted")
-)
-
-type RowsEventHandler interface {
- // Handle RowsEvent, if return ErrHandleInterrupted, canal will
- // stop the sync
- Do(e *RowsEvent) error
+type EventHandler interface {
+ OnRotate(roateEvent *replication.RotateEvent) error
+ // OnTableChanged is called when the table is created, altered, renamed or dropped.
+ // You need to clear the associated data like cache with the table.
+ // It will be called before OnDDL.
+ OnTableChanged(schema string, table string) error
+ OnDDL(nextPos mysql.Position, queryEvent *replication.QueryEvent) error
+ OnRow(e *RowsEvent) error
+ OnXID(nextPos mysql.Position) error
+ OnGTID(gtid mysql.GTIDSet) error
+ // OnPosSynced Use your own way to sync position. When force is true, sync position immediately.
+ OnPosSynced(pos mysql.Position, force bool) error
String() string
}
-func (c *Canal) RegRowsEventHandler(h RowsEventHandler) {
- c.rsLock.Lock()
- c.rsHandlers = append(c.rsHandlers, h)
- c.rsLock.Unlock()
+type DummyEventHandler struct {
}
-func (c *Canal) travelRowsEventHandler(e *RowsEvent) error {
- c.rsLock.Lock()
- defer c.rsLock.Unlock()
-
- var err error
- for _, h := range c.rsHandlers {
- if err = h.Do(e); err != nil && !mysql.ErrorEqual(err, ErrHandleInterrupted) {
- log.Errorf("handle %v err: %v", h, err)
- } else if mysql.ErrorEqual(err, ErrHandleInterrupted) {
- log.Errorf("handle %v err, interrupted", h)
- return ErrHandleInterrupted
- }
-
- }
+func (h *DummyEventHandler) OnRotate(*replication.RotateEvent) error { return nil }
+func (h *DummyEventHandler) OnTableChanged(schema string, table string) error { return nil }
+func (h *DummyEventHandler) OnDDL(nextPos mysql.Position, queryEvent *replication.QueryEvent) error {
return nil
}
+func (h *DummyEventHandler) OnRow(*RowsEvent) error { return nil }
+func (h *DummyEventHandler) OnXID(mysql.Position) error { return nil }
+func (h *DummyEventHandler) OnGTID(mysql.GTIDSet) error { return nil }
+func (h *DummyEventHandler) OnPosSynced(mysql.Position, bool) error { return nil }
+func (h *DummyEventHandler) String() string { return "DummyEventHandler" }
+
+// `SetEventHandler` registers the sync handler, you must register your
+// own handler before starting Canal.
+func (c *Canal) SetEventHandler(h EventHandler) {
+ c.eventHandler = h
+}
diff --git a/vendor/github.com/siddontang/go-mysql/canal/master.go b/vendor/github.com/siddontang/go-mysql/canal/master.go
index 6897d05..10a230b 100644
--- a/vendor/github.com/siddontang/go-mysql/canal/master.go
+++ b/vendor/github.com/siddontang/go-mysql/canal/master.go
@@ -1,89 +1,66 @@
package canal
import (
- "bytes"
- "os"
"sync"
- "time"
- "github.com/BurntSushi/toml"
- "github.com/juju/errors"
- "github.com/ngaut/log"
+ "github.com/siddontang/go-log/log"
"github.com/siddontang/go-mysql/mysql"
- "github.com/siddontang/go/ioutil2"
)
type masterInfo struct {
- Addr string `toml:"addr"`
- Name string `toml:"bin_name"`
- Position uint32 `toml:"bin_pos"`
+ sync.RWMutex
- name string
+ pos mysql.Position
- l sync.Mutex
+ gset mysql.GTIDSet
- lastSaveTime time.Time
+ timestamp uint32
}
-func loadMasterInfo(name string) (*masterInfo, error) {
- var m masterInfo
+func (m *masterInfo) Update(pos mysql.Position) {
+ log.Debugf("update master position %s", pos)
- m.name = name
-
- f, err := os.Open(name)
- if err != nil && !os.IsNotExist(errors.Cause(err)) {
- return nil, errors.Trace(err)
- } else if os.IsNotExist(errors.Cause(err)) {
- return &m, nil
- }
- defer f.Close()
-
- _, err = toml.DecodeReader(f, &m)
-
- return &m, err
+ m.Lock()
+ m.pos = pos
+ m.Unlock()
}
-func (m *masterInfo) Save(force bool) error {
- m.l.Lock()
- defer m.l.Unlock()
+func (m *masterInfo) UpdateTimestamp(ts uint32) {
+ log.Debugf("update master timestamp %s", ts)
- n := time.Now()
- if !force && n.Sub(m.lastSaveTime) < time.Second {
+ m.Lock()
+ m.timestamp = ts
+ m.Unlock()
+}
+
+func (m *masterInfo) UpdateGTIDSet(gset mysql.GTIDSet) {
+ log.Debugf("update master gtid set %s", gset)
+
+ m.Lock()
+ m.gset = gset
+ m.Unlock()
+}
+
+func (m *masterInfo) Position() mysql.Position {
+ m.RLock()
+ defer m.RUnlock()
+
+ return m.pos
+}
+
+func (m *masterInfo) Timestamp() uint32 {
+ m.RLock()
+ defer m.RUnlock()
+
+ return m.timestamp
+}
+
+func (m *masterInfo) GTIDSet() mysql.GTIDSet {
+ m.RLock()
+ defer m.RUnlock()
+
+ if m.gset == nil {
return nil
}
-
- var buf bytes.Buffer
- e := toml.NewEncoder(&buf)
-
- e.Encode(m)
-
- var err error
- if err = ioutil2.WriteFileAtomic(m.name, buf.Bytes(), 0644); err != nil {
- log.Errorf("canal save master info to file %s err %v", m.name, err)
- }
-
- m.lastSaveTime = n
-
- return errors.Trace(err)
-}
-
-func (m *masterInfo) Update(name string, pos uint32) {
- m.l.Lock()
- m.Name = name
- m.Position = pos
- m.l.Unlock()
-}
-
-func (m *masterInfo) Pos() mysql.Position {
- var pos mysql.Position
- m.l.Lock()
- pos.Name = m.Name
- pos.Pos = m.Position
- m.l.Unlock()
-
- return pos
-}
-
-func (m *masterInfo) Close() {
- m.Save(true)
+ return m.gset.Clone()
}
diff --git a/vendor/github.com/siddontang/go-mysql/canal/rows.go b/vendor/github.com/siddontang/go-mysql/canal/rows.go
index 5c5e467..e246ee5 100644
--- a/vendor/github.com/siddontang/go-mysql/canal/rows.go
+++ b/vendor/github.com/siddontang/go-mysql/canal/rows.go
@@ -3,16 +3,18 @@ package canal
import (
"fmt"
- "github.com/juju/errors"
+ "github.com/siddontang/go-mysql/replication"
"github.com/siddontang/go-mysql/schema"
)
+// The action name for sync.
const (
UpdateAction = "update"
InsertAction = "insert"
DeleteAction = "delete"
)
+// RowsEvent is the event for row replication.
type RowsEvent struct {
Table *schema.Table
Action string
@@ -22,35 +24,49 @@ type RowsEvent struct {
// Two rows for one event, format is [before update row, after update row]
// for update v0, only one row for a event, and we don't support this version.
Rows [][]interface{}
+ // Header can be used to inspect the event
+ Header *replication.EventHeader
}
-func newRowsEvent(table *schema.Table, action string, rows [][]interface{}) *RowsEvent {
+func newRowsEvent(table *schema.Table, action string, rows [][]interface{}, header *replication.EventHeader) *RowsEvent {
e := new(RowsEvent)
e.Table = table
e.Action = action
e.Rows = rows
+ e.Header = header
+
+ e.handleUnsigned()
return e
}
-// Get primary keys in one row for a table, a table may use multi fields as the PK
-func GetPKValues(table *schema.Table, row []interface{}) ([]interface{}, error) {
- indexes := table.PKColumns
- if len(indexes) == 0 {
- return nil, errors.Errorf("table %s has no PK", table)
- } else if len(table.Columns) != len(row) {
- return nil, errors.Errorf("table %s has %d columns, but row data %v len is %d", table,
- len(table.Columns), row, len(row))
+func (r *RowsEvent) handleUnsigned() {
+ // Handle Unsigned Columns here, for binlog replication, we can't know the integer is unsigned or not,
+ // so we use int type but this may cause overflow outside sometimes, so we must convert to the really .
+ // unsigned type
+ if len(r.Table.UnsignedColumns) == 0 {
+ return
}
- values := make([]interface{}, 0, len(indexes))
-
- for _, index := range indexes {
- values = append(values, row[index])
+ for i := 0; i < len(r.Rows); i++ {
+ for _, index := range r.Table.UnsignedColumns {
+ switch t := r.Rows[i][index].(type) {
+ case int8:
+ r.Rows[i][index] = uint8(t)
+ case int16:
+ r.Rows[i][index] = uint16(t)
+ case int32:
+ r.Rows[i][index] = uint32(t)
+ case int64:
+ r.Rows[i][index] = uint64(t)
+ case int:
+ r.Rows[i][index] = uint(t)
+ default:
+ // nothing to do
+ }
+ }
}
-
- return values, nil
}
// String implements fmt.Stringer interface.
diff --git a/vendor/github.com/siddontang/go-mysql/canal/sync.go b/vendor/github.com/siddontang/go-mysql/canal/sync.go
index e76eea4..4146a2d 100644
--- a/vendor/github.com/siddontang/go-mysql/canal/sync.go
+++ b/vendor/github.com/siddontang/go-mysql/canal/sync.go
@@ -1,49 +1,68 @@
package canal
import (
+ "fmt"
+ "regexp"
"time"
- "golang.org/x/net/context"
-
"github.com/juju/errors"
- "github.com/ngaut/log"
+ "github.com/satori/go.uuid"
+ "github.com/siddontang/go-log/log"
"github.com/siddontang/go-mysql/mysql"
"github.com/siddontang/go-mysql/replication"
+ "github.com/siddontang/go-mysql/schema"
)
-func (c *Canal) startSyncBinlog() error {
- pos := mysql.Position{c.master.Name, c.master.Position}
+var (
+ expCreateTable = regexp.MustCompile("(?i)^CREATE\\sTABLE(\\sIF\\sNOT\\sEXISTS)?\\s`{0,1}(.*?)`{0,1}\\.{0,1}`{0,1}([^`\\.]+?)`{0,1}\\s.*")
+ expAlterTable = regexp.MustCompile("(?i)^ALTER\\sTABLE\\s.*?`{0,1}(.*?)`{0,1}\\.{0,1}`{0,1}([^`\\.]+?)`{0,1}\\s.*")
+ expRenameTable = regexp.MustCompile("(?i)^RENAME\\sTABLE\\s.*?`{0,1}(.*?)`{0,1}\\.{0,1}`{0,1}([^`\\.]+?)`{0,1}\\s{1,}TO\\s.*?")
+ expDropTable = regexp.MustCompile("(?i)^DROP\\sTABLE(\\sIF\\sEXISTS){0,1}\\s`{0,1}(.*?)`{0,1}\\.{0,1}`{0,1}([^`\\.]+?)`{0,1}(?:$|\\s)")
+ expTruncateTable = regexp.MustCompile("(?i)^TRUNCATE\\s+(?:TABLE\\s+)?(?:`?([^`\\s]+)`?\\.`?)?([^`\\s]+)`?")
+)
- log.Infof("start sync binlog at %v", pos)
+func (c *Canal) startSyncer() (*replication.BinlogStreamer, error) {
+ gset := c.master.GTIDSet()
+ if gset == nil {
+ pos := c.master.Position()
+ s, err := c.syncer.StartSync(pos)
+ if err != nil {
+ return nil, errors.Errorf("start sync replication at binlog %v error %v", pos, err)
+ }
+ log.Infof("start sync binlog at binlog file %v", pos)
+ return s, nil
+ } else {
+ s, err := c.syncer.StartSyncGTID(gset)
+ if err != nil {
+ return nil, errors.Errorf("start sync replication at GTID set %v error %v", gset, err)
+ }
+ log.Infof("start sync binlog at GTID set %v", gset)
+ return s, nil
+ }
+}
- s, err := c.syncer.StartSync(pos)
+func (c *Canal) runSyncBinlog() error {
+ s, err := c.startSyncer()
if err != nil {
- return errors.Errorf("start sync replication at %v error %v", pos, err)
+ return err
}
- timeout := time.Second
- forceSavePos := false
+ savePos := false
+ force := false
for {
- ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
- ev, err := s.GetEvent(ctx)
- cancel()
-
- if err == context.DeadlineExceeded {
- timeout = 2 * timeout
- continue
- }
+ ev, err := s.GetEvent(c.ctx)
if err != nil {
return errors.Trace(err)
}
+ savePos = false
+ force = false
+ pos := c.master.Position()
- timeout = time.Second
-
+ curPos := pos.Pos
//next binlog pos
pos.Pos = ev.Header.LogPos
- forceSavePos = false
-
// We only save position with RotateEvent and XIDEvent.
// For RowsEvent, we can't save the position until meeting XIDEvent
// which tells the whole transaction is over.
@@ -52,24 +71,105 @@ func (c *Canal) startSyncBinlog() error {
case *replication.RotateEvent:
pos.Name = string(e.NextLogName)
pos.Pos = uint32(e.Position)
- // r.ev <- pos
- forceSavePos = true
- log.Infof("rotate binlog to %v", pos)
+ log.Infof("rotate binlog to %s", pos)
+ savePos = true
+ force = true
+ if err = c.eventHandler.OnRotate(e); err != nil {
+ return errors.Trace(err)
+ }
case *replication.RowsEvent:
// we only focus row based event
- if err = c.handleRowsEvent(ev); err != nil {
- log.Errorf("handle rows event error %v", err)
- return errors.Trace(err)
+ err = c.handleRowsEvent(ev)
+ if err != nil {
+ e := errors.Cause(err)
+ // if error is not ErrExcludedTable or ErrTableNotExist or ErrMissingTableMeta, stop canal
+ if e != ErrExcludedTable &&
+ e != schema.ErrTableNotExist &&
+ e != schema.ErrMissingTableMeta {
+ log.Errorf("handle rows event at (%s, %d) error %v", pos.Name, curPos, err)
+ return errors.Trace(err)
+ }
}
continue
case *replication.XIDEvent:
+ if e.GSet != nil {
+ c.master.UpdateGTIDSet(e.GSet)
+ }
+ savePos = true
// try to save the position later
+ if err := c.eventHandler.OnXID(pos); err != nil {
+ return errors.Trace(err)
+ }
+ case *replication.MariadbGTIDEvent:
+ // try to save the GTID later
+ gtid, err := mysql.ParseMariadbGTIDSet(e.GTID.String())
+ if err != nil {
+ return errors.Trace(err)
+ }
+ if err := c.eventHandler.OnGTID(gtid); err != nil {
+ return errors.Trace(err)
+ }
+ case *replication.GTIDEvent:
+ u, _ := uuid.FromBytes(e.SID)
+ gtid, err := mysql.ParseMysqlGTIDSet(fmt.Sprintf("%s:%d", u.String(), e.GNO))
+ if err != nil {
+ return errors.Trace(err)
+ }
+ if err := c.eventHandler.OnGTID(gtid); err != nil {
+ return errors.Trace(err)
+ }
+ case *replication.QueryEvent:
+ if e.GSet != nil {
+ c.master.UpdateGTIDSet(e.GSet)
+ }
+ var (
+ mb [][]byte
+ db []byte
+ table []byte
+ )
+ regexps := []regexp.Regexp{*expCreateTable, *expAlterTable, *expRenameTable, *expDropTable, *expTruncateTable}
+ for _, reg := range regexps {
+ mb = reg.FindSubmatch(e.Query)
+ if len(mb) != 0 {
+ break
+ }
+ }
+ mbLen := len(mb)
+ if mbLen == 0 {
+ continue
+ }
+
+ // the first last is table name, the second last is database name(if exists)
+ if len(mb[mbLen-2]) == 0 {
+ db = e.Schema
+ } else {
+ db = mb[mbLen-2]
+ }
+ table = mb[mbLen-1]
+
+ savePos = true
+ force = true
+ c.ClearTableCache(db, table)
+ log.Infof("table structure changed, clear table cache: %s.%s\n", db, table)
+ if err = c.eventHandler.OnTableChanged(string(db), string(table)); err != nil && errors.Cause(err) != schema.ErrTableNotExist {
+ return errors.Trace(err)
+ }
+
+ // Now we only handle Table Changed DDL, maybe we will support more later.
+ if err = c.eventHandler.OnDDL(pos, e); err != nil {
+ return errors.Trace(err)
+ }
default:
continue
}
- c.master.Update(pos.Name, pos.Pos)
- c.master.Save(forceSavePos)
+ if savePos {
+ c.master.Update(pos)
+ c.master.UpdateTimestamp(ev.Header.Timestamp)
+ if err := c.eventHandler.OnPosSynced(pos, force); err != nil {
+ return errors.Trace(err)
+ }
+ }
}
return nil
@@ -84,7 +184,7 @@ func (c *Canal) handleRowsEvent(e *replication.BinlogEvent) error {
t, err := c.GetTable(schema, table)
if err != nil {
- return errors.Trace(err)
+ return err
}
var action string
switch e.Header.EventType {
@@ -97,25 +197,31 @@ func (c *Canal) handleRowsEvent(e *replication.BinlogEvent) error {
default:
return errors.Errorf("%s not supported now", e.Header.EventType)
}
- events := newRowsEvent(t, action, ev.Rows)
- return c.travelRowsEventHandler(events)
+ events := newRowsEvent(t, action, ev.Rows, e.Header)
+ return c.eventHandler.OnRow(events)
}
-func (c *Canal) WaitUntilPos(pos mysql.Position, timeout int) error {
- if timeout <= 0 {
- timeout = 60
- }
+func (c *Canal) FlushBinlog() error {
+ _, err := c.Execute("FLUSH BINARY LOGS")
+ return errors.Trace(err)
+}
- timer := time.NewTimer(time.Duration(timeout) * time.Second)
+func (c *Canal) WaitUntilPos(pos mysql.Position, timeout time.Duration) error {
+ timer := time.NewTimer(timeout)
for {
select {
case <-timer.C:
- return errors.Errorf("wait position %v err", pos)
+ return errors.Errorf("wait position %v too long > %s", pos, timeout)
default:
- curpos := c.master.Pos()
- if curpos.Compare(pos) >= 0 {
+ err := c.FlushBinlog()
+ if err != nil {
+ return errors.Trace(err)
+ }
+ curPos := c.master.Position()
+ if curPos.Compare(pos) >= 0 {
return nil
} else {
+ log.Debugf("master pos is %v, wait catching %v", curPos, pos)
time.Sleep(100 * time.Millisecond)
}
}
@@ -124,14 +230,46 @@ func (c *Canal) WaitUntilPos(pos mysql.Position, timeout int) error {
return nil
}
-func (c *Canal) CatchMasterPos(timeout int) error {
+func (c *Canal) GetMasterPos() (mysql.Position, error) {
rr, err := c.Execute("SHOW MASTER STATUS")
if err != nil {
- return errors.Trace(err)
+ return mysql.Position{}, errors.Trace(err)
}
name, _ := rr.GetString(0, 0)
pos, _ := rr.GetInt(0, 1)
- return c.WaitUntilPos(mysql.Position{name, uint32(pos)}, timeout)
+ return mysql.Position{Name: name, Pos: uint32(pos)}, nil
+}
+
+func (c *Canal) GetMasterGTIDSet() (mysql.GTIDSet, error) {
+ query := ""
+ switch c.cfg.Flavor {
+ case mysql.MariaDBFlavor:
+ query = "SELECT @@GLOBAL.gtid_current_pos"
+ default:
+ query = "SELECT @@GLOBAL.GTID_EXECUTED"
+ }
+ rr, err := c.Execute(query)
+ if err != nil {
+ return nil, errors.Trace(err)
+ }
+ gx, err := rr.GetString(0, 0)
+ if err != nil {
+ return nil, errors.Trace(err)
+ }
+ gset, err := mysql.ParseGTIDSet(c.cfg.Flavor, gx)
+ if err != nil {
+ return nil, errors.Trace(err)
+ }
+ return gset, nil
+}
+
+func (c *Canal) CatchMasterPos(timeout time.Duration) error {
+ pos, err := c.GetMasterPos()
+ if err != nil {
+ return errors.Trace(err)
+ }
+
+ return c.WaitUntilPos(pos, timeout)
}
diff --git a/vendor/github.com/siddontang/go-mysql/clear_vendor.sh b/vendor/github.com/siddontang/go-mysql/clear_vendor.sh
new file mode 100755
index 0000000..81ba6b1
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/clear_vendor.sh
@@ -0,0 +1,6 @@
+find vendor \( -type f -or -type l \) -not -name "*.go" -not -name "LICENSE" -not -name "*.s" -not -name "PATENTS" -not -name "*.h" -not -name "*.c" | xargs -I {} rm {}
+# delete all test files
+find vendor -type f -name "*_generated.go" | xargs -I {} rm {}
+find vendor -type f -name "*_test.go" | xargs -I {} rm {}
+find vendor -type d -name "_vendor" | xargs -I {} rm -rf {}
+find vendor -type d -empty | xargs -I {} rm -rf {}
\ No newline at end of file
diff --git a/vendor/github.com/siddontang/go-mysql/client/auth.go b/vendor/github.com/siddontang/go-mysql/client/auth.go
index 85b688c..5ba9c9f 100644
--- a/vendor/github.com/siddontang/go-mysql/client/auth.go
+++ b/vendor/github.com/siddontang/go-mysql/client/auth.go
@@ -4,12 +4,29 @@ import (
"bytes"
"crypto/tls"
"encoding/binary"
+ "fmt"
"github.com/juju/errors"
. "github.com/siddontang/go-mysql/mysql"
"github.com/siddontang/go-mysql/packet"
)
+const defaultAuthPluginName = AUTH_NATIVE_PASSWORD
+
+// defines the supported auth plugins
+var supportedAuthPlugins = []string{AUTH_NATIVE_PASSWORD, AUTH_SHA256_PASSWORD, AUTH_CACHING_SHA2_PASSWORD}
+
+// helper function to determine what auth methods are allowed by this client
+func authPluginAllowed(pluginName string) bool {
+ for _, p := range supportedAuthPlugins {
+ if pluginName == p {
+ return true
+ }
+ }
+ return false
+}
+
+// See: http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake
func (c *Conn) readInitialHandshake() error {
data, err := c.ReadPacket()
if err != nil {
@@ -24,39 +41,44 @@ func (c *Conn) readInitialHandshake() error {
return errors.Errorf("invalid protocol version %d, must >= 10", data[0])
}
- //skip mysql version
- //mysql version end with 0x00
+ // skip mysql version
+ // mysql version end with 0x00
pos := 1 + bytes.IndexByte(data[1:], 0x00) + 1
- //connection id length is 4
+ // connection id length is 4
c.connectionID = uint32(binary.LittleEndian.Uint32(data[pos : pos+4]))
pos += 4
c.salt = []byte{}
c.salt = append(c.salt, data[pos:pos+8]...)
- //skip filter
+ // skip filter
pos += 8 + 1
- //capability lower 2 bytes
+ // capability lower 2 bytes
c.capability = uint32(binary.LittleEndian.Uint16(data[pos : pos+2]))
-
+ // check protocol
+ if c.capability&CLIENT_PROTOCOL_41 == 0 {
+ return errors.New("the MySQL server can not support protocol 41 and above required by the client")
+ }
+ if c.capability&CLIENT_SSL == 0 && c.tlsConfig != nil {
+ return errors.New("the MySQL Server does not support TLS required by the client")
+ }
pos += 2
if len(data) > pos {
- //skip server charset
+ // skip server charset
//c.charset = data[pos]
pos += 1
c.status = binary.LittleEndian.Uint16(data[pos : pos+2])
pos += 2
-
+ // capability flags (upper 2 bytes)
c.capability = uint32(binary.LittleEndian.Uint16(data[pos:pos+2]))<<16 | c.capability
-
pos += 2
- //skip auth data len or [00]
- //skip reserved (all [00])
+ // skip auth data len or [00]
+ // skip reserved (all [00])
pos += 10 + 1
// The documentation is ambiguous about the length.
@@ -64,78 +86,131 @@ func (c *Conn) readInitialHandshake() error {
// mysql-proxy also use 12
// which is not documented but seems to work.
c.salt = append(c.salt, data[pos:pos+12]...)
+ pos += 13
+ // auth plugin
+ if end := bytes.IndexByte(data[pos:], 0x00); end != -1 {
+ c.authPluginName = string(data[pos : pos+end])
+ } else {
+ c.authPluginName = string(data[pos:])
+ }
+ }
+
+ // if server gives no default auth plugin name, use a client default
+ if c.authPluginName == "" {
+ c.authPluginName = defaultAuthPluginName
}
return nil
}
+// generate auth response data according to auth plugin
+//
+// NOTE: the returned boolean value indicates whether to add a \NUL to the end of data.
+// it is quite tricky because MySQl server expects different formats of responses in different auth situations.
+// here the \NUL needs to be added when sending back the empty password or cleartext password in 'sha256_password'
+// authentication.
+func (c *Conn) genAuthResponse(authData []byte) ([]byte, bool, error) {
+ // password hashing
+ switch c.authPluginName {
+ case AUTH_NATIVE_PASSWORD:
+ return CalcPassword(authData[:20], []byte(c.password)), false, nil
+ case AUTH_CACHING_SHA2_PASSWORD:
+ return CalcCachingSha2Password(authData, c.password), false, nil
+ case AUTH_SHA256_PASSWORD:
+ if len(c.password) == 0 {
+ return nil, true, nil
+ }
+ if c.tlsConfig != nil || c.proto == "unix" {
+ // write cleartext auth packet
+ // see: https://dev.mysql.com/doc/refman/8.0/en/sha256-pluggable-authentication.html
+ return []byte(c.password), true, nil
+ } else {
+ // request public key from server
+ // see: https://dev.mysql.com/doc/internals/en/public-key-retrieval.html
+ return []byte{1}, false, nil
+ }
+ default:
+ // not reachable
+ return nil, false, fmt.Errorf("auth plugin '%s' is not supported", c.authPluginName)
+ }
+}
+
+// See: http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse
func (c *Conn) writeAuthHandshake() error {
+ if !authPluginAllowed(c.authPluginName) {
+ return fmt.Errorf("unknow auth plugin name '%s'", c.authPluginName)
+ }
// Adjust client capability flags based on server support
capability := CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION |
- CLIENT_LONG_PASSWORD | CLIENT_TRANSACTIONS | CLIENT_LONG_FLAG
+ CLIENT_LONG_PASSWORD | CLIENT_TRANSACTIONS | CLIENT_PLUGIN_AUTH | c.capability&CLIENT_LONG_FLAG
// To enable TLS / SSL
- if c.TLSConfig != nil {
- capability |= CLIENT_PLUGIN_AUTH
+ if c.tlsConfig != nil {
capability |= CLIENT_SSL
}
- capability &= c.capability
+ auth, addNull, err := c.genAuthResponse(c.salt)
+ if err != nil {
+ return err
+ }
+
+ // encode length of the auth plugin data
+ // here we use the Length-Encoded-Integer(LEI) as the data length may not fit into one byte
+ // see: https://dev.mysql.com/doc/internals/en/integer.html#length-encoded-integer
+ var authRespLEIBuf [9]byte
+ authRespLEI := AppendLengthEncodedInteger(authRespLEIBuf[:0], uint64(len(auth)))
+ if len(authRespLEI) > 1 {
+ // if the length can not be written in 1 byte, it must be written as a
+ // length encoded integer
+ capability |= CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA
+ }
//packet length
- //capbility 4
+ //capability 4
//max-packet size 4
//charset 1
//reserved all[0] 23
- length := 4 + 4 + 1 + 23
-
//username
- length += len(c.user) + 1
-
- //we only support secure connection
- auth := CalcPassword(c.salt, []byte(c.password))
-
- length += 1 + len(auth)
-
+ //auth
+ //mysql_native_password + null-terminated
+ length := 4 + 4 + 1 + 23 + len(c.user) + 1 + len(authRespLEI) + len(auth) + 21 + 1
+ if addNull {
+ length++
+ }
+ // db name
if len(c.db) > 0 {
capability |= CLIENT_CONNECT_WITH_DB
-
length += len(c.db) + 1
}
- // mysql_native_password + null-terminated
- length += 21 + 1
-
- c.capability = capability
-
data := make([]byte, length+4)
- //capability [32 bit]
+ // capability [32 bit]
data[4] = byte(capability)
data[5] = byte(capability >> 8)
data[6] = byte(capability >> 16)
data[7] = byte(capability >> 24)
- //MaxPacketSize [32 bit] (none)
- //data[8] = 0x00
- //data[9] = 0x00
- //data[10] = 0x00
- //data[11] = 0x00
+ // MaxPacketSize [32 bit] (none)
+ data[8] = 0x00
+ data[9] = 0x00
+ data[10] = 0x00
+ data[11] = 0x00
- //Charset [1 byte]
- //use default collation id 33 here, is utf-8
+ // Charset [1 byte]
+ // use default collation id 33 here, is utf-8
data[12] = byte(DEFAULT_COLLATION_ID)
// SSL Connection Request Packet
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest
- if c.TLSConfig != nil {
+ if c.tlsConfig != nil {
// Send TLS / SSL request packet
if err := c.WritePacket(data[:(4+4+1+23)+4]); err != nil {
return err
}
// Switch to TLS
- tlsConn := tls.Client(c.Conn.Conn, c.TLSConfig)
+ tlsConn := tls.Client(c.Conn.Conn, c.tlsConfig)
if err := tlsConn.Handshake(); err != nil {
return err
}
@@ -145,10 +220,13 @@ func (c *Conn) writeAuthHandshake() error {
c.Sequence = currentSequence
}
- //Filler [23 bytes] (all 0x00)
- pos := 13 + 23
+ // Filler [23 bytes] (all 0x00)
+ pos := 13
+ for ; pos < 13+23; pos++ {
+ data[pos] = 0
+ }
- //User [null terminated string]
+ // User [null terminated string]
if len(c.user) > 0 {
pos += copy(data[pos:], c.user)
}
@@ -156,8 +234,12 @@ func (c *Conn) writeAuthHandshake() error {
pos++
// auth [length encoded integer]
- data[pos] = byte(len(auth))
- pos += 1 + copy(data[pos+1:], auth)
+ pos += copy(data[pos:], authRespLEI)
+ pos += copy(data[pos:], auth)
+ if addNull {
+ data[pos] = 0x00
+ pos++
+ }
// db [null terminated string]
if len(c.db) > 0 {
@@ -167,7 +249,7 @@ func (c *Conn) writeAuthHandshake() error {
}
// Assume native client during response
- pos += copy(data[pos:], "mysql_native_password")
+ pos += copy(data[pos:], c.authPluginName)
data[pos] = 0x00
return c.WritePacket(data)
diff --git a/vendor/github.com/siddontang/go-mysql/client/client_test.go b/vendor/github.com/siddontang/go-mysql/client/client_test.go
index 85dd8f2..04bfdb2 100644
--- a/vendor/github.com/siddontang/go-mysql/client/client_test.go
+++ b/vendor/github.com/siddontang/go-mysql/client/client_test.go
@@ -1,41 +1,56 @@
package client
import (
- "crypto/tls"
"flag"
"fmt"
"strings"
"testing"
+ "github.com/juju/errors"
. "github.com/pingcap/check"
+ "github.com/siddontang/go-mysql/test_util/test_keys"
"github.com/siddontang/go-mysql/mysql"
)
var testHost = flag.String("host", "127.0.0.1", "MySQL server host")
-var testPort = flag.Int("port", 3306, "MySQL server port")
+// We cover the whole range of MySQL server versions using docker-compose to bind them to different ports for testing.
+// MySQL is constantly updating auth plugin to make it secure:
+// starting from MySQL 8.0.4, a new auth plugin is introduced, causing plain password auth to fail with error:
+// ERROR 1251 (08004): Client does not support authentication protocol requested by server; consider upgrading MySQL client
+// Hint: use docker-compose to start corresponding MySQL docker containers and add the their ports here
+var testPort = flag.String("port", "3306", "MySQL server port") // choose one or more form 5561,5641,3306,5722,8003,8012,8013, e.g. '3306,5722,8003'
var testUser = flag.String("user", "root", "MySQL user")
var testPassword = flag.String("pass", "", "MySQL password")
var testDB = flag.String("db", "test", "MySQL test database")
func Test(t *testing.T) {
+ segs := strings.Split(*testPort, ",")
+ for _, seg := range segs {
+ Suite(&clientTestSuite{port: seg})
+ }
TestingT(t)
}
type clientTestSuite struct {
- c *Conn
+ c *Conn
+ port string
}
-var _ = Suite(&clientTestSuite{})
-
func (s *clientTestSuite) SetUpSuite(c *C) {
var err error
- addr := fmt.Sprintf("%s:%d", *testHost, *testPort)
- s.c, err = Connect(addr, *testUser, *testPassword, *testDB)
+ addr := fmt.Sprintf("%s:%s", *testHost, s.port)
+ s.c, err = Connect(addr, *testUser, *testPassword, "")
if err != nil {
c.Fatal(err)
}
+ _, err = s.c.Execute("CREATE DATABASE IF NOT EXISTS " + *testDB)
+ c.Assert(err, IsNil)
+
+ _, err = s.c.Execute("USE " + *testDB)
+ c.Assert(err, IsNil)
+
s.testConn_CreateTable(c)
s.testStmt_CreateTable(c)
}
@@ -78,12 +93,15 @@ func (s *clientTestSuite) TestConn_Ping(c *C) {
c.Assert(err, IsNil)
}
-func (s *clientTestSuite) TestConn_TLS(c *C) {
+// NOTE for MySQL 5.5 and 5.6, server side has to config SSL to pass the TLS test, otherwise, it will throw error that
+// MySQL server does not support TLS required by the client. However, for MySQL 5.7 and above, auto generated certificates
+// are used by default so that manual config is no longer necessary.
+func (s *clientTestSuite) TestConn_TLS_Verify(c *C) {
// Verify that the provided tls.Config is used when attempting to connect to mysql.
// An empty tls.Config will result in a connection error.
- addr := fmt.Sprintf("%s:%d", *testHost, *testPort)
+ addr := fmt.Sprintf("%s:%s", *testHost, s.port)
_, err := Connect(addr, *testUser, *testPassword, *testDB, func(c *Conn) {
- c.TLSConfig = &tls.Config{}
+ c.UseSSL(false)
})
if err == nil {
c.Fatal("expected error")
@@ -91,7 +109,34 @@ func (s *clientTestSuite) TestConn_TLS(c *C) {
expected := "either ServerName or InsecureSkipVerify must be specified in the tls.Config"
if !strings.Contains(err.Error(), expected) {
- c.Fatal("expected '%s' to contain '%s'", err.Error(), expected)
+ c.Fatalf("expected '%s' to contain '%s'", err.Error(), expected)
+ }
+}
+
+func (s *clientTestSuite) TestConn_TLS_Skip_Verify(c *C) {
+ // An empty tls.Config will result in a connection error but we can configure to skip it.
+ addr := fmt.Sprintf("%s:%s", *testHost, s.port)
+ _, err := Connect(addr, *testUser, *testPassword, *testDB, func(c *Conn) {
+ c.UseSSL(true)
+ })
+ c.Assert(err, Equals, nil)
+}
+
+func (s *clientTestSuite) TestConn_TLS_Certificate(c *C) {
+ // This test uses the TLS suite in 'go-mysql/docker/resources'. The certificates are not valid for any names.
+ // And if server uses auto-generated certificates, it will be an error like:
+ // "x509: certificate is valid for MySQL_Server_8.0.12_Auto_Generated_Server_Certificate, not not-a-valid-name"
+ tlsConfig := NewClientTLSConfig(test_keys.CaPem, test_keys.CertPem, test_keys.KeyPem, false, "not-a-valid-name")
+ addr := fmt.Sprintf("%s:%s", *testHost, s.port)
+ _, err := Connect(addr, *testUser, *testPassword, *testDB, func(c *Conn) {
+ c.SetTLSConfig(tlsConfig)
+ })
+ if err == nil {
+ c.Fatal("expected error")
+ }
+ if !strings.Contains(errors.Details(err), "certificate is not valid for any names") &&
+ !strings.Contains(errors.Details(err), "certificate is valid for") {
+ c.Fatalf("expected errors for server name verification, but got unknown error: %s", errors.Details(err))
}
}
@@ -349,4 +394,4 @@ func (s *clientTestSuite) TestStmt_Trans(c *C) {
str, _ = r.GetString(0, 0)
c.Assert(str, Equals, `abc`)
-}
+}
\ No newline at end of file
diff --git a/vendor/github.com/siddontang/go-mysql/client/conn.go b/vendor/github.com/siddontang/go-mysql/client/conn.go
index 54ee3f0..b015b43 100644
--- a/vendor/github.com/siddontang/go-mysql/client/conn.go
+++ b/vendor/github.com/siddontang/go-mysql/client/conn.go
@@ -18,7 +18,8 @@ type Conn struct {
user string
password string
db string
- TLSConfig *tls.Config
+ tlsConfig *tls.Config
+ proto string
capability uint32
@@ -26,7 +27,8 @@ type Conn struct {
charset string
- salt []byte
+ salt []byte
+ authPluginName string
connectionID uint32
}
@@ -56,6 +58,7 @@ func Connect(addr string, user string, password string, dbName string, options .
c.user = user
c.password = password
c.db = dbName
+ c.proto = proto
//use default charset here, utf-8
c.charset = DEFAULT_CHARSET
@@ -85,7 +88,7 @@ func (c *Conn) handshake() error {
return errors.Trace(err)
}
- if _, err := c.readOK(); err != nil {
+ if err := c.handleAuthResult(); err != nil {
c.Close()
return errors.Trace(err)
}
@@ -109,6 +112,18 @@ func (c *Conn) Ping() error {
return nil
}
+// use default SSL
+// pass to options when connect
+func (c *Conn) UseSSL(insecureSkipVerify bool) {
+ c.tlsConfig = &tls.Config{InsecureSkipVerify: insecureSkipVerify}
+}
+
+// use user-specified TLS config
+// pass to options when connect
+func (c *Conn) SetTLSConfig(config *tls.Config) {
+ c.tlsConfig = config
+}
+
func (c *Conn) UseDB(dbName string) error {
if c.db == dbName {
return nil
diff --git a/vendor/github.com/siddontang/go-mysql/client/resp.go b/vendor/github.com/siddontang/go-mysql/client/resp.go
index 4e5f855..71aa1bc 100644
--- a/vendor/github.com/siddontang/go-mysql/client/resp.go
+++ b/vendor/github.com/siddontang/go-mysql/client/resp.go
@@ -1,8 +1,14 @@
package client
+import "C"
import (
"encoding/binary"
+ "bytes"
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
+
"github.com/juju/errors"
. "github.com/siddontang/go-mysql/mysql"
"github.com/siddontang/go/hack"
@@ -32,7 +38,7 @@ func (c *Conn) isEOFPacket(data []byte) bool {
func (c *Conn) handleOKPacket(data []byte) (*Result, error) {
var n int
- var pos int = 1
+ var pos = 1
r := new(Result)
@@ -64,7 +70,7 @@ func (c *Conn) handleOKPacket(data []byte) (*Result, error) {
func (c *Conn) handleErrorPacket(data []byte) error {
e := new(MyError)
- var pos int = 1
+ var pos = 1
e.Code = binary.LittleEndian.Uint16(data[pos:])
pos += 2
@@ -81,6 +87,116 @@ func (c *Conn) handleErrorPacket(data []byte) error {
return e
}
+func (c *Conn) handleAuthResult() error {
+ data, switchToPlugin, err := c.readAuthResult()
+ if err != nil {
+ return err
+ }
+ // handle auth switch, only support 'sha256_password', and 'caching_sha2_password'
+ if switchToPlugin != "" {
+ //fmt.Printf("now switching auth plugin to '%s'\n", switchToPlugin)
+ if data == nil {
+ data = c.salt
+ } else {
+ copy(c.salt, data)
+ }
+ c.authPluginName = switchToPlugin
+ auth, addNull, err := c.genAuthResponse(data)
+ if err = c.WriteAuthSwitchPacket(auth, addNull); err != nil {
+ return err
+ }
+
+ // Read Result Packet
+ data, switchToPlugin, err = c.readAuthResult()
+ if err != nil {
+ return err
+ }
+
+ // Do not allow to change the auth plugin more than once
+ if switchToPlugin != "" {
+ return errors.Errorf("can not switch auth plugin more than once")
+ }
+ }
+
+ // handle caching_sha2_password
+ if c.authPluginName == AUTH_CACHING_SHA2_PASSWORD {
+ if data == nil {
+ return nil // auth already succeeded
+ }
+ if data[0] == CACHE_SHA2_FAST_AUTH {
+ if _, err = c.readOK(); err == nil {
+ return nil // auth successful
+ }
+ } else if data[0] == CACHE_SHA2_FULL_AUTH {
+ // need full authentication
+ if c.tlsConfig != nil || c.proto == "unix" {
+ if err = c.WriteClearAuthPacket(c.password); err != nil {
+ return err
+ }
+ } else {
+ if err = c.WritePublicKeyAuthPacket(c.password, c.salt); err != nil {
+ return err
+ }
+ }
+ } else {
+ errors.Errorf("invalid packet")
+ }
+ } else if c.authPluginName == AUTH_SHA256_PASSWORD {
+ if len(data) == 0 {
+ return nil // auth already succeeded
+ }
+ block, _ := pem.Decode(data)
+ pub, err := x509.ParsePKIXPublicKey(block.Bytes)
+ if err != nil {
+ return err
+ }
+ // send encrypted password
+ err = c.WriteEncryptedPassword(c.password, c.salt, pub.(*rsa.PublicKey))
+ if err != nil {
+ return err
+ }
+ _, err = c.readOK()
+ return err
+ }
+ return nil
+}
+
+func (c *Conn) readAuthResult() ([]byte, string, error) {
+ data, err := c.ReadPacket()
+ if err != nil {
+ return nil, "", err
+ }
+
+ // see: https://insidemysql.com/preparing-your-community-connector-for-mysql-8-part-2-sha256/
+ // packet indicator
+ switch data[0] {
+
+ case OK_HEADER:
+ _, err := c.handleOKPacket(data)
+ return nil, "", err
+
+ case MORE_DATE_HEADER:
+ return data[1:], "", err
+
+ case EOF_HEADER:
+ // server wants to switch auth
+ if len(data) < 1 {
+ // https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
+ return nil, AUTH_MYSQL_OLD_PASSWORD, nil
+ }
+ pluginEndIndex := bytes.IndexByte(data, 0x00)
+ if pluginEndIndex < 0 {
+ return nil, "", errors.New("invalid packet")
+ }
+ plugin := string(data[1:pluginEndIndex])
+ authData := data[pluginEndIndex+1:]
+ return authData, plugin, nil
+
+ default: // Error otherwise
+ return nil, "", c.handleErrorPacket(data)
+ }
+}
+
func (c *Conn) readOK() (*Result, error) {
data, err := c.ReadPacket()
if err != nil {
diff --git a/vendor/github.com/siddontang/go-mysql/client/tls.go b/vendor/github.com/siddontang/go-mysql/client/tls.go
new file mode 100644
index 0000000..3772a50
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/client/tls.go
@@ -0,0 +1,28 @@
+package client
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+)
+
+// generate TLS config for client side
+// if insecureSkipVerify is set to true, serverName will not be validated
+func NewClientTLSConfig(caPem, certPem, keyPem []byte, insecureSkipVerify bool, serverName string) *tls.Config {
+ pool := x509.NewCertPool()
+ if !pool.AppendCertsFromPEM(caPem) {
+ panic("failed to add ca PEM")
+ }
+
+ cert, err := tls.X509KeyPair(certPem, keyPem)
+ if err != nil {
+ panic(err)
+ }
+
+ config := &tls.Config{
+ Certificates: []tls.Certificate{cert},
+ RootCAs: pool,
+ InsecureSkipVerify: insecureSkipVerify,
+ ServerName: serverName,
+ }
+ return config
+}
diff --git a/vendor/github.com/siddontang/go-mysql/cmd/go-binlogparser/main.go b/vendor/github.com/siddontang/go-mysql/cmd/go-binlogparser/main.go
index 3990d00..fa5b5cf 100644
--- a/vendor/github.com/siddontang/go-mysql/cmd/go-binlogparser/main.go
+++ b/vendor/github.com/siddontang/go-mysql/cmd/go-binlogparser/main.go
@@ -23,6 +23,6 @@ func main() {
err := p.ParseFile(*name, *offset, f)
if err != nil {
- println(err)
+ println(err.Error())
}
}
diff --git a/vendor/github.com/siddontang/go-mysql/cmd/go-canal/main.go b/vendor/github.com/siddontang/go-mysql/cmd/go-canal/main.go
index 4d0c1df..6f60a4a 100644
--- a/vendor/github.com/siddontang/go-mysql/cmd/go-canal/main.go
+++ b/vendor/github.com/siddontang/go-mysql/cmd/go-canal/main.go
@@ -7,8 +7,10 @@ import (
"os/signal"
"strings"
"syscall"
+ "time"
"github.com/siddontang/go-mysql/canal"
+ "github.com/siddontang/go-mysql/mysql"
)
var host = flag.String("host", "127.0.0.1", "MySQL host")
@@ -18,8 +20,6 @@ var password = flag.String("password", "", "MySQL password")
var flavor = flag.String("flavor", "mysql", "Flavor: mysql or mariadb")
-var dataDir = flag.String("data-dir", "./var", "Path to store data, like master.info")
-
var serverID = flag.Int("server-id", 101, "Unique Server ID")
var mysqldump = flag.String("mysqldump", "mysqldump", "mysqldump execution path")
@@ -28,6 +28,12 @@ var tables = flag.String("tables", "", "dump tables, seperated by comma, will ov
var tableDB = flag.String("table_db", "test", "database for dump tables")
var ignoreTables = flag.String("ignore_tables", "", "ignore tables, must be database.table format, separated by comma")
+var startName = flag.String("bin_name", "", "start sync from binlog name")
+var startPos = flag.Uint("bin_pos", 0, "start sync from binlog position of")
+
+var heartbeatPeriod = flag.Duration("heartbeat", 60*time.Second, "master heartbeat period")
+var readTimeout = flag.Duration("read_timeout", 90*time.Second, "connection read timeout")
+
func main() {
flag.Parse()
@@ -36,8 +42,10 @@ func main() {
cfg.User = *user
cfg.Password = *password
cfg.Flavor = *flavor
- cfg.DataDir = *dataDir
+ cfg.UseDecimal = true
+ cfg.ReadTimeout = *readTimeout
+ cfg.HeartbeatPeriod = *heartbeatPeriod
cfg.ServerID = uint32(*serverID)
cfg.Dump.ExecutionPath = *mysqldump
cfg.Dump.DiscardErr = false
@@ -65,14 +73,20 @@ func main() {
c.AddDumpDatabases(subs...)
}
- c.RegRowsEventHandler(&handler{})
+ c.SetEventHandler(&handler{})
- err = c.Start()
- if err != nil {
- fmt.Printf("start canal err %V", err)
- os.Exit(1)
+ startPos := mysql.Position{
+ Name: *startName,
+ Pos: uint32(*startPos),
}
+ go func() {
+ err = c.RunFrom(startPos)
+ if err != nil {
+ fmt.Printf("start canal err %v", err)
+ }
+ }()
+
sc := make(chan os.Signal, 1)
signal.Notify(sc,
os.Kill,
@@ -88,9 +102,10 @@ func main() {
}
type handler struct {
+ canal.DummyEventHandler
}
-func (h *handler) Do(e *canal.RowsEvent) error {
+func (h *handler) OnRow(e *canal.RowsEvent) error {
fmt.Printf("%v\n", e)
return nil
diff --git a/vendor/github.com/siddontang/go-mysql/cmd/go-mysqlbinlog/main.go b/vendor/github.com/siddontang/go-mysql/cmd/go-mysqlbinlog/main.go
index 80fa73d..2c19c87 100644
--- a/vendor/github.com/siddontang/go-mysql/cmd/go-mysqlbinlog/main.go
+++ b/vendor/github.com/siddontang/go-mysql/cmd/go-mysqlbinlog/main.go
@@ -4,12 +4,11 @@
package main
import (
+ "context"
"flag"
"fmt"
"os"
- "golang.org/x/net/context"
-
"github.com/juju/errors"
"github.com/siddontang/go-mysql/mysql"
"github.com/siddontang/go-mysql/replication"
@@ -43,11 +42,12 @@ func main() {
Password: *password,
RawModeEnabled: *rawMode,
SemiSyncEnabled: *semiSync,
+ UseDecimal: true,
}
b := replication.NewBinlogSyncer(cfg)
- pos := mysql.Position{*file, uint32(*pos)}
+ pos := mysql.Position{Name: *file, Pos: uint32(*pos)}
if len(*backupPath) > 0 {
// Backup will always use RawMode.
err := b.StartBackup(*backupPath, pos, 0)
@@ -65,6 +65,11 @@ func main() {
for {
e, err := s.GetEvent(context.Background())
if err != nil {
+ // Try to output all left events
+ events := s.DumpEvents()
+ for _, e := range events {
+ e.Dump(os.Stdout)
+ }
fmt.Printf("Get event error: %v\n", errors.ErrorStack(err))
return
}
diff --git a/vendor/github.com/siddontang/go-mysql/docker/docker-compose.yaml b/vendor/github.com/siddontang/go-mysql/docker/docker-compose.yaml
new file mode 100644
index 0000000..151786e
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/docker/docker-compose.yaml
@@ -0,0 +1,80 @@
+version: '3'
+services:
+
+ mysql-5.5.61:
+ image: "mysql:5.5.61"
+ container_name: "mysql-server-5.5.61"
+ ports:
+ - "5561:3306"
+ command: --ssl=TRUE --ssl-ca=/usr/local/mysql/ca.pem --ssl-cert=/usr/local/mysql/server-cert.pem --ssl-key=/usr/local/mysql/server-key.pem
+ volumes:
+ - ./resources/ca.pem:/usr/local/mysql/ca.pem
+ - ./resources/server-cert.pem:/usr/local/mysql/server-cert.pem
+ - ./resources/server-key.pem:/usr/local/mysql/server-key.pem
+ environment:
+ - MYSQL_ALLOW_EMPTY_PASSWORD=true
+ - bind-address=0.0.0.0
+
+ mysql-5.6.41:
+ image: "mysql:5.6.41"
+ container_name: "mysql-server-5.6.41"
+ ports:
+ - "5641:3306"
+ command: --ssl=TRUE --ssl-ca=/usr/local/mysql/ca.pem --ssl-cert=/usr/local/mysql/server-cert.pem --ssl-key=/usr/local/mysql/server-key.pem
+ volumes:
+ - ./resources/ca.pem:/usr/local/mysql/ca.pem
+ - ./resources/server-cert.pem:/usr/local/mysql/server-cert.pem
+ - ./resources/server-key.pem:/usr/local/mysql/server-key.pem
+ environment:
+ - MYSQL_ALLOW_EMPTY_PASSWORD=true
+ - bind-address=0.0.0.0
+
+ mysql-default:
+ image: "mysql:5.7.22"
+ container_name: "mysql-server-default"
+ ports:
+ - "3306:3306"
+ command: ["mysqld", "--log-bin=mysql-bin", "--server-id=1"]
+ environment:
+ - MYSQL_ALLOW_EMPTY_PASSWORD=true
+ - bind-address=0.0.0.0
+
+ mysql-5.7.22:
+ image: "mysql:5.7.22"
+ container_name: "mysql-server-5.7.22"
+ ports:
+ - "5722:3306"
+ environment:
+ - MYSQL_ALLOW_EMPTY_PASSWORD=true
+ - bind-address=0.0.0.0
+
+ mysql-8.0.3:
+ image: "mysql:8.0.3"
+ container_name: "mysql-server-8.0.3"
+ ports:
+ - "8003:3306"
+ environment:
+ - MYSQL_ALLOW_EMPTY_PASSWORD=true
+ - bind-address=0.0.0.0
+
+ mysql-8.0.12:
+ image: "mysql:8.0.12"
+ container_name: "mysql-server-8.0.12"
+ ports:
+ - "8012:3306"
+ environment:
+ #- MYSQL_ROOT_PASSWORD=abc123
+ - MYSQL_ALLOW_EMPTY_PASSWORD=true
+ - bind-address=0.0.0.0
+
+ mysql-8.0.12-sha256:
+ image: "mysql:8.0.12"
+ container_name: "mysql-server-8.0.12-sha256"
+ ports:
+ - "8013:3306"
+ entrypoint: ['/entrypoint.sh', '--default-authentication-plugin=sha256_password']
+ environment:
+ #- MYSQL_ROOT_PASSWORD=abc123
+ - MYSQL_ALLOW_EMPTY_PASSWORD=true
+ - bind-address=0.0.0.0
+
diff --git a/vendor/github.com/siddontang/go-mysql/docker/resources/ca.key b/vendor/github.com/siddontang/go-mysql/docker/resources/ca.key
new file mode 100644
index 0000000..8344ed2
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/docker/resources/ca.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAsV6xlhFxMn14Pn7XBRGLt8/HXmhVVu20IKFgIOyX7gAZr0QL
+suT1fGf5zH9HrlgOMkfdhV847U03KPfUnBsi9lS6/xOxnH/OzTYM0WW0eNMGF7eo
+xrS64GSbPVX4pLi5+uwrrZT5HmDgZi49ANmuX6UYmH/eRRvSIoYUTV6t0aYsLyKv
+lpEAtRAe4AlKB236j5ggmJ36QUhTFTbeNbeOOgloTEdPK8Y/kgpnhiqzMdPqqIc7
+IeXUc456yX8MJUgniTM2qCNTFdEw+C2Ok0RbU6TI2SuEgVF4jtCcVEKxZ8kYbioO
+NaePQKFR/EhdXO+/ag1IEdXElH9knLOfB+zCgwIDAQABAoIBAC2U0jponRiGmgIl
+gohw6+D+6pNeaKAAUkwYbKXJZ3noWLFr4T3GDTg9WDqvcvJg+rT9NvZxdCW3tDc5
+CVBcwO1g9PVcUEaRqcme3EhrxKdQQ76QmjUGeQf1ktd+YnmiZ1kOnGLtZ9/gsYpQ
+06iGSIOX3+xA4BQOhEAPCOShMjYv+pWvWGhZCSmeulKulNVPBbG2H1I9EoT5Wd+Q
+8LUfgZOuUXrtcsuvEf2XeacCo0pUbjx8ErhDHP6aPasFAXq15Bm8DnsUOrrsjcLy
+sPy/mHwpd6kTw+O3EzjTdaYSFRoDSpfpIS5Bk+yicdxOmTwp1pzDu6HyYnuOnc9Q
+JQ8HvlECgYEA2z+1HKVz5k7NYyRihW4l30vAcAGcgG1RObB6DmLbGu4MPvMymgLO
+1QhYjlCcKfRHhVS2864op3Oba2fIgCc2am0DIQQ6kZ23ick78aj9G2ZXYpdpIPLu
+Kl1AZHj6XDrOPVqidwcE6iYHLLWp9x4Atgw5d44XmhQ0kwrqAfccOX8CgYEAzxnl
+7Uu+v5WI3hBVJpxS6eoS1TdztVIJaumyE43pBoHEuJrp4MRf0Lu2DiDpH8R3/RoE
+o+ykn6xzphYwUopYaCWzYTKoXvxCvmLkDjHcpdzLtwWbKG+MJih2nTADEDI7sK4e
+a3IU8miK6FeqkQHfs/5dlQa8q31yxiukw0qQEP0CgYAtLg6jTZD5l6mJUZkfx9f0
+EMciDaLzcBN54Nz2E/b0sLNDUZhO1l9K1QJyqTfVCWqnlhJxWqU0BIW1d1iA2BPF
+kJtBdX6gPTDyKs64eMtXlxpQzcSzLnxXrIm1apyk3tVbHU83WfHwUk/OLc1NiBg7
+a394HIbOkHVZC7m3F/Xv/wKBgQDHrM2du8D+kJs0l4SxxFjAxPlBb8R01tLTrNwP
+tGwu5OEZp+rE1jEXXFRMTPjXsyKI+hPtRJT4ilm6kXwnqNFSIL9RgHkLk6Z6T3hY
+I0T8+ePD43jURLBYffzW0tqxO+2HDGmx6H0/twHuv89pHehkb2Qk8ijoIvyNCrlB
+vVsntQKBgCK04nbb+G45D6TKCcZ6XKT/+qneJQE5cfvHl5EqrfjSmlnEUpJjJfyc
+6Q1PtXtWOtOScU93f1JKL7+JBbWDn9uBlboM8BSkAVVd/2vyg88RuEtIru1syxcW
+d1rMxqaMRJuhuqaS33CoPUpn30b4zVrPhQJ2+TwDAol4qIGHaie8
+-----END RSA PRIVATE KEY-----
diff --git a/vendor/github.com/siddontang/go-mysql/docker/resources/ca.pem b/vendor/github.com/siddontang/go-mysql/docker/resources/ca.pem
new file mode 100644
index 0000000..e251bd6
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/docker/resources/ca.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDtTCCAp2gAwIBAgIJANeS1FOzWXlZMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTgwODE2MTUxNDE5WhcNMjEwNjA1MTUxNDE5WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAsV6xlhFxMn14Pn7XBRGLt8/HXmhVVu20IKFgIOyX7gAZr0QLsuT1fGf5
+zH9HrlgOMkfdhV847U03KPfUnBsi9lS6/xOxnH/OzTYM0WW0eNMGF7eoxrS64GSb
+PVX4pLi5+uwrrZT5HmDgZi49ANmuX6UYmH/eRRvSIoYUTV6t0aYsLyKvlpEAtRAe
+4AlKB236j5ggmJ36QUhTFTbeNbeOOgloTEdPK8Y/kgpnhiqzMdPqqIc7IeXUc456
+yX8MJUgniTM2qCNTFdEw+C2Ok0RbU6TI2SuEgVF4jtCcVEKxZ8kYbioONaePQKFR
+/EhdXO+/ag1IEdXElH9knLOfB+zCgwIDAQABo4GnMIGkMB0GA1UdDgQWBBQgHiwD
+00upIbCOunlK4HRw89DhjjB1BgNVHSMEbjBsgBQgHiwD00upIbCOunlK4HRw89Dh
+jqFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV
+BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJANeS1FOzWXlZMAwGA1UdEwQF
+MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAFMZFQTFKU5tWIpWh8BbVZeVZcng0Kiq
+qwbhVwaTkqtfmbqw8/w+faOWylmLncQEMmgvnUltGMQlQKBwQM2byzPkz9phal3g
+uI0JWJYqtcMyIQUB9QbbhrDNC9kdt/ji/x6rrIqzaMRuiBXqH5LQ9h856yXzArqd
+cAQGzzYpbUCIv7ciSB93cKkU73fQLZVy5ZBy1+oAa1V9U4cb4G/20/PDmT+G3Gxz
+pEjeDKtz8XINoWgA2cSdfAhNZt5vqJaCIZ8qN0z6C7SUKwUBderERUMLUXdhUldC
+KTVHyEPvd0aULd5S5vEpKCnHcQmFcLdoN8t9k9pR9ZgwqXbyJHlxWFo=
+-----END CERTIFICATE-----
diff --git a/vendor/github.com/siddontang/go-mysql/docker/resources/client-cert.pem b/vendor/github.com/siddontang/go-mysql/docker/resources/client-cert.pem
new file mode 100644
index 0000000..e478e78
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/docker/resources/client-cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAe4CCQDg06wCf7hcuTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMB4XDTE4MDgxOTA4NDY0N1oXDTI4MDgxNjA4NDY0N1owRTELMAkG
+A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0
+IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AMmivNyk3Rc1ZvLPhb3WPNkf9f2G4g9nMc0+eMrR1IKJ1U1A98ojeIBT+pfk1bSq
+Ol0UDm66Vd3YQ+4HpyYHaYV6mwoTEulL9Quk8RLa7TRwQu3PLi3o567RhVIrx8Z3
+umuWb9UUzJfSFH04Uy9+By4CJCqIQXU4BocLIKHhIkNjmAQ9fWO1hZ8zmPHSEfvu
+Wqa/DYKGvF0MJr4Lnkm/sKUd+O94p9suvwM6OGIDibACiKRF2H+JbgQLbA58zkLv
+DHtXOqsCL7HxiONX8VDrQjN/66Nh9omk/Bx2Ec8IqappHvWf768HSH79x/znaial
+VEV+6K0gP+voJHfnA10laWMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAPD+Fn1qj
+HN62GD3eIgx6wJxYuemhdbgmEwrZZf4V70lS6e9Iloif0nBiISDxJUpXVWNRCN3Z
+3QVC++F7deDmWL/3dSpXRvWsapzbCUhVQ2iBcnZ7QCOdvAqYR1ecZx70zvXCwBcd
+6XKmRtdeNV6B211KRFmTYtVyPq4rcWrkTPGwPBncJI1eQQmyFv2T9SwVVp96Nbrq
+sf7zrJGmuVCdXGPRi/ALVHtJCz6oPoft3I707eMe+ijnFqwGbmMD4fMD6Ync/hEz
+PyR5FMZkXSXHS0gkA5pfwW7wJ2WSWDhI6JMS1gbatY7QzgHbKoQpxBPUXlnzzj2h
+7O9cgFTh/XOZXQ==
+-----END CERTIFICATE-----
diff --git a/vendor/github.com/siddontang/go-mysql/docker/resources/client-key.pem b/vendor/github.com/siddontang/go-mysql/docker/resources/client-key.pem
new file mode 100644
index 0000000..996a97b
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/docker/resources/client-key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAyaK83KTdFzVm8s+FvdY82R/1/YbiD2cxzT54ytHUgonVTUD3
+yiN4gFP6l+TVtKo6XRQObrpV3dhD7genJgdphXqbChMS6Uv1C6TxEtrtNHBC7c8u
+LejnrtGFUivHxne6a5Zv1RTMl9IUfThTL34HLgIkKohBdTgGhwsgoeEiQ2OYBD19
+Y7WFnzOY8dIR++5apr8Ngoa8XQwmvgueSb+wpR3473in2y6/Azo4YgOJsAKIpEXY
+f4luBAtsDnzOQu8Me1c6qwIvsfGI41fxUOtCM3/ro2H2iaT8HHYRzwipqmke9Z/v
+rwdIfv3H/OdqJqVURX7orSA/6+gkd+cDXSVpYwIDAQABAoIBAAGLY5L1GFRzLkSx
+3j5kA7dODV5RyC2CBtmhnt8+2DffwmiDFOLRfrzM5+B9+j0WCLhpzOqANuQqIesS
+1+7so5xIIiPjnYN393qNWuNgFe0O5xRXP+1OGWg3ZqQIfdFBXYYxcs3ZCPAoxctn
+wQteFcP+dDR3MrkpIrOqHCfhR5foieOMP+9k5kCjk+aZqhEmFyko+X+xVO/32xs+
++3qXhUrHt3Op5on30QMOFguniQlYwLJkd9qVjGuGMIrVPxoUz0rya4SKrGKgkAr8
+mvQe2+sZo7cc6zC2ceaGMJU7z1RalTrCObbg5mynlu+Vf0E/YiES0abkQhSbcSB9
+mAkJC7ECgYEA/H1NDEiO164yYK9ji4HM/8CmHegWS4qsgrzAs8lU0yAcgdg9e19A
+mNi8yssfIBCw62RRE4UGWS5F82myhmvq/mXbf8eCJ2CMgdCHQh1rT7WFD/Uc5Pe/
+8Lv2jNMQ61POguPyq6D0qcf8iigKIMHa1MIgAOmrgWrxulfbSUhm370CgYEAzHBu
+J9p4dAqW32+Hrtv2XE0KUjH72TXr13WErosgeGTfsIW2exXByvLasxOJSY4Wb8oS
+OLZ7bgp/EBchAc7my+nF8n5uOJxipWQUB5BoeB9aUJZ9AnWF4RDl94Jlm5PYBG/J
+lRXrMtSTTIgmSw3Ft2A1vRMOQaHX89lNwOZL758CgYAXOT84/gOFexRPKFKzpkDA
+1WtyHMLQN/UeIVZoMwCGWtHEb6tYCa7bYDQdQwmd3Wsoe5WpgfbPhR4SAYrWKl72
+/09tNWCXVp4V4qRORH52Wm/ew+Dgfpk8/0zyLwfDXXYFPAo6Fxfp9ecYng4wbSQ/
+pYtkChooUTniteoJl4s+0QKBgHbFEpoAqF3yEPi52L/TdmrlLwvVkhT86IkB8xVc
+Kn8HS5VH+V3EpBN9x2SmAupCq/JCGRftnAOwAWWdqkVcqGTq6V8Z6HrnD8A6RhCm
+6qpuvI94/iNBl4fLw25pyRH7cFITh68fTsb3DKQ3rNeJpsYEFPRFb9Ddb5JxOmTI
+5nDNAoGBAM+SyOhUGU+0Uw2WJaGWzmEutjeMRr5Z+cZ8keC/ZJNdji/faaQoeOQR
+OXI8O6RBTBwVNQMyDyttT8J8BkISwfAhSdPkjgPw9GZ1pGREl53uCFDIlX2nvtQM
+ioNzG5WHB7Gd7eUUTA91kRF9MZJTHPqNiNGR0Udj/trGyGqJebni
+-----END RSA PRIVATE KEY-----
diff --git a/vendor/github.com/siddontang/go-mysql/docker/resources/server-cert.pem b/vendor/github.com/siddontang/go-mysql/docker/resources/server-cert.pem
new file mode 100644
index 0000000..3cb3b9c
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/docker/resources/server-cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAe4CCQDg06wCf7hcuDANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMB4XDTE4MDgxOTA4NDUyNVoXDTI4MDgxNjA4NDUyNVowRTELMAkG
+A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0
+IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ALK2gqK4uvTlxJANO2JKdibvmh899z6oCo9Km0mz5unj4dpnq9hljsQuKtcHUcM4
+HXcE06knaJ4TOF7lcsjaqoDO7r/SaFgjjXCqNvHD0Su4B+7qe52BZZTRV1AANP10
+PvebarXSEzaZUCyHHhSF8+Qb4vX04XKX/TOqinTVGtlnduKzP5+qsaFBtpLAw1V0
+At9EQB5BgnTYtdIsmvD4/2WhBvOjVxab75yx0R4oof4F3u528tbEegcWhBtmy2Xd
+HI3S+TLljj3kOOdB+pgrVUl+KaDavWK3T+F1vTNDe56HEVNKeWlLy1scul61E0j9
+IkZAu6aRDxtKdl7bKu0BkzMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAma3yFqR7
+xkeaZBg4/1I3jSlaNe5+2JB4iybAkMOu77fG5zytLomTbzdhewsuBwpTVMJdga8T
+IdPeIFCin1U+5SkbjSMlpKf+krE+5CyrNJ5jAgO9ATIqx66oCTYXfGlNapGRLfSE
+sa0iMqCe/dr4GPU+flW2DZFWiyJVDSF1JjReQnfrWY+SD2SpP/lmlgltnY8MJngd
+xBLG5nsZCpUXGB713Q8ZyIm2ThVAMiskcxBleIZDDghLuhGvY/9eFJhZpvOkjWa6
+XGEi4E1G/SA+zVKFl41nHKCdqXdmIOnpcLlFBUVloQok5a95Kqc1TYw3f+WbdFff
+99dAgk3gWwWZQA==
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/vendor/github.com/siddontang/go-mysql/docker/resources/server-key.pem b/vendor/github.com/siddontang/go-mysql/docker/resources/server-key.pem
new file mode 100644
index 0000000..babaaae
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/docker/resources/server-key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAsraCori69OXEkA07Ykp2Ju+aHz33PqgKj0qbSbPm6ePh2mer
+2GWOxC4q1wdRwzgddwTTqSdonhM4XuVyyNqqgM7uv9JoWCONcKo28cPRK7gH7up7
+nYFllNFXUAA0/XQ+95tqtdITNplQLIceFIXz5Bvi9fThcpf9M6qKdNUa2Wd24rM/
+n6qxoUG2ksDDVXQC30RAHkGCdNi10iya8Pj/ZaEG86NXFpvvnLHRHiih/gXe7nby
+1sR6BxaEG2bLZd0cjdL5MuWOPeQ450H6mCtVSX4poNq9YrdP4XW9M0N7nocRU0p5
+aUvLWxy6XrUTSP0iRkC7ppEPG0p2Xtsq7QGTMwIDAQABAoIBAGh1m8hHWCg7gXh9
+838RbRx3IswuKS27hWiaQEiFWmzOIb7KqDy1qAxtu+ayRY1paHegH6QY/+Kd824s
+ibpzbgQacJ04/HrAVTVMmQ8Z2VLHoAN7lcPL1bd14aZGaLLZVtDeTDJ413grhxxv
+4ho27gcgcbo4Z+rWgk7H2WRPCAGYqWYAycm3yF5vy9QaO6edU+T588YsEQOos5iy
+5pVFSGDGZkcUp1ukL3BJYR+jvygn6WPCobQ/LScUdi+ucitaI9i+UdlLokZARVRG
+M/msqcTM73thR8yVRcexU6NUDxRBfZ/f7moSAEbBmGDXuxDcIyH9KGMQ2rMtN1X3
+lK8UNwkCgYEA2STJq/IUQHjdqd3Dqh/Q7Zm8/pMWFqLJSkqpnFtFsXPyUOx9zDOy
+KqkIfGeyKwvsj9X9BcZ0FUKj9zoct1/WpPY+h7i7+z0MIujBh4AMjAcDrt4o76yK
+UHuVmG2xKTdJoAbqOdToQeX6E82Ioal5pbB2W7AbCQScNBPZ52jxgtcCgYEA0rE7
+2dFiRm0YmuszFYxft2+GP6NgP3R2TQNEooi1uCXG2xgwObie1YCHzpZ5CfSqJIxP
+XB7DXpIWi7PxJoeai2F83LnmdFz6F1BPRobwDoSFNdaSKLg4Yf856zpgYNKhL1fE
+OoOXj4VBWBZh1XDfZV44fgwlMIf7edOF1XOagwUCgYAw953O+7FbdKYwF0V3iOM5
+oZDAK/UwN5eC/GFRVDfcM5RycVJRCVtlSWcTfuLr2C2Jpiz/72fgH34QU3eEVsV1
+v94MBznFB1hESw7ReqvZq/9FoO3EVrl+OtBaZmosLD6bKtQJJJ0Xtz/01UW5hxla
+pveZ55XBK9v51nwuNjk4UwKBgHD8fJUllSchUCWb5cwzeAz98Kdl7LJ6uQo5q2/i
+EllLYOWThiEeIYdrIuklholRPIDXAaPsF2c6vn5yo+q+o6EFSZlw0+YpCjDAb5Lp
+wAh5BprFk6HkkM/0t9Guf4rMyYWC8odSlE9x7YXYkuSMYDCTI4Zs6vCoq7I8PbQn
+B4AlAoGAZ6Ee5m/ph5UVp/3+cR6jCY7aHBUU/M3pbJSkVjBW+ymEBVJ6sUdz8k3P
+x8BiPEQggNN7faWBqRWP7KXPnDYHh6shYUgPJwI5HX6NE/ZDnnXjeysHRyf0oCo5
+S6tHXwHNKB5HS1c/KDyyNGjP2oi/MF4o/MGWNWEcK6TJA3RGOYM=
+-----END RSA PRIVATE KEY-----
diff --git a/vendor/github.com/siddontang/go-mysql/driver/dirver_test.go b/vendor/github.com/siddontang/go-mysql/driver/dirver_test.go
index 54a72cb..d43580f 100644
--- a/vendor/github.com/siddontang/go-mysql/driver/dirver_test.go
+++ b/vendor/github.com/siddontang/go-mysql/driver/dirver_test.go
@@ -11,6 +11,11 @@ import (
// Use docker mysql to test, mysql is 3306
var testHost = flag.String("host", "127.0.0.1", "MySQL master host")
+// possible choices for different MySQL versions are: 5561,5641,3306,5722,8003,8012
+var testPort = flag.Int("port", 3306, "MySQL server port")
+var testUser = flag.String("user", "root", "MySQL user")
+var testPassword = flag.String("pass", "", "MySQL password")
+var testDB = flag.String("db", "test", "MySQL test database")
func TestDriver(t *testing.T) {
TestingT(t)
@@ -23,7 +28,8 @@ type testDriverSuite struct {
var _ = Suite(&testDriverSuite{})
func (s *testDriverSuite) SetUpSuite(c *C) {
- dsn := fmt.Sprintf("root@%s:3306?test", *testHost)
+ addr := fmt.Sprintf("%s:%d", *testHost, *testPort)
+ dsn := fmt.Sprintf("%s:%s@%s?%s", *testUser, *testPassword, addr, *testDB)
var err error
s.db, err = sqlx.Open("mysql", dsn)
diff --git a/vendor/github.com/siddontang/go-mysql/driver/driver.go b/vendor/github.com/siddontang/go-mysql/driver/driver.go
index 6263929..e131548 100644
--- a/vendor/github.com/siddontang/go-mysql/driver/driver.go
+++ b/vendor/github.com/siddontang/go-mysql/driver/driver.go
@@ -20,7 +20,8 @@ type driver struct {
// DSN user:password@addr[?db]
func (d driver) Open(dsn string) (sqldriver.Conn, error) {
- seps := strings.Split(dsn, "@")
+ lastIndex := strings.LastIndex(dsn, "@")
+ seps := []string{dsn[:lastIndex], dsn[lastIndex+1:]}
if len(seps) != 2 {
return nil, errors.Errorf("invalid dsn, must user:password@addr[?db]")
}
diff --git a/vendor/github.com/siddontang/go-mysql/dump/dump.go b/vendor/github.com/siddontang/go-mysql/dump/dump.go
index a6ff209..1f8384d 100644
--- a/vendor/github.com/siddontang/go-mysql/dump/dump.go
+++ b/vendor/github.com/siddontang/go-mysql/dump/dump.go
@@ -8,6 +8,8 @@ import (
"strings"
"github.com/juju/errors"
+ "github.com/siddontang/go-log/log"
+ . "github.com/siddontang/go-mysql/mysql"
)
// Unlick mysqldump, Dumper is designed for parsing and syning data easily.
@@ -25,9 +27,16 @@ type Dumper struct {
Databases []string
+ Where string
+ Charset string
+
IgnoreTables map[string][]string
ErrOut io.Writer
+
+ masterDataSkipped bool
+ maxAllowedPacket int
+ hexBlob bool
}
func NewDumper(executionPath string, addr string, user string, password string) (*Dumper, error) {
@@ -47,17 +56,40 @@ func NewDumper(executionPath string, addr string, user string, password string)
d.Password = password
d.Tables = make([]string, 0, 16)
d.Databases = make([]string, 0, 16)
+ d.Charset = DEFAULT_CHARSET
d.IgnoreTables = make(map[string][]string)
+ d.masterDataSkipped = false
d.ErrOut = os.Stderr
return d, nil
}
+func (d *Dumper) SetCharset(charset string) {
+ d.Charset = charset
+}
+
+func (d *Dumper) SetWhere(where string) {
+ d.Where = where
+}
+
func (d *Dumper) SetErrOut(o io.Writer) {
d.ErrOut = o
}
+// In some cloud MySQL, we have no privilege to use `--master-data`.
+func (d *Dumper) SkipMasterData(v bool) {
+ d.masterDataSkipped = v
+}
+
+func (d *Dumper) SetMaxAllowedPacket(i int) {
+ d.maxAllowedPacket = i
+}
+
+func (d *Dumper) SetHexBlob(v bool) {
+ d.hexBlob = v
+}
+
func (d *Dumper) AddDatabases(dbs ...string) {
d.Databases = append(d.Databases, dbs...)
}
@@ -82,22 +114,35 @@ func (d *Dumper) Reset() {
d.TableDB = ""
d.IgnoreTables = make(map[string][]string)
d.Databases = d.Databases[0:0]
+ d.Where = ""
}
func (d *Dumper) Dump(w io.Writer) error {
args := make([]string, 0, 16)
// Common args
- seps := strings.Split(d.Addr, ":")
- args = append(args, fmt.Sprintf("--host=%s", seps[0]))
- if len(seps) > 1 {
- args = append(args, fmt.Sprintf("--port=%s", seps[1]))
+ if strings.Contains(d.Addr, "/") {
+ args = append(args, fmt.Sprintf("--socket=%s", d.Addr))
+ } else {
+ seps := strings.SplitN(d.Addr, ":", 2)
+ args = append(args, fmt.Sprintf("--host=%s", seps[0]))
+ if len(seps) > 1 {
+ args = append(args, fmt.Sprintf("--port=%s", seps[1]))
+ }
}
args = append(args, fmt.Sprintf("--user=%s", d.User))
args = append(args, fmt.Sprintf("--password=%s", d.Password))
- args = append(args, "--master-data")
+ if !d.masterDataSkipped {
+ args = append(args, "--master-data")
+ }
+
+ if d.maxAllowedPacket > 0 {
+ // mysqldump param should be --max-allowed-packet=%dM not be --max_allowed_packet=%dM
+ args = append(args, fmt.Sprintf("--max-allowed-packet=%dM", d.maxAllowedPacket))
+ }
+
args = append(args, "--single-transaction")
args = append(args, "--skip-lock-tables")
@@ -112,12 +157,25 @@ func (d *Dumper) Dump(w io.Writer) error {
// Multi row is easy for us to parse the data
args = append(args, "--skip-extended-insert")
+ if d.hexBlob {
+ // Use hex for the binary type
+ args = append(args, "--hex-blob")
+ }
+
for db, tables := range d.IgnoreTables {
for _, table := range tables {
args = append(args, fmt.Sprintf("--ignore-table=%s.%s", db, table))
}
}
+ if len(d.Charset) != 0 {
+ args = append(args, fmt.Sprintf("--default-character-set=%s", d.Charset))
+ }
+
+ if len(d.Where) != 0 {
+ args = append(args, fmt.Sprintf("--where=%s", d.Where))
+ }
+
if len(d.Tables) == 0 && len(d.Databases) == 0 {
args = append(args, "--all-databases")
} else if len(d.Tables) == 0 {
@@ -133,6 +191,7 @@ func (d *Dumper) Dump(w io.Writer) error {
w.Write([]byte(fmt.Sprintf("USE `%s`;\n", d.TableDB)))
}
+ log.Infof("exec mysqldump with %v", args)
cmd := exec.Command(d.ExecutionPath, args...)
cmd.Stderr = d.ErrOut
@@ -147,7 +206,7 @@ func (d *Dumper) DumpAndParse(h ParseHandler) error {
done := make(chan error, 1)
go func() {
- err := Parse(r, h)
+ err := Parse(r, h, !d.masterDataSkipped)
r.CloseWithError(err)
done <- err
}()
diff --git a/vendor/github.com/siddontang/go-mysql/dump/dump_test.go b/vendor/github.com/siddontang/go-mysql/dump/dump_test.go
index 39e430f..eed4c75 100644
--- a/vendor/github.com/siddontang/go-mysql/dump/dump_test.go
+++ b/vendor/github.com/siddontang/go-mysql/dump/dump_test.go
@@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"os"
+ "strings"
"testing"
. "github.com/pingcap/check"
@@ -38,6 +39,7 @@ func (s *schemaTestSuite) SetUpSuite(c *C) {
c.Assert(err, IsNil)
c.Assert(s.d, NotNil)
+ s.d.SetCharset("utf8")
s.d.SetErrOut(os.Stderr)
_, err = s.conn.Execute("CREATE DATABASE IF NOT EXISTS test1")
@@ -177,7 +179,7 @@ func (s *schemaTestSuite) TestParse(c *C) {
err := s.d.Dump(&buf)
c.Assert(err, IsNil)
- err = Parse(&buf, new(testParseHandler))
+ err = Parse(&buf, new(testParseHandler), true)
c.Assert(err, IsNil)
}
@@ -196,3 +198,29 @@ func (s *parserTestSuite) TestParseValue(c *C) {
values, err = parseValues(str)
c.Assert(err, NotNil)
}
+
+func (s *parserTestSuite) TestParseLine(c *C) {
+ lines := []struct {
+ line string
+ expected string
+ }{
+ {line: "INSERT INTO `test` VALUES (1, 'first', 'hello mysql; 2', 'e1', 'a,b');",
+ expected: "1, 'first', 'hello mysql; 2', 'e1', 'a,b'"},
+ {line: "INSERT INTO `test` VALUES (0x22270073646661736661736466, 'first', 'hello mysql; 2', 'e1', 'a,b');",
+ expected: "0x22270073646661736661736466, 'first', 'hello mysql; 2', 'e1', 'a,b'"},
+ }
+
+ f := func(c rune) bool {
+ return c == '\r' || c == '\n'
+ }
+
+ for _, t := range lines {
+ l := strings.TrimRightFunc(t.line, f)
+
+ m := valuesExp.FindAllStringSubmatch(l, -1)
+
+ c.Assert(m, HasLen, 1)
+ c.Assert(m[0][1], Matches, "test")
+ c.Assert(m[0][2], Matches, t.expected)
+ }
+}
diff --git a/vendor/github.com/siddontang/go-mysql/dump/parser.go b/vendor/github.com/siddontang/go-mysql/dump/parser.go
index 69f65c7..ad40925 100644
--- a/vendor/github.com/siddontang/go-mysql/dump/parser.go
+++ b/vendor/github.com/siddontang/go-mysql/dump/parser.go
@@ -6,6 +6,7 @@ import (
"io"
"regexp"
"strconv"
+ "strings"
"github.com/juju/errors"
"github.com/siddontang/go-mysql/mysql"
@@ -34,7 +35,7 @@ func init() {
// Parse the dump data with Dumper generate.
// It can not parse all the data formats with mysqldump outputs
-func Parse(r io.Reader, h ParseHandler) error {
+func Parse(r io.Reader, h ParseHandler, parseBinlogPos bool) error {
rb := bufio.NewReaderSize(r, 1024*16)
var db string
@@ -48,9 +49,12 @@ func Parse(r io.Reader, h ParseHandler) error {
break
}
- line = line[0 : len(line)-1]
+ // Ignore '\n' on Linux or '\r\n' on Windows
+ line = strings.TrimRightFunc(line, func(c rune) bool {
+ return c == '\r' || c == '\n'
+ })
- if !binlogParsed {
+ if parseBinlogPos && !binlogParsed {
if m := binlogExp.FindAllStringSubmatch(line, -1); len(m) == 1 {
name := m[0][1]
pos, err := strconv.ParseUint(m[0][2], 10, 64)
diff --git a/vendor/github.com/siddontang/go-mysql/failover/mariadb_gtid_handler.go b/vendor/github.com/siddontang/go-mysql/failover/mariadb_gtid_handler.go
index 5c8bd64..2798241 100644
--- a/vendor/github.com/siddontang/go-mysql/failover/mariadb_gtid_handler.go
+++ b/vendor/github.com/siddontang/go-mysql/failover/mariadb_gtid_handler.go
@@ -46,12 +46,12 @@ func (h *MariadbGTIDHandler) FindBestSlaves(slaves []*Server) ([]*Server, error)
if len(str) == 0 {
seq = 0
} else {
- g, err := ParseMariadbGTIDSet(str)
+ g, err := ParseMariadbGTID(str)
if err != nil {
return nil, errors.Trace(err)
}
- seq = g.(MariadbGTID).SequenceNumber
+ seq = g.SequenceNumber
}
ps[i] = seq
@@ -118,7 +118,7 @@ func (h *MariadbGTIDHandler) WaitRelayLogDone(s *Server) error {
fname, _ := r.GetStringByName(0, "Master_Log_File")
pos, _ := r.GetIntByName(0, "Read_Master_Log_Pos")
- return s.MasterPosWait(Position{fname, uint32(pos)}, 0)
+ return s.MasterPosWait(Position{Name: fname, Pos: uint32(pos)}, 0)
}
func (h *MariadbGTIDHandler) WaitCatchMaster(s *Server, m *Server) error {
diff --git a/vendor/github.com/siddontang/go-mysql/failover/server.go b/vendor/github.com/siddontang/go-mysql/failover/server.go
index ff87464..c02d6c8 100644
--- a/vendor/github.com/siddontang/go-mysql/failover/server.go
+++ b/vendor/github.com/siddontang/go-mysql/failover/server.go
@@ -152,7 +152,7 @@ func (s *Server) FetchSlaveReadPos() (Position, error) {
fname, _ := r.GetStringByName(0, "Master_Log_File")
pos, _ := r.GetIntByName(0, "Read_Master_Log_Pos")
- return Position{fname, uint32(pos)}, nil
+ return Position{Name: fname, Pos: uint32(pos)}, nil
}
// Get current executed binlog filename and position from master
@@ -165,7 +165,7 @@ func (s *Server) FetchSlaveExecutePos() (Position, error) {
fname, _ := r.GetStringByName(0, "Relay_Master_Log_File")
pos, _ := r.GetIntByName(0, "Exec_Master_Log_Pos")
- return Position{fname, uint32(pos)}, nil
+ return Position{Name: fname, Pos: uint32(pos)}, nil
}
func (s *Server) MasterPosWait(pos Position, timeout int) error {
diff --git a/vendor/github.com/siddontang/go-mysql/glide.lock b/vendor/github.com/siddontang/go-mysql/glide.lock
deleted file mode 100644
index 7d71953..0000000
--- a/vendor/github.com/siddontang/go-mysql/glide.lock
+++ /dev/null
@@ -1,30 +0,0 @@
-hash: 1a3d05afef96cd7601a004e573b128db9051eecd1b5d0a3d69d3fa1ee1a3e3b8
-updated: 2016-09-03T12:30:00.028685232+08:00
-imports:
-- name: github.com/BurntSushi/toml
- version: 056c9bc7be7190eaa7715723883caffa5f8fa3e4
-- name: github.com/go-sql-driver/mysql
- version: 3654d25ec346ee8ce71a68431025458d52a38ac0
-- name: github.com/jmoiron/sqlx
- version: 54aec3fd91a2b2129ffaca0d652b8a9223ee2d9e
- subpackages:
- - reflectx
-- name: github.com/juju/errors
- version: 6f54ff6318409d31ff16261533ce2c8381a4fd5d
-- name: github.com/ngaut/log
- version: cec23d3e10b016363780d894a0eb732a12c06e02
-- name: github.com/pingcap/check
- version: ce8a2f822ab1e245a4eefcef2996531c79c943f1
-- name: github.com/satori/go.uuid
- version: 879c5887cd475cd7864858769793b2ceb0d44feb
-- name: github.com/siddontang/go
- version: 354e14e6c093c661abb29fd28403b3c19cff5514
- subpackages:
- - hack
- - ioutil2
- - sync2
-- name: golang.org/x/net
- version: 6acef71eb69611914f7a30939ea9f6e194c78172
- subpackages:
- - context
-testImports: []
diff --git a/vendor/github.com/siddontang/go-mysql/glide.yaml b/vendor/github.com/siddontang/go-mysql/glide.yaml
deleted file mode 100644
index 3561648..0000000
--- a/vendor/github.com/siddontang/go-mysql/glide.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
-package: github.com/siddontang/go-mysql
-import:
-- package: github.com/BurntSushi/toml
- version: 056c9bc7be7190eaa7715723883caffa5f8fa3e4
-- package: github.com/go-sql-driver/mysql
- version: 3654d25ec346ee8ce71a68431025458d52a38ac0
-- package: github.com/jmoiron/sqlx
- version: 54aec3fd91a2b2129ffaca0d652b8a9223ee2d9e
- subpackages:
- - reflectx
-- package: github.com/juju/errors
- version: 6f54ff6318409d31ff16261533ce2c8381a4fd5d
-- package: github.com/ngaut/log
- version: cec23d3e10b016363780d894a0eb732a12c06e02
-- package: github.com/pingcap/check
- version: ce8a2f822ab1e245a4eefcef2996531c79c943f1
-- package: github.com/satori/go.uuid
- version: ^1.1.0
-- package: github.com/siddontang/go
- version: 354e14e6c093c661abb29fd28403b3c19cff5514
- subpackages:
- - hack
- - ioutil2
- - sync2
-- package: golang.org/x/net
- version: 6acef71eb69611914f7a30939ea9f6e194c78172
diff --git a/vendor/github.com/siddontang/go-mysql/mysql/const.go b/vendor/github.com/siddontang/go-mysql/mysql/const.go
index 2f4ab63..256d163 100644
--- a/vendor/github.com/siddontang/go-mysql/mysql/const.go
+++ b/vendor/github.com/siddontang/go-mysql/mysql/const.go
@@ -6,16 +6,22 @@ const (
TimeFormat string = "2006-01-02 15:04:05"
)
-var (
- // maybe you can change for your specified name
- ServerVersion string = "go-mysql-0.1"
-)
-
const (
OK_HEADER byte = 0x00
+ MORE_DATE_HEADER byte = 0x01
ERR_HEADER byte = 0xff
EOF_HEADER byte = 0xfe
LocalInFile_HEADER byte = 0xfb
+
+ CACHE_SHA2_FAST_AUTH byte = 0x03
+ CACHE_SHA2_FULL_AUTH byte = 0x04
+)
+
+const (
+ AUTH_MYSQL_OLD_PASSWORD = "mysql_old_password"
+ AUTH_NATIVE_PASSWORD = "mysql_native_password"
+ AUTH_CACHING_SHA2_PASSWORD = "caching_sha2_password"
+ AUTH_SHA256_PASSWORD = "sha256_password"
)
const (
@@ -151,7 +157,6 @@ const (
)
const (
- AUTH_NAME = "mysql_native_password"
DEFAULT_CHARSET = "utf8"
DEFAULT_COLLATION_ID uint8 = 33
DEFAULT_COLLATION_NAME string = "utf8_general_ci"
diff --git a/vendor/github.com/siddontang/go-mysql/mysql/error.go b/vendor/github.com/siddontang/go-mysql/mysql/error.go
index 227e70e..876a408 100644
--- a/vendor/github.com/siddontang/go-mysql/mysql/error.go
+++ b/vendor/github.com/siddontang/go-mysql/mysql/error.go
@@ -57,3 +57,10 @@ func NewError(errCode uint16, message string) *MyError {
return e
}
+
+func ErrorCode(errMsg string) (code int) {
+ var tmpStr string
+ // golang scanf doesn't support %*,so I used a temporary variable
+ fmt.Sscanf(errMsg, "%s%d", &tmpStr, &code)
+ return
+}
diff --git a/vendor/github.com/siddontang/go-mysql/mysql/field.go b/vendor/github.com/siddontang/go-mysql/mysql/field.go
index c26f6a2..891f00b 100644
--- a/vendor/github.com/siddontang/go-mysql/mysql/field.go
+++ b/vendor/github.com/siddontang/go-mysql/mysql/field.go
@@ -31,42 +31,42 @@ func (p FieldData) Parse() (f *Field, err error) {
var n int
pos := 0
//skip catelog, always def
- n, err = SkipLengthEnodedString(p)
+ n, err = SkipLengthEncodedString(p)
if err != nil {
return
}
pos += n
//schema
- f.Schema, _, n, err = LengthEnodedString(p[pos:])
+ f.Schema, _, n, err = LengthEncodedString(p[pos:])
if err != nil {
return
}
pos += n
//table
- f.Table, _, n, err = LengthEnodedString(p[pos:])
+ f.Table, _, n, err = LengthEncodedString(p[pos:])
if err != nil {
return
}
pos += n
//org_table
- f.OrgTable, _, n, err = LengthEnodedString(p[pos:])
+ f.OrgTable, _, n, err = LengthEncodedString(p[pos:])
if err != nil {
return
}
pos += n
//name
- f.Name, _, n, err = LengthEnodedString(p[pos:])
+ f.Name, _, n, err = LengthEncodedString(p[pos:])
if err != nil {
return
}
pos += n
//org_name
- f.OrgName, _, n, err = LengthEnodedString(p[pos:])
+ f.OrgName, _, n, err = LengthEncodedString(p[pos:])
if err != nil {
return
}
diff --git a/vendor/github.com/siddontang/go-mysql/mysql/gtid.go b/vendor/github.com/siddontang/go-mysql/mysql/gtid.go
index db0d638..cde9901 100644
--- a/vendor/github.com/siddontang/go-mysql/mysql/gtid.go
+++ b/vendor/github.com/siddontang/go-mysql/mysql/gtid.go
@@ -11,6 +11,10 @@ type GTIDSet interface {
Equal(o GTIDSet) bool
Contain(o GTIDSet) bool
+
+ Update(GTIDStr string) error
+
+ Clone() GTIDSet
}
func ParseGTIDSet(flavor string, s string) (GTIDSet, error) {
diff --git a/vendor/github.com/siddontang/go-mysql/mysql/mariadb_gtid.go b/vendor/github.com/siddontang/go-mysql/mysql/mariadb_gtid.go
index ea89458..09fe7ac 100644
--- a/vendor/github.com/siddontang/go-mysql/mysql/mariadb_gtid.go
+++ b/vendor/github.com/siddontang/go-mysql/mysql/mariadb_gtid.go
@@ -1,28 +1,32 @@
package mysql
import (
+ "bytes"
"fmt"
"strconv"
"strings"
"github.com/juju/errors"
+ "github.com/siddontang/go-log/log"
+ "github.com/siddontang/go/hack"
)
+// MariadbGTID represent mariadb gtid, [domain ID]-[server-id]-[sequence]
type MariadbGTID struct {
DomainID uint32
ServerID uint32
SequenceNumber uint64
}
-// We don't support multi source replication, so the mariadb gtid set may have only domain-server-sequence
-func ParseMariadbGTIDSet(str string) (GTIDSet, error) {
+// ParseMariadbGTID parses mariadb gtid, [domain ID]-[server-id]-[sequence]
+func ParseMariadbGTID(str string) (*MariadbGTID, error) {
if len(str) == 0 {
- return MariadbGTID{0, 0, 0}, nil
+ return &MariadbGTID{0, 0, 0}, nil
}
seps := strings.Split(str, "-")
- var gtid MariadbGTID
+ gtid := new(MariadbGTID)
if len(seps) != 3 {
return gtid, errors.Errorf("invalid Mariadb GTID %v, must domain-server-sequence", str)
@@ -43,13 +47,13 @@ func ParseMariadbGTIDSet(str string) (GTIDSet, error) {
return gtid, errors.Errorf("invalid MariaDB GTID Sequence number (%v): %v", seps[2], err)
}
- return MariadbGTID{
+ return &MariadbGTID{
DomainID: uint32(domainID),
ServerID: uint32(serverID),
SequenceNumber: sequenceID}, nil
}
-func (gtid MariadbGTID) String() string {
+func (gtid *MariadbGTID) String() string {
if gtid.DomainID == 0 && gtid.ServerID == 0 && gtid.SequenceNumber == 0 {
return ""
}
@@ -57,24 +61,172 @@ func (gtid MariadbGTID) String() string {
return fmt.Sprintf("%d-%d-%d", gtid.DomainID, gtid.ServerID, gtid.SequenceNumber)
}
-func (gtid MariadbGTID) Encode() []byte {
- return []byte(gtid.String())
-}
-
-func (gtid MariadbGTID) Equal(o GTIDSet) bool {
- other, ok := o.(MariadbGTID)
- if !ok {
- return false
- }
-
- return gtid == other
-}
-
-func (gtid MariadbGTID) Contain(o GTIDSet) bool {
- other, ok := o.(MariadbGTID)
- if !ok {
- return false
- }
-
+// Contain return whether one mariadb gtid covers another mariadb gtid
+func (gtid *MariadbGTID) Contain(other *MariadbGTID) bool {
return gtid.DomainID == other.DomainID && gtid.SequenceNumber >= other.SequenceNumber
}
+
+// Clone clones a mariadb gtid
+func (gtid *MariadbGTID) Clone() *MariadbGTID {
+ o := new(MariadbGTID)
+ *o = *gtid
+ return o
+}
+
+func (gtid *MariadbGTID) forward(newer *MariadbGTID) error {
+ if newer.DomainID != gtid.DomainID {
+ return errors.Errorf("%s is not same with doamin of %s", newer, gtid)
+ }
+
+ /*
+ Here's a simplified example of binlog events.
+ Although I think one domain should have only one update at same time, we can't limit the user's usage.
+ we just output a warn log and let it go on
+ | mysqld-bin.000001 | 1453 | Gtid | 112 | 1495 | BEGIN GTID 0-112-6 |
+ | mysqld-bin.000001 | 1624 | Xid | 112 | 1655 | COMMIT xid=74 |
+ | mysqld-bin.000001 | 1655 | Gtid | 112 | 1697 | BEGIN GTID 0-112-7 |
+ | mysqld-bin.000001 | 1826 | Xid | 112 | 1857 | COMMIT xid=75 |
+ | mysqld-bin.000001 | 1857 | Gtid | 111 | 1899 | BEGIN GTID 0-111-5 |
+ | mysqld-bin.000001 | 1981 | Xid | 111 | 2012 | COMMIT xid=77 |
+ | mysqld-bin.000001 | 2012 | Gtid | 112 | 2054 | BEGIN GTID 0-112-8 |
+ | mysqld-bin.000001 | 2184 | Xid | 112 | 2215 | COMMIT xid=116 |
+ | mysqld-bin.000001 | 2215 | Gtid | 111 | 2257 | BEGIN GTID 0-111-6 |
+ */
+ if newer.SequenceNumber <= gtid.SequenceNumber {
+ log.Warnf("out of order binlog appears with gtid %s vs current position gtid %s", newer, gtid)
+ }
+
+ gtid.ServerID = newer.ServerID
+ gtid.SequenceNumber = newer.SequenceNumber
+ return nil
+}
+
+// MariadbGTIDSet is a set of mariadb gtid
+type MariadbGTIDSet struct {
+ Sets map[uint32]*MariadbGTID
+}
+
+// ParseMariadbGTIDSet parses str into mariadb gtid sets
+func ParseMariadbGTIDSet(str string) (GTIDSet, error) {
+ s := new(MariadbGTIDSet)
+ s.Sets = make(map[uint32]*MariadbGTID)
+ if str == "" {
+ return s, nil
+ }
+
+ sp := strings.Split(str, ",")
+
+ //todo, handle redundant same uuid
+ for i := 0; i < len(sp); i++ {
+ err := s.Update(sp[i])
+ if err != nil {
+ return nil, errors.Trace(err)
+ }
+ }
+ return s, nil
+}
+
+// AddSet adds mariadb gtid into mariadb gtid set
+func (s *MariadbGTIDSet) AddSet(gtid *MariadbGTID) error {
+ if gtid == nil {
+ return nil
+ }
+
+ o, ok := s.Sets[gtid.DomainID]
+ if ok {
+ err := o.forward(gtid)
+ if err != nil {
+ return errors.Trace(err)
+ }
+ } else {
+ s.Sets[gtid.DomainID] = gtid
+ }
+
+ return nil
+}
+
+// Update updates mariadb gtid set
+func (s *MariadbGTIDSet) Update(GTIDStr string) error {
+ gtid, err := ParseMariadbGTID(GTIDStr)
+ if err != nil {
+ return err
+ }
+
+ err = s.AddSet(gtid)
+ return errors.Trace(err)
+}
+
+func (s *MariadbGTIDSet) String() string {
+ return hack.String(s.Encode())
+}
+
+// Encode encodes mariadb gtid set
+func (s *MariadbGTIDSet) Encode() []byte {
+ var buf bytes.Buffer
+ sep := ""
+ for _, gtid := range s.Sets {
+ buf.WriteString(sep)
+ buf.WriteString(gtid.String())
+ sep = ","
+ }
+
+ return buf.Bytes()
+}
+
+// Clone clones a mariadb gtid set
+func (s *MariadbGTIDSet) Clone() GTIDSet {
+ clone := &MariadbGTIDSet{
+ Sets: make(map[uint32]*MariadbGTID),
+ }
+ for domainID, gtid := range s.Sets {
+ clone.Sets[domainID] = gtid.Clone()
+ }
+
+ return clone
+}
+
+// Equal returns true if two mariadb gtid set is same, otherwise return false
+func (s *MariadbGTIDSet) Equal(o GTIDSet) bool {
+ other, ok := o.(*MariadbGTIDSet)
+ if !ok {
+ return false
+ }
+
+ if len(other.Sets) != len(s.Sets) {
+ return false
+ }
+
+ for domainID, gtid := range other.Sets {
+ o, ok := s.Sets[domainID]
+ if !ok {
+ return false
+ }
+
+ if *gtid != *o {
+ return false
+ }
+ }
+
+ return true
+}
+
+// Contain return whether one mariadb gtid set covers another mariadb gtid set
+func (s *MariadbGTIDSet) Contain(o GTIDSet) bool {
+ other, ok := o.(*MariadbGTIDSet)
+ if !ok {
+ return false
+ }
+
+ for doaminID, gtid := range other.Sets {
+ o, ok := s.Sets[doaminID]
+ if !ok {
+ return false
+ }
+
+ if !o.Contain(gtid) {
+ return false
+ }
+ }
+
+ return true
+}
diff --git a/vendor/github.com/siddontang/go-mysql/mysql/mariadb_gtid_test.go b/vendor/github.com/siddontang/go-mysql/mysql/mariadb_gtid_test.go
new file mode 100644
index 0000000..1455e26
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/mysql/mariadb_gtid_test.go
@@ -0,0 +1,234 @@
+package mysql
+
+import (
+ "github.com/pingcap/check"
+)
+
+type mariaDBTestSuite struct {
+}
+
+var _ = check.Suite(&mariaDBTestSuite{})
+
+func (t *mariaDBTestSuite) SetUpSuite(c *check.C) {
+
+}
+
+func (t *mariaDBTestSuite) TearDownSuite(c *check.C) {
+
+}
+
+func (t *mariaDBTestSuite) TestParseMariaDBGTID(c *check.C) {
+ cases := []struct {
+ gtidStr string
+ hashError bool
+ }{
+ {"0-1-1", false},
+ {"", false},
+ {"0-1-1-1", true},
+ {"1", true},
+ {"0-1-seq", true},
+ }
+
+ for _, cs := range cases {
+ gtid, err := ParseMariadbGTID(cs.gtidStr)
+ if cs.hashError {
+ c.Assert(err, check.NotNil)
+ } else {
+ c.Assert(err, check.IsNil)
+ c.Assert(gtid.String(), check.Equals, cs.gtidStr)
+ }
+ }
+}
+
+func (t *mariaDBTestSuite) TestMariaDBGTIDConatin(c *check.C) {
+ cases := []struct {
+ originGTIDStr, otherGTIDStr string
+ contain bool
+ }{
+ {"0-1-1", "0-1-2", false},
+ {"0-1-1", "", true},
+ {"2-1-1", "1-1-1", false},
+ {"1-2-1", "1-1-1", true},
+ {"1-2-2", "1-1-1", true},
+ }
+
+ for _, cs := range cases {
+ originGTID, err := ParseMariadbGTID(cs.originGTIDStr)
+ c.Assert(err, check.IsNil)
+ otherGTID, err := ParseMariadbGTID(cs.otherGTIDStr)
+ c.Assert(err, check.IsNil)
+
+ c.Assert(originGTID.Contain(otherGTID), check.Equals, cs.contain)
+ }
+}
+
+func (t *mariaDBTestSuite) TestMariaDBGTIDClone(c *check.C) {
+ gtid, err := ParseMariadbGTID("1-1-1")
+ c.Assert(err, check.IsNil)
+
+ clone := gtid.Clone()
+ c.Assert(gtid, check.DeepEquals, clone)
+}
+
+func (t *mariaDBTestSuite) TestMariaDBForward(c *check.C) {
+ cases := []struct {
+ currentGTIDStr, newerGTIDStr string
+ hashError bool
+ }{
+ {"0-1-1", "0-1-2", false},
+ {"0-1-1", "", false},
+ {"2-1-1", "1-1-1", true},
+ {"1-2-1", "1-1-1", false},
+ {"1-2-2", "1-1-1", false},
+ }
+
+ for _, cs := range cases {
+ currentGTID, err := ParseMariadbGTID(cs.currentGTIDStr)
+ c.Assert(err, check.IsNil)
+ newerGTID, err := ParseMariadbGTID(cs.newerGTIDStr)
+ c.Assert(err, check.IsNil)
+
+ err = currentGTID.forward(newerGTID)
+ if cs.hashError {
+ c.Assert(err, check.NotNil)
+ c.Assert(currentGTID.String(), check.Equals, cs.currentGTIDStr)
+ } else {
+ c.Assert(err, check.IsNil)
+ c.Assert(currentGTID.String(), check.Equals, cs.newerGTIDStr)
+ }
+ }
+}
+
+func (t *mariaDBTestSuite) TestParseMariaDBGTIDSet(c *check.C) {
+ cases := []struct {
+ gtidStr string
+ subGTIDs map[uint32]string //domain ID => gtid string
+ expectedStr []string // test String()
+ hasError bool
+ }{
+ {"0-1-1", map[uint32]string{0: "0-1-1"}, []string{"0-1-1"}, false},
+ {"", nil, []string{""}, false},
+ {"0-1-1,1-2-3", map[uint32]string{0: "0-1-1", 1: "1-2-3"}, []string{"0-1-1,1-2-3", "1-2-3,0-1-1"}, false},
+ {"0-1--1", nil, nil, true},
+ }
+
+ for _, cs := range cases {
+ gtidSet, err := ParseMariadbGTIDSet(cs.gtidStr)
+ if cs.hasError {
+ c.Assert(err, check.NotNil)
+ } else {
+ c.Assert(err, check.IsNil)
+ mariadbGTIDSet, ok := gtidSet.(*MariadbGTIDSet)
+ c.Assert(ok, check.IsTrue)
+
+ // check sub gtid
+ c.Assert(mariadbGTIDSet.Sets, check.HasLen, len(cs.subGTIDs))
+ for domainID, gtid := range mariadbGTIDSet.Sets {
+ c.Assert(mariadbGTIDSet.Sets, check.HasKey, domainID)
+ c.Assert(gtid.String(), check.Equals, cs.subGTIDs[domainID])
+ }
+
+ // check String() function
+ inExpectedResult := false
+ actualStr := mariadbGTIDSet.String()
+ for _, str := range cs.expectedStr {
+ if str == actualStr {
+ inExpectedResult = true
+ break
+ }
+ }
+ c.Assert(inExpectedResult, check.IsTrue)
+ }
+ }
+}
+
+func (t *mariaDBTestSuite) TestMariaDBGTIDSetUpdate(c *check.C) {
+ cases := []struct {
+ isNilGTID bool
+ gtidStr string
+ subGTIDs map[uint32]string
+ }{
+ {true, "", map[uint32]string{1: "1-1-1", 2: "2-2-2"}},
+ {false, "1-2-2", map[uint32]string{1: "1-2-2", 2: "2-2-2"}},
+ {false, "1-2-1", map[uint32]string{1: "1-2-1", 2: "2-2-2"}},
+ {false, "3-2-1", map[uint32]string{1: "1-1-1", 2: "2-2-2", 3: "3-2-1"}},
+ }
+
+ for _, cs := range cases {
+ gtidSet, err := ParseMariadbGTIDSet("1-1-1,2-2-2")
+ c.Assert(err, check.IsNil)
+ mariadbGTIDSet, ok := gtidSet.(*MariadbGTIDSet)
+ c.Assert(ok, check.IsTrue)
+
+ if cs.isNilGTID {
+ c.Assert(mariadbGTIDSet.AddSet(nil), check.IsNil)
+ } else {
+ err := gtidSet.Update(cs.gtidStr)
+ c.Assert(err, check.IsNil)
+ }
+ // check sub gtid
+ c.Assert(mariadbGTIDSet.Sets, check.HasLen, len(cs.subGTIDs))
+ for domainID, gtid := range mariadbGTIDSet.Sets {
+ c.Assert(mariadbGTIDSet.Sets, check.HasKey, domainID)
+ c.Assert(gtid.String(), check.Equals, cs.subGTIDs[domainID])
+ }
+ }
+}
+
+func (t *mariaDBTestSuite) TestMariaDBGTIDSetEqual(c *check.C) {
+ cases := []struct {
+ originGTIDStr, otherGTIDStr string
+ equals bool
+ }{
+ {"", "", true},
+ {"1-1-1", "1-1-1,2-2-2", false},
+ {"1-1-1,2-2-2", "1-1-1", false},
+ {"1-1-1,2-2-2", "1-1-1,2-2-2", true},
+ {"1-1-1,2-2-2", "1-1-1,2-2-3", false},
+ }
+
+ for _, cs := range cases {
+ originGTID, err := ParseMariadbGTIDSet(cs.originGTIDStr)
+ c.Assert(err, check.IsNil)
+
+ otherGTID, err := ParseMariadbGTIDSet(cs.otherGTIDStr)
+ c.Assert(err, check.IsNil)
+
+ c.Assert(originGTID.Equal(otherGTID), check.Equals, cs.equals)
+ }
+}
+
+func (t *mariaDBTestSuite) TestMariaDBGTIDSetContain(c *check.C) {
+ cases := []struct {
+ originGTIDStr, otherGTIDStr string
+ contain bool
+ }{
+ {"", "", true},
+ {"1-1-1", "1-1-1,2-2-2", false},
+ {"1-1-1,2-2-2", "1-1-1", true},
+ {"1-1-1,2-2-2", "1-1-1,2-2-2", true},
+ {"1-1-1,2-2-2", "1-1-1,2-2-1", true},
+ {"1-1-1,2-2-2", "1-1-1,2-2-3", false},
+ }
+
+ for _, cs := range cases {
+ originGTIDSet, err := ParseMariadbGTIDSet(cs.originGTIDStr)
+ c.Assert(err, check.IsNil)
+
+ otherGTIDSet, err := ParseMariadbGTIDSet(cs.otherGTIDStr)
+ c.Assert(err, check.IsNil)
+
+ c.Assert(originGTIDSet.Contain(otherGTIDSet), check.Equals, cs.contain)
+ }
+}
+
+func (t *mariaDBTestSuite) TestMariaDBGTIDSetClone(c *check.C) {
+ cases := []string{"", "1-1-1", "1-1-1,2-2-2"}
+
+ for _, str := range cases {
+ gtidSet, err := ParseMariadbGTIDSet(str)
+ c.Assert(err, check.IsNil)
+
+ c.Assert(gtidSet.Clone(), check.DeepEquals, gtidSet)
+ }
+}
diff --git a/vendor/github.com/siddontang/go-mysql/mysql/mysql_gtid.go b/vendor/github.com/siddontang/go-mysql/mysql/mysql_gtid.go
index c54ded0..a937cb8 100644
--- a/vendor/github.com/siddontang/go-mysql/mysql/mysql_gtid.go
+++ b/vendor/github.com/siddontang/go-mysql/mysql/mysql_gtid.go
@@ -97,7 +97,11 @@ func (s IntervalSlice) Normalize() IntervalSlice {
n = append(n, s[i])
continue
} else {
- n[len(n)-1] = Interval{last.Start, s[i].Stop}
+ stop := s[i].Stop
+ if last.Stop > stop {
+ stop = last.Stop
+ }
+ n[len(n)-1] = Interval{last.Start, stop}
}
}
@@ -285,17 +289,28 @@ func (s *UUIDSet) Decode(data []byte) error {
return err
}
+func (s *UUIDSet) Clone() *UUIDSet {
+ clone := new(UUIDSet)
+
+ clone.SID, _ = uuid.FromString(s.SID.String())
+ clone.Intervals = s.Intervals.Normalize()
+
+ return clone
+}
+
type MysqlGTIDSet struct {
Sets map[string]*UUIDSet
}
func ParseMysqlGTIDSet(str string) (GTIDSet, error) {
s := new(MysqlGTIDSet)
+ s.Sets = make(map[string]*UUIDSet)
+ if str == "" {
+ return s, nil
+ }
sp := strings.Split(str, ",")
- s.Sets = make(map[string]*UUIDSet, len(sp))
-
//todo, handle redundant same uuid
for i := 0; i < len(sp); i++ {
if set, err := ParseUUIDSet(sp[i]); err != nil {
@@ -334,6 +349,9 @@ func DecodeMysqlGTIDSet(data []byte) (*MysqlGTIDSet, error) {
}
func (s *MysqlGTIDSet) AddSet(set *UUIDSet) {
+ if set == nil {
+ return
+ }
sid := set.SID.String()
o, ok := s.Sets[sid]
if ok {
@@ -343,6 +361,17 @@ func (s *MysqlGTIDSet) AddSet(set *UUIDSet) {
}
}
+func (s *MysqlGTIDSet) Update(GTIDStr string) error {
+ uuidSet, err := ParseUUIDSet(GTIDStr)
+ if err != nil {
+ return err
+ }
+
+ s.AddSet(uuidSet)
+
+ return nil
+}
+
func (s *MysqlGTIDSet) Contain(o GTIDSet) bool {
sub, ok := o.(*MysqlGTIDSet)
if !ok {
@@ -407,3 +436,14 @@ func (s *MysqlGTIDSet) Encode() []byte {
return buf.Bytes()
}
+
+func (gtid *MysqlGTIDSet) Clone() GTIDSet {
+ clone := &MysqlGTIDSet{
+ Sets: make(map[string]*UUIDSet),
+ }
+ for sid, uuidSet := range gtid.Sets {
+ clone.Sets[sid] = uuidSet.Clone()
+ }
+
+ return clone
+}
diff --git a/vendor/github.com/siddontang/go-mysql/mysql/mysql_test.go b/vendor/github.com/siddontang/go-mysql/mysql/mysql_test.go
index 8fbaaab..df4b206 100644
--- a/vendor/github.com/siddontang/go-mysql/mysql/mysql_test.go
+++ b/vendor/github.com/siddontang/go-mysql/mysql/mysql_test.go
@@ -1,6 +1,7 @@
package mysql
import (
+ "strings"
"testing"
"github.com/pingcap/check"
@@ -15,11 +16,11 @@ type mysqlTestSuite struct {
var _ = check.Suite(&mysqlTestSuite{})
-func (s *mysqlTestSuite) SetUpSuite(c *check.C) {
+func (t *mysqlTestSuite) SetUpSuite(c *check.C) {
}
-func (s *mysqlTestSuite) TearDownSuite(c *check.C) {
+func (t *mysqlTestSuite) TearDownSuite(c *check.C) {
}
@@ -59,6 +60,12 @@ func (t *mysqlTestSuite) TestMysqlGTIDIntervalSlice(c *check.C) {
n = i.Normalize()
c.Assert(n, check.DeepEquals, IntervalSlice{Interval{1, 3}, Interval{4, 5}})
+ i = IntervalSlice{Interval{1, 4}, Interval{2, 3}}
+ i.Sort()
+ c.Assert(i, check.DeepEquals, IntervalSlice{Interval{1, 4}, Interval{2, 3}})
+ n = i.Normalize()
+ c.Assert(n, check.DeepEquals, IntervalSlice{Interval{1, 4}})
+
n1 := IntervalSlice{Interval{1, 3}, Interval{4, 5}}
n2 := IntervalSlice{Interval{1, 2}}
@@ -91,6 +98,15 @@ func (t *mysqlTestSuite) TestMysqlGTIDCodec(c *check.C) {
c.Assert(gs, check.DeepEquals, o)
}
+func (t *mysqlTestSuite) TestMysqlUpdate(c *check.C) {
+ g1, err := ParseMysqlGTIDSet("3E11FA47-71CA-11E1-9E33-C80AA9429562:21-57")
+ c.Assert(err, check.IsNil)
+
+ g1.Update("3E11FA47-71CA-11E1-9E33-C80AA9429562:21-58")
+
+ c.Assert(strings.ToUpper(g1.String()), check.Equals, "3E11FA47-71CA-11E1-9E33-C80AA9429562:21-58")
+}
+
func (t *mysqlTestSuite) TestMysqlGTIDContain(c *check.C) {
g1, err := ParseMysqlGTIDSet("3E11FA47-71CA-11E1-9E33-C80AA9429562:23")
c.Assert(err, check.IsNil)
@@ -151,3 +167,26 @@ func (t *mysqlTestSuite) TestMysqlParseBinaryUint64(c *check.C) {
u64 := ParseBinaryUint64([]byte{1, 2, 3, 4, 5, 6, 7, 128})
c.Assert(u64, check.Equals, 128*uint64(72057594037927936)+7*uint64(281474976710656)+6*uint64(1099511627776)+5*uint64(4294967296)+4*16777216+3*65536+2*256+1)
}
+
+func (t *mysqlTestSuite) TestErrorCode(c *check.C) {
+ tbls := []struct {
+ msg string
+ code int
+ }{
+ {"ERROR 1094 (HY000): Unknown thread id: 1094", 1094},
+ {"error string", 0},
+ {"abcdefg", 0},
+ {"123455 ks094", 0},
+ {"ERROR 1046 (3D000): Unknown error 1046", 1046},
+ }
+ for _, v := range tbls {
+ c.Assert(ErrorCode(v.msg), check.Equals, v.code)
+ }
+}
+
+func (t *mysqlTestSuite) TestMysqlNullDecode(c *check.C) {
+ _, isNull, n := LengthEncodedInt([]byte{0xfb})
+
+ c.Assert(isNull, check.IsTrue)
+ c.Assert(n, check.Equals, 1)
+}
diff --git a/vendor/github.com/siddontang/go-mysql/mysql/resultset.go b/vendor/github.com/siddontang/go-mysql/mysql/resultset.go
index a50d9f8..b01e1a5 100644
--- a/vendor/github.com/siddontang/go-mysql/mysql/resultset.go
+++ b/vendor/github.com/siddontang/go-mysql/mysql/resultset.go
@@ -28,7 +28,7 @@ func (p RowData) ParseText(f []*Field) ([]interface{}, error) {
var n int = 0
for i := range f {
- v, isNull, n, err = LengthEnodedString(p[pos:])
+ v, isNull, n, err = LengthEncodedString(p[pos:])
if err != nil {
return nil, errors.Trace(err)
}
@@ -115,7 +115,8 @@ func (p RowData) ParseBinary(f []*Field) ([]interface{}, error) {
} else {
data[i] = ParseBinaryInt24(p[pos : pos+3])
}
- pos += 4
+ //3 byte
+ pos += 3
continue
case MYSQL_TYPE_LONG:
@@ -150,7 +151,7 @@ func (p RowData) ParseBinary(f []*Field) ([]interface{}, error) {
MYSQL_TYPE_BIT, MYSQL_TYPE_ENUM, MYSQL_TYPE_SET, MYSQL_TYPE_TINY_BLOB,
MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_BLOB,
MYSQL_TYPE_VAR_STRING, MYSQL_TYPE_STRING, MYSQL_TYPE_GEOMETRY:
- v, isNull, n, err = LengthEnodedString(p[pos:])
+ v, isNull, n, err = LengthEncodedString(p[pos:])
pos += n
if err != nil {
return nil, errors.Trace(err)
diff --git a/vendor/github.com/siddontang/go-mysql/mysql/resultset_helper.go b/vendor/github.com/siddontang/go-mysql/mysql/resultset_helper.go
index 488d253..307684d 100644
--- a/vendor/github.com/siddontang/go-mysql/mysql/resultset_helper.go
+++ b/vendor/github.com/siddontang/go-mysql/mysql/resultset_helper.go
@@ -38,6 +38,8 @@ func formatTextValue(value interface{}) ([]byte, error) {
return v, nil
case string:
return hack.Slice(v), nil
+ case nil:
+ return nil, nil
default:
return nil, errors.Errorf("invalid type %T", value)
}
@@ -77,23 +79,40 @@ func formatBinaryValue(value interface{}) ([]byte, error) {
return nil, errors.Errorf("invalid type %T", value)
}
}
+
+func fieldType(value interface{}) (typ uint8, err error) {
+ switch value.(type) {
+ case int8, int16, int32, int64, int:
+ typ = MYSQL_TYPE_LONGLONG
+ case uint8, uint16, uint32, uint64, uint:
+ typ = MYSQL_TYPE_LONGLONG
+ case float32, float64:
+ typ = MYSQL_TYPE_DOUBLE
+ case string, []byte:
+ typ = MYSQL_TYPE_VAR_STRING
+ case nil:
+ typ = MYSQL_TYPE_NULL
+ default:
+ err = errors.Errorf("unsupport type %T for resultset", value)
+ }
+ return
+}
+
func formatField(field *Field, value interface{}) error {
switch value.(type) {
case int8, int16, int32, int64, int:
field.Charset = 63
- field.Type = MYSQL_TYPE_LONGLONG
field.Flag = BINARY_FLAG | NOT_NULL_FLAG
case uint8, uint16, uint32, uint64, uint:
field.Charset = 63
- field.Type = MYSQL_TYPE_LONGLONG
field.Flag = BINARY_FLAG | NOT_NULL_FLAG | UNSIGNED_FLAG
case float32, float64:
field.Charset = 63
- field.Type = MYSQL_TYPE_DOUBLE
field.Flag = BINARY_FLAG | NOT_NULL_FLAG
case string, []byte:
field.Charset = 33
- field.Type = MYSQL_TYPE_VAR_STRING
+ case nil:
+ field.Charset = 33
default:
return errors.Errorf("unsupport type %T for resultset", value)
}
@@ -106,7 +125,13 @@ func BuildSimpleTextResultset(names []string, values [][]interface{}) (*Resultse
r.Fields = make([]*Field, len(names))
var b []byte
- var err error
+
+ if len(values) == 0 {
+ for i, name := range names {
+ r.Fields[i] = &Field{Name: hack.Slice(name), Charset: 33, Type: MYSQL_TYPE_NULL}
+ }
+ return r, nil
+ }
for i, vs := range values {
if len(vs) != len(r.Fields) {
@@ -115,13 +140,23 @@ func BuildSimpleTextResultset(names []string, values [][]interface{}) (*Resultse
var row []byte
for j, value := range vs {
- if i == 0 {
- field := &Field{}
- r.Fields[j] = field
- field.Name = hack.Slice(names[j])
-
- if err = formatField(field, value); err != nil {
- return nil, errors.Trace(err)
+ typ, err := fieldType(value)
+ if err != nil {
+ return nil, errors.Trace(err)
+ }
+ if r.Fields[j] == nil {
+ r.Fields[j] = &Field{Name: hack.Slice(names[j]), Type: typ}
+ formatField(r.Fields[j], value)
+ } else if typ != r.Fields[j].Type {
+ // we got another type in the same column. in general, we treat it as an error, except
+ // the case, when old value was null, and the new one isn't null, so we can update
+ // type info for fields.
+ oldIsNull, newIsNull := r.Fields[j].Type == MYSQL_TYPE_NULL, typ == MYSQL_TYPE_NULL
+ if oldIsNull && !newIsNull { // old is null, new isn't, update type info.
+ r.Fields[j].Type = typ
+ formatField(r.Fields[j], value)
+ } else if !oldIsNull && !newIsNull { // different non-null types, that's an error.
+ return nil, errors.Errorf("row types aren't consistent")
}
}
b, err = formatTextValue(value)
@@ -130,7 +165,12 @@ func BuildSimpleTextResultset(names []string, values [][]interface{}) (*Resultse
return nil, errors.Trace(err)
}
- row = append(row, PutLengthEncodedString(b)...)
+ if b == nil {
+ // NULL value is encoded as 0xfb here (without additional info about length)
+ row = append(row, 0xfb)
+ } else {
+ row = append(row, PutLengthEncodedString(b)...)
+ }
}
r.RowDatas = append(r.RowDatas, row)
@@ -145,7 +185,6 @@ func BuildSimpleBinaryResultset(names []string, values [][]interface{}) (*Result
r.Fields = make([]*Field, len(names))
var b []byte
- var err error
bitmapLen := ((len(names) + 7 + 2) >> 3)
@@ -161,8 +200,12 @@ func BuildSimpleBinaryResultset(names []string, values [][]interface{}) (*Result
row = append(row, nullBitmap...)
for j, value := range vs {
+ typ, err := fieldType(value)
+ if err != nil {
+ return nil, errors.Trace(err)
+ }
if i == 0 {
- field := &Field{}
+ field := &Field{Type: typ}
r.Fields[j] = field
field.Name = hack.Slice(names[j])
diff --git a/vendor/github.com/siddontang/go-mysql/mysql/util.go b/vendor/github.com/siddontang/go-mysql/mysql/util.go
index 7fe41fa..757910e 100644
--- a/vendor/github.com/siddontang/go-mysql/mysql/util.go
+++ b/vendor/github.com/siddontang/go-mysql/mysql/util.go
@@ -11,6 +11,8 @@ import (
"github.com/juju/errors"
"github.com/siddontang/go/hack"
+ "crypto/sha256"
+ "crypto/rsa"
)
func Pstack() string {
@@ -48,6 +50,62 @@ func CalcPassword(scramble, password []byte) []byte {
return scramble
}
+// Hash password using MySQL 8+ method (SHA256)
+func CalcCachingSha2Password(scramble []byte, password string) []byte {
+ if len(password) == 0 {
+ return nil
+ }
+
+ // XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble))
+
+ crypt := sha256.New()
+ crypt.Write([]byte(password))
+ message1 := crypt.Sum(nil)
+
+ crypt.Reset()
+ crypt.Write(message1)
+ message1Hash := crypt.Sum(nil)
+
+ crypt.Reset()
+ crypt.Write(message1Hash)
+ crypt.Write(scramble)
+ message2 := crypt.Sum(nil)
+
+ for i := range message1 {
+ message1[i] ^= message2[i]
+ }
+
+ return message1
+}
+
+
+func EncryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte, error) {
+ plain := make([]byte, len(password)+1)
+ copy(plain, password)
+ for i := range plain {
+ j := i % len(seed)
+ plain[i] ^= seed[j]
+ }
+ sha1v := sha1.New()
+ return rsa.EncryptOAEP(sha1v, rand.Reader, pub, plain, nil)
+}
+
+// encodes a uint64 value and appends it to the given bytes slice
+func AppendLengthEncodedInteger(b []byte, n uint64) []byte {
+ switch {
+ case n <= 250:
+ return append(b, byte(n))
+
+ case n <= 0xffff:
+ return append(b, 0xfc, byte(n), byte(n>>8))
+
+ case n <= 0xffffff:
+ return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16))
+ }
+ return append(b, 0xfe, byte(n), byte(n>>8), byte(n>>16), byte(n>>24),
+ byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56))
+}
+
func RandomBuf(size int) ([]byte, error) {
buf := make([]byte, size)
@@ -84,39 +142,33 @@ func BFixedLengthInt(buf []byte) uint64 {
}
func LengthEncodedInt(b []byte) (num uint64, isNull bool, n int) {
- switch b[0] {
+ if len(b) == 0 {
+ return 0, true, 1
+ }
+ switch b[0] {
// 251: NULL
case 0xfb:
- n = 1
- isNull = true
- return
+ return 0, true, 1
- // 252: value of following 2
+ // 252: value of following 2
case 0xfc:
- num = uint64(b[1]) | uint64(b[2])<<8
- n = 3
- return
+ return uint64(b[1]) | uint64(b[2])<<8, false, 3
- // 253: value of following 3
+ // 253: value of following 3
case 0xfd:
- num = uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16
- n = 4
- return
+ return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4
- // 254: value of following 8
+ // 254: value of following 8
case 0xfe:
- num = uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 |
+ return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 |
uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 |
- uint64(b[7])<<48 | uint64(b[8])<<56
- n = 9
- return
+ uint64(b[7])<<48 | uint64(b[8])<<56,
+ false, 9
}
// 0-250: value of first byte
- num = uint64(b[0])
- n = 1
- return
+ return uint64(b[0]), false, 1
}
func PutLengthEncodedInt(n uint64) []byte {
@@ -137,23 +189,26 @@ func PutLengthEncodedInt(n uint64) []byte {
return nil
}
-func LengthEnodedString(b []byte) ([]byte, bool, int, error) {
+// returns the string read as a bytes slice, whether the value is NULL,
+// the number of bytes read and an error, in case the string is longer than
+// the input slice
+func LengthEncodedString(b []byte) ([]byte, bool, int, error) {
// Get length
num, isNull, n := LengthEncodedInt(b)
if num < 1 {
- return nil, isNull, n, nil
+ return b[n:n], isNull, n, nil
}
n += int(num)
// Check data length
if len(b) >= n {
- return b[n-int(num) : n], false, n, nil
+ return b[n-int(num) : n : n], false, n, nil
}
return nil, false, n, io.EOF
}
-func SkipLengthEnodedString(b []byte) (int, error) {
+func SkipLengthEncodedString(b []byte) (int, error) {
// Get length
num, _, n := LengthEncodedInt(b)
if num < 1 {
diff --git a/vendor/github.com/siddontang/go-mysql/packet/conn.go b/vendor/github.com/siddontang/go-mysql/packet/conn.go
index 3772e1a..41b1bf1 100644
--- a/vendor/github.com/siddontang/go-mysql/packet/conn.go
+++ b/vendor/github.com/siddontang/go-mysql/packet/conn.go
@@ -1,11 +1,17 @@
package packet
+import "C"
import (
- "bufio"
"bytes"
"io"
"net"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha1"
+ "crypto/x509"
+ "encoding/pem"
+
"github.com/juju/errors"
. "github.com/siddontang/go-mysql/mysql"
)
@@ -15,7 +21,9 @@ import (
*/
type Conn struct {
net.Conn
- br *bufio.Reader
+
+ // we removed the buffer reader because it will cause the SSLRequest to block (tls connection handshake won't be
+ // able to read the "Client Hello" data since it has been buffered into the buffer reader)
Sequence uint8
}
@@ -23,7 +31,6 @@ type Conn struct {
func NewConn(conn net.Conn) *Conn {
c := new(Conn)
- c.br = bufio.NewReaderSize(conn, 4096)
c.Conn = conn
return c
@@ -37,55 +44,20 @@ func (c *Conn) ReadPacket() ([]byte, error) {
} else {
return buf.Bytes(), nil
}
-
- // header := []byte{0, 0, 0, 0}
-
- // if _, err := io.ReadFull(c.br, header); err != nil {
- // return nil, ErrBadConn
- // }
-
- // length := int(uint32(header[0]) | uint32(header[1])<<8 | uint32(header[2])<<16)
- // if length < 1 {
- // return nil, fmt.Errorf("invalid payload length %d", length)
- // }
-
- // sequence := uint8(header[3])
-
- // if sequence != c.Sequence {
- // return nil, fmt.Errorf("invalid sequence %d != %d", sequence, c.Sequence)
- // }
-
- // c.Sequence++
-
- // data := make([]byte, length)
- // if _, err := io.ReadFull(c.br, data); err != nil {
- // return nil, ErrBadConn
- // } else {
- // if length < MaxPayloadLen {
- // return data, nil
- // }
-
- // var buf []byte
- // buf, err = c.ReadPacket()
- // if err != nil {
- // return nil, ErrBadConn
- // } else {
- // return append(data, buf...), nil
- // }
- // }
}
func (c *Conn) ReadPacketTo(w io.Writer) error {
header := []byte{0, 0, 0, 0}
- if _, err := io.ReadFull(c.br, header); err != nil {
+ if _, err := io.ReadFull(c.Conn, header); err != nil {
return ErrBadConn
}
length := int(uint32(header[0]) | uint32(header[1])<<8 | uint32(header[2])<<16)
- if length < 1 {
- return errors.Errorf("invalid payload length %d", length)
- }
+ // bug fixed: caching_sha2_password will send 0-length payload (the unscrambled password) when the password is empty
+ //if length < 1 {
+ // return errors.Errorf("invalid payload length %d", length)
+ //}
sequence := uint8(header[3])
@@ -95,7 +67,7 @@ func (c *Conn) ReadPacketTo(w io.Writer) error {
c.Sequence++
- if n, err := io.CopyN(w, c.br, int64(length)); err != nil {
+ if n, err := io.CopyN(w, c.Conn, int64(length)); err != nil {
return ErrBadConn
} else if n != int64(length) {
return ErrBadConn
@@ -150,6 +122,77 @@ func (c *Conn) WritePacket(data []byte) error {
}
}
+// Client clear text authentication packet
+// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
+func (c *Conn) WriteClearAuthPacket(password string) error {
+ // Calculate the packet length and add a tailing 0
+ pktLen := len(password) + 1
+ data := make([]byte, 4 + pktLen)
+
+ // Add the clear password [null terminated string]
+ copy(data[4:], password)
+ data[4+pktLen-1] = 0x00
+
+ return c.WritePacket(data)
+}
+
+// Caching sha2 authentication. Public key request and send encrypted password
+// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
+func (c *Conn) WritePublicKeyAuthPacket(password string, cipher []byte) error {
+ // request public key
+ data := make([]byte, 4 + 1)
+ data[4] = 2 // cachingSha2PasswordRequestPublicKey
+ c.WritePacket(data)
+
+ data, err := c.ReadPacket()
+ if err != nil {
+ return err
+ }
+
+ block, _ := pem.Decode(data[1:])
+ pub, err := x509.ParsePKIXPublicKey(block.Bytes)
+ if err != nil {
+ return err
+ }
+
+ plain := make([]byte, len(password)+1)
+ copy(plain, password)
+ for i := range plain {
+ j := i % len(cipher)
+ plain[i] ^= cipher[j]
+ }
+ sha1v := sha1.New()
+ enc, _ := rsa.EncryptOAEP(sha1v, rand.Reader, pub.(*rsa.PublicKey), plain, nil)
+ data = make([]byte, 4 + len(enc))
+ copy(data[4:], enc)
+ return c.WritePacket(data)
+}
+
+func (c *Conn) WriteEncryptedPassword(password string, seed []byte, pub *rsa.PublicKey) error {
+ enc, err := EncryptPassword(password, seed, pub)
+ if err != nil {
+ return err
+ }
+ return c.WriteAuthSwitchPacket(enc, false)
+}
+
+// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
+func (c *Conn) WriteAuthSwitchPacket(authData []byte, addNUL bool) error {
+ pktLen := 4 + len(authData)
+ if addNUL {
+ pktLen++
+ }
+ data := make([]byte, pktLen)
+
+ // Add the auth data [EOF]
+ copy(data[4:], authData)
+ if addNUL {
+ data[pktLen-1] = 0x00
+ }
+
+ return c.WritePacket(data)
+}
+
func (c *Conn) ResetSequence() {
c.Sequence = 0
}
diff --git a/vendor/github.com/siddontang/go-mysql/replication/backup.go b/vendor/github.com/siddontang/go-mysql/replication/backup.go
index 744c38c..24a25ae 100644
--- a/vendor/github.com/siddontang/go-mysql/replication/backup.go
+++ b/vendor/github.com/siddontang/go-mysql/replication/backup.go
@@ -1,13 +1,12 @@
package replication
import (
+ "context"
"io"
"os"
"path"
"time"
- "golang.org/x/net/context"
-
"github.com/juju/errors"
. "github.com/siddontang/go-mysql/mysql"
)
@@ -41,7 +40,7 @@ func (b *BinlogSyncer) StartBackup(backupDir string, p Position, timeout time.Du
}()
for {
- ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
+ ctx, cancel := context.WithTimeout(context.Background(), timeout)
e, err := s.GetEvent(ctx)
cancel()
diff --git a/vendor/github.com/siddontang/go-mysql/replication/backup_test.go b/vendor/github.com/siddontang/go-mysql/replication/backup_test.go
new file mode 100644
index 0000000..5e39e7f
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/replication/backup_test.go
@@ -0,0 +1,50 @@
+package replication
+
+import (
+ "context"
+ "github.com/juju/errors"
+ . "github.com/pingcap/check"
+ "github.com/siddontang/go-mysql/mysql"
+ "os"
+ "sync"
+ "time"
+)
+
+func (t *testSyncerSuite) TestStartBackupEndInGivenTime(c *C) {
+ t.setupTest(c, mysql.MySQLFlavor)
+
+ t.testExecute(c, "RESET MASTER")
+
+ var wg sync.WaitGroup
+ wg.Add(1)
+ defer wg.Wait()
+
+ go func() {
+ defer wg.Done()
+
+ t.testSync(c, nil)
+
+ t.testExecute(c, "FLUSH LOGS")
+
+ t.testSync(c, nil)
+ }()
+
+ os.RemoveAll("./var")
+ timeout := 2 * time.Second
+
+ done := make(chan bool)
+
+ go func() {
+ err := t.b.StartBackup("./var", mysql.Position{Name: "", Pos: uint32(0)}, timeout)
+ c.Assert(err, IsNil)
+ done <- true
+ }()
+ failTimeout := 5 * timeout
+ ctx, _ := context.WithTimeout(context.Background(), failTimeout)
+ select {
+ case <-done:
+ return
+ case <-ctx.Done():
+ c.Assert(errors.New("time out error"), IsNil)
+ }
+}
diff --git a/vendor/github.com/siddontang/go-mysql/replication/binlogstreamer.go b/vendor/github.com/siddontang/go-mysql/replication/binlogstreamer.go
index e5b165c..c1e4057 100644
--- a/vendor/github.com/siddontang/go-mysql/replication/binlogstreamer.go
+++ b/vendor/github.com/siddontang/go-mysql/replication/binlogstreamer.go
@@ -1,10 +1,10 @@
package replication
import (
- "golang.org/x/net/context"
-
+ "context"
+ "time"
"github.com/juju/errors"
- "github.com/ngaut/log"
+ "github.com/siddontang/go-log/log"
)
var (
@@ -36,6 +36,36 @@ func (s *BinlogStreamer) GetEvent(ctx context.Context) (*BinlogEvent, error) {
}
}
+// Get the binlog event with starttime, if current binlog event timestamp smaller than specify starttime
+// return nil event
+func (s *BinlogStreamer) GetEventWithStartTime(ctx context.Context,startTime time.Time) (*BinlogEvent, error) {
+ if s.err != nil {
+ return nil, ErrNeedSyncAgain
+ }
+ startUnix := startTime.Unix()
+ select {
+ case c := <-s.ch:
+ if int64(c.Header.Timestamp) >= startUnix {
+ return c, nil
+ }
+ return nil,nil
+ case s.err = <-s.ech:
+ return nil, s.err
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ }
+}
+
+// DumpEvents dumps all left events
+func (s *BinlogStreamer) DumpEvents() []*BinlogEvent {
+ count := len(s.ch)
+ events := make([]*BinlogEvent, 0, count)
+ for i := 0; i < count; i++ {
+ events = append(events, <-s.ch)
+ }
+ return events
+}
+
func (s *BinlogStreamer) close() {
s.closeWithError(ErrSyncClosed)
}
diff --git a/vendor/github.com/siddontang/go-mysql/replication/binlogsyncer.go b/vendor/github.com/siddontang/go-mysql/replication/binlogsyncer.go
index 2a72c6d..552798b 100644
--- a/vendor/github.com/siddontang/go-mysql/replication/binlogsyncer.go
+++ b/vendor/github.com/siddontang/go-mysql/replication/binlogsyncer.go
@@ -1,17 +1,19 @@
package replication
import (
+ "context"
"crypto/tls"
"encoding/binary"
"fmt"
+ "net"
"os"
+ "strings"
"sync"
"time"
- "golang.org/x/net/context"
-
"github.com/juju/errors"
- "github.com/ngaut/log"
+ "github.com/satori/go.uuid"
+ "github.com/siddontang/go-log/log"
"github.com/siddontang/go-mysql/client"
. "github.com/siddontang/go-mysql/mysql"
)
@@ -40,6 +42,9 @@ type BinlogSyncerConfig struct {
// If not set, use os.Hostname() instead.
Localhost string
+ // Charset is for MySQL client character set
+ Charset string
+
// SemiSyncEnabled enables semi-sync or not.
SemiSyncEnabled bool
@@ -48,6 +53,46 @@ type BinlogSyncerConfig struct {
// If not nil, use the provided tls.Config to connect to the database using TLS/SSL.
TLSConfig *tls.Config
+
+ // Use replication.Time structure for timestamp and datetime.
+ // We will use Local location for timestamp and UTC location for datatime.
+ ParseTime bool
+
+ // If ParseTime is false, convert TIMESTAMP into this specified timezone. If
+ // ParseTime is true, this option will have no effect and TIMESTAMP data will
+ // be parsed into the local timezone and a full time.Time struct will be
+ // returned.
+ //
+ // Note that MySQL TIMESTAMP columns are offset from the machine local
+ // timezone while DATETIME columns are offset from UTC. This is consistent
+ // with documented MySQL behaviour as it return TIMESTAMP in local timezone
+ // and DATETIME in UTC.
+ //
+ // Setting this to UTC effectively equalizes the TIMESTAMP and DATETIME time
+ // strings obtained from MySQL.
+ TimestampStringLocation *time.Location
+
+ // Use decimal.Decimal structure for decimals.
+ UseDecimal bool
+
+ // RecvBufferSize sets the size in bytes of the operating system's receive buffer associated with the connection.
+ RecvBufferSize int
+
+ // master heartbeat period
+ HeartbeatPeriod time.Duration
+
+ // read timeout
+ ReadTimeout time.Duration
+
+ // maximum number of attempts to re-establish a broken connection
+ MaxReconnectAttempts int
+
+ // Only works when MySQL/MariaDB variable binlog_checksum=CRC32.
+ // For MySQL, binlog_checksum was introduced since 5.6.2, but CRC32 was set as default value since 5.6.6 .
+ // https://dev.mysql.com/doc/refman/5.6/en/replication-options-binary-log.html#option_mysqld_binlog-checksum
+ // For MariaDB, binlog_checksum was introduced since MariaDB 5.3, but CRC32 was set as default value since MariaDB 10.2.1 .
+ // https://mariadb.com/kb/en/library/replication-and-binary-log-server-system-variables/#binlog_checksum
+ VerifyChecksum bool
}
// BinlogSyncer syncs binlog event from server.
@@ -64,14 +109,23 @@ type BinlogSyncer struct {
nextPos Position
+ gset GTIDSet
+
running bool
ctx context.Context
cancel context.CancelFunc
+
+ lastConnectionID uint32
+
+ retryCount int
}
// NewBinlogSyncer creates the BinlogSyncer with cfg.
func NewBinlogSyncer(cfg BinlogSyncerConfig) *BinlogSyncer {
+ if cfg.ServerID == 0 {
+ log.Fatal("can't use 0 as the server ID")
+ }
// Clear the Password to avoid outputing it in log.
pass := cfg.Password
@@ -84,7 +138,10 @@ func NewBinlogSyncer(cfg BinlogSyncerConfig) *BinlogSyncer {
b.cfg = cfg
b.parser = NewBinlogParser()
b.parser.SetRawMode(b.cfg.RawModeEnabled)
-
+ b.parser.SetParseTime(b.cfg.ParseTime)
+ b.parser.SetTimestampStringLocation(b.cfg.TimestampStringLocation)
+ b.parser.SetUseDecimal(b.cfg.UseDecimal)
+ b.parser.SetVerifyChecksum(b.cfg.VerifyChecksum)
b.running = false
b.ctx, b.cancel = context.WithCancel(context.Background())
@@ -136,15 +193,53 @@ func (b *BinlogSyncer) registerSlave() error {
b.c.Close()
}
- log.Infof("register slave for master server %s:%d", b.cfg.Host, b.cfg.Port)
+ addr := ""
+ if strings.Contains(b.cfg.Host, "/") {
+ addr = b.cfg.Host
+ } else {
+ addr = fmt.Sprintf("%s:%d", b.cfg.Host, b.cfg.Port)
+ }
+
+ log.Infof("register slave for master server %s", addr)
var err error
- b.c, err = client.Connect(fmt.Sprintf("%s:%d", b.cfg.Host, b.cfg.Port), b.cfg.User, b.cfg.Password, "", func(c *client.Conn) {
- c.TLSConfig = b.cfg.TLSConfig
+ b.c, err = client.Connect(addr, b.cfg.User, b.cfg.Password, "", func(c *client.Conn) {
+ c.SetTLSConfig(b.cfg.TLSConfig)
})
if err != nil {
return errors.Trace(err)
}
+ if len(b.cfg.Charset) != 0 {
+ b.c.SetCharset(b.cfg.Charset)
+ }
+
+ //set read timeout
+ if b.cfg.ReadTimeout > 0 {
+ b.c.SetReadDeadline(time.Now().Add(b.cfg.ReadTimeout))
+ }
+
+ if b.cfg.RecvBufferSize > 0 {
+ if tcp, ok := b.c.Conn.Conn.(*net.TCPConn); ok {
+ tcp.SetReadBuffer(b.cfg.RecvBufferSize)
+ }
+ }
+
+ // kill last connection id
+ if b.lastConnectionID > 0 {
+ cmd := fmt.Sprintf("KILL %d", b.lastConnectionID)
+ if _, err := b.c.Execute(cmd); err != nil {
+ log.Errorf("kill connection %d error %v", b.lastConnectionID, err)
+ // Unknown thread id
+ if code := ErrorCode(err.Error()); code != ER_NO_SUCH_THREAD {
+ return errors.Trace(err)
+ }
+ }
+ log.Infof("kill last connection id %d", b.lastConnectionID)
+ }
+
+ // save last last connection id for kill
+ b.lastConnectionID = b.c.GetConnectionID()
+
//for mysql 5.6+, binlog has a crc32 checksum
//before mysql 5.6, this will not work, don't matter.:-)
if r, err := b.c.Execute("SHOW GLOBAL VARIABLES LIKE 'BINLOG_CHECKSUM'"); err != nil {
@@ -180,6 +275,14 @@ func (b *BinlogSyncer) registerSlave() error {
}
}
+ if b.cfg.HeartbeatPeriod > 0 {
+ _, err = b.c.Execute(fmt.Sprintf("SET @master_heartbeat_period=%d;", b.cfg.HeartbeatPeriod))
+ if err != nil {
+ log.Errorf("failed to set @master_heartbeat_period=%d, err: %v", b.cfg.HeartbeatPeriod, err)
+ return errors.Trace(err)
+ }
+ }
+
if err = b.writeRegisterSlaveCommand(); err != nil {
return errors.Trace(err)
}
@@ -191,7 +294,7 @@ func (b *BinlogSyncer) registerSlave() error {
return nil
}
-func (b *BinlogSyncer) enalbeSemiSync() error {
+func (b *BinlogSyncer) enableSemiSync() error {
if !b.cfg.SemiSyncEnabled {
return nil
}
@@ -224,7 +327,7 @@ func (b *BinlogSyncer) prepare() error {
return errors.Trace(err)
}
- if err := b.enalbeSemiSync(); err != nil {
+ if err := b.enableSemiSync(); err != nil {
return errors.Trace(err)
}
@@ -241,6 +344,11 @@ func (b *BinlogSyncer) startDumpStream() *BinlogStreamer {
return s
}
+// GetNextPosition returns the next position of the syncer
+func (b *BinlogSyncer) GetNextPosition() Position {
+ return b.nextPos
+}
+
// StartSync starts syncing from the `pos` position.
func (b *BinlogSyncer) StartSync(pos Position) (*BinlogStreamer, error) {
log.Infof("begin to sync binlog from position %s", pos)
@@ -261,7 +369,9 @@ func (b *BinlogSyncer) StartSync(pos Position) (*BinlogStreamer, error) {
// StartSyncGTID starts syncing from the `gset` GTIDSet.
func (b *BinlogSyncer) StartSyncGTID(gset GTIDSet) (*BinlogStreamer, error) {
- log.Infof("begin to sync binlog from GTID %s", gset)
+ log.Infof("begin to sync binlog from GTID set %s", gset)
+
+ b.gset = gset
b.m.Lock()
defer b.m.Unlock()
@@ -275,11 +385,12 @@ func (b *BinlogSyncer) StartSyncGTID(gset GTIDSet) (*BinlogStreamer, error) {
}
var err error
- if b.cfg.Flavor != MariaDBFlavor {
+ switch b.cfg.Flavor {
+ case MariaDBFlavor:
+ err = b.writeBinlogDumpMariadbGTIDCommand(gset)
+ default:
// default use MySQL
err = b.writeBinlogDumpMysqlGTIDCommand(gset)
- } else {
- err = b.writeBinlogDumpMariadbGTIDCommand(gset)
}
if err != nil {
@@ -289,7 +400,7 @@ func (b *BinlogSyncer) StartSyncGTID(gset GTIDSet) (*BinlogStreamer, error) {
return b.startDumpStream(), nil
}
-func (b *BinlogSyncer) writeBinglogDumpCommand(p Position) error {
+func (b *BinlogSyncer) writeBinlogDumpCommand(p Position) error {
b.c.ResetSequence()
data := make([]byte, 4+1+4+2+4+len(p.Name))
@@ -313,7 +424,7 @@ func (b *BinlogSyncer) writeBinglogDumpCommand(p Position) error {
}
func (b *BinlogSyncer) writeBinlogDumpMysqlGTIDCommand(gset GTIDSet) error {
- p := Position{"", 4}
+ p := Position{Name: "", Pos: 4}
gtidData := gset.Encode()
b.c.ResetSequence()
@@ -369,7 +480,7 @@ func (b *BinlogSyncer) writeBinlogDumpMariadbGTIDCommand(gset GTIDSet) error {
}
// Since we use @slave_connect_state, the file and position here are ignored.
- return b.writeBinglogDumpCommand(Position{"", 0})
+ return b.writeBinlogDumpCommand(Position{Name: "", Pos: 0})
}
// localHostname returns the hostname that register slave would register as.
@@ -444,21 +555,25 @@ func (b *BinlogSyncer) replySemiSyncACK(p Position) error {
return errors.Trace(err)
}
- _, err = b.c.ReadOKPacket()
- if err != nil {
- }
- return errors.Trace(err)
+ return nil
}
func (b *BinlogSyncer) retrySync() error {
b.m.Lock()
defer b.m.Unlock()
- log.Infof("begin to re-sync from %s", b.nextPos)
-
b.parser.Reset()
- if err := b.prepareSyncPos(b.nextPos); err != nil {
- return errors.Trace(err)
+
+ if b.gset != nil {
+ log.Infof("begin to re-sync from %s", b.gset.String())
+ if err := b.prepareSyncGTID(b.gset); err != nil {
+ return errors.Trace(err)
+ }
+ } else {
+ log.Infof("begin to re-sync from %s", b.nextPos)
+ if err := b.prepareSyncPos(b.nextPos); err != nil {
+ return errors.Trace(err)
+ }
}
return nil
@@ -474,13 +589,34 @@ func (b *BinlogSyncer) prepareSyncPos(pos Position) error {
return errors.Trace(err)
}
- if err := b.writeBinglogDumpCommand(pos); err != nil {
+ if err := b.writeBinlogDumpCommand(pos); err != nil {
return errors.Trace(err)
}
return nil
}
+func (b *BinlogSyncer) prepareSyncGTID(gset GTIDSet) error {
+ var err error
+
+ if err = b.prepare(); err != nil {
+ return errors.Trace(err)
+ }
+
+ switch b.cfg.Flavor {
+ case MariaDBFlavor:
+ err = b.writeBinlogDumpMariadbGTIDCommand(gset)
+ default:
+ // default use MySQL
+ err = b.writeBinlogDumpMysqlGTIDCommand(gset)
+ }
+
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
func (b *BinlogSyncer) onStream(s *BinlogStreamer) {
defer func() {
if e := recover(); e != nil {
@@ -495,21 +631,27 @@ func (b *BinlogSyncer) onStream(s *BinlogStreamer) {
log.Error(err)
// we meet connection error, should re-connect again with
- // last nextPos we got.
- if len(b.nextPos.Name) == 0 {
+ // last nextPos or nextGTID we got.
+ if len(b.nextPos.Name) == 0 && b.gset == nil {
// we can't get the correct position, close.
s.closeWithError(err)
return
}
- // TODO: add a max retry count.
for {
select {
case <-b.ctx.Done():
s.close()
return
case <-time.After(time.Second):
+ b.retryCount++
if err = b.retrySync(); err != nil {
+ if b.cfg.MaxReconnectAttempts > 0 && b.retryCount >= b.cfg.MaxReconnectAttempts {
+ log.Errorf("retry sync err: %v, exceeded max retries (%d)", err, b.cfg.MaxReconnectAttempts)
+ s.closeWithError(err)
+ return
+ }
+
log.Errorf("retry sync err: %v, wait 1s and retry again", err)
continue
}
@@ -522,6 +664,14 @@ func (b *BinlogSyncer) onStream(s *BinlogStreamer) {
continue
}
+ //set read timeout
+ if b.cfg.ReadTimeout > 0 {
+ b.c.SetReadDeadline(time.Now().Add(b.cfg.ReadTimeout))
+ }
+
+ // Reset retry count on successful packet receieve
+ b.retryCount = 0
+
switch data[0] {
case OK_HEADER:
if err = b.parseEvent(s, data); err != nil {
@@ -557,7 +707,7 @@ func (b *BinlogSyncer) parseEvent(s *BinlogStreamer, data []byte) error {
data = data[2:]
}
- e, err := b.parser.parse(data)
+ e, err := b.parser.Parse(data)
if err != nil {
return errors.Trace(err)
}
@@ -566,11 +716,33 @@ func (b *BinlogSyncer) parseEvent(s *BinlogStreamer, data []byte) error {
// Some events like FormatDescriptionEvent return 0, ignore.
b.nextPos.Pos = e.Header.LogPos
}
-
- if re, ok := e.Event.(*RotateEvent); ok {
- b.nextPos.Name = string(re.NextLogName)
- b.nextPos.Pos = uint32(re.Position)
+ switch event := e.Event.(type) {
+ case *RotateEvent:
+ b.nextPos.Name = string(event.NextLogName)
+ b.nextPos.Pos = uint32(event.Position)
log.Infof("rotate to %s", b.nextPos)
+ case *GTIDEvent:
+ if b.gset == nil {
+ break
+ }
+ u, _ := uuid.FromBytes(event.SID)
+ err := b.gset.Update(fmt.Sprintf("%s:%d", u.String(), event.GNO))
+ if err != nil {
+ return errors.Trace(err)
+ }
+ case *MariadbGTIDEvent:
+ if b.gset == nil {
+ break
+ }
+ GTID := event.GTID
+ err := b.gset.Update(fmt.Sprintf("%d-%d-%d", GTID.DomainID, GTID.ServerID, GTID.SequenceNumber))
+ if err != nil {
+ return errors.Trace(err)
+ }
+ case *XIDEvent:
+ event.GSet = b.getGtidSet()
+ case *QueryEvent:
+ event.GSet = b.getGtidSet()
}
needStop := false
@@ -593,3 +765,15 @@ func (b *BinlogSyncer) parseEvent(s *BinlogStreamer, data []byte) error {
return nil
}
+
+func (b *BinlogSyncer) getGtidSet() GTIDSet {
+ if b.gset == nil {
+ return nil
+ }
+ return b.gset.Clone()
+}
+
+// LastConnectionID returns last connectionID.
+func (b *BinlogSyncer) LastConnectionID() uint32 {
+ return b.lastConnectionID
+}
diff --git a/vendor/github.com/siddontang/go-mysql/replication/event.go b/vendor/github.com/siddontang/go-mysql/replication/event.go
index 1a0d2c6..737b431 100644
--- a/vendor/github.com/siddontang/go-mysql/replication/event.go
+++ b/vendor/github.com/siddontang/go-mysql/replication/event.go
@@ -2,7 +2,6 @@ package replication
import (
"encoding/binary"
- //"encoding/hex"
"fmt"
"io"
"strconv"
@@ -16,11 +15,15 @@ import (
)
const (
- EventHeaderSize = 19
+ EventHeaderSize = 19
+ SidLength = 16
+ LogicalTimestampTypeCode = 2
+ PartLogicalTimestampLength = 8
+ BinlogChecksumLength = 4
)
type BinlogEvent struct {
- // raw binlog data, including crc32 checksum if exists
+ // raw binlog data which contains all data, including binlog header and event body, and including crc32 checksum if exists
RawData []byte
Header *EventHeader
@@ -50,7 +53,7 @@ type EventError struct {
}
func (e *EventError) Error() string {
- return e.Err
+ return fmt.Sprintf("Header %#v, Data %q, Err: %v", e.Header, e.Data, e.Err)
}
type EventHeader struct {
@@ -216,6 +219,9 @@ func (e *RotateEvent) Dump(w io.Writer) {
type XIDEvent struct {
XID uint64
+
+ // in fact XIDEvent dosen't have the GTIDSet information, just for beneficial to use
+ GSet GTIDSet
}
func (e *XIDEvent) Decode(data []byte) error {
@@ -225,6 +231,9 @@ func (e *XIDEvent) Decode(data []byte) error {
func (e *XIDEvent) Dump(w io.Writer) {
fmt.Fprintf(w, "XID: %d\n", e.XID)
+ if e.GSet != nil {
+ fmt.Fprintf(w, "GTIDSet: %s\n", e.GSet.String())
+ }
fmt.Fprintln(w)
}
@@ -235,6 +244,9 @@ type QueryEvent struct {
StatusVars []byte
Schema []byte
Query []byte
+
+ // in fact QueryEvent dosen't have the GTIDSet information, just for beneficial to use
+ GSet GTIDSet
}
func (e *QueryEvent) Decode(data []byte) error {
@@ -275,21 +287,36 @@ func (e *QueryEvent) Dump(w io.Writer) {
//fmt.Fprintf(w, "Status vars: \n%s", hex.Dump(e.StatusVars))
fmt.Fprintf(w, "Schema: %s\n", e.Schema)
fmt.Fprintf(w, "Query: %s\n", e.Query)
+ if e.GSet != nil {
+ fmt.Fprintf(w, "GTIDSet: %s\n", e.GSet.String())
+ }
fmt.Fprintln(w)
}
type GTIDEvent struct {
- CommitFlag uint8
- SID []byte
- GNO int64
+ CommitFlag uint8
+ SID []byte
+ GNO int64
+ LastCommitted int64
+ SequenceNumber int64
}
func (e *GTIDEvent) Decode(data []byte) error {
- e.CommitFlag = uint8(data[0])
-
- e.SID = data[1:17]
-
- e.GNO = int64(binary.LittleEndian.Uint64(data[17:]))
+ pos := 0
+ e.CommitFlag = uint8(data[pos])
+ pos++
+ e.SID = data[pos : pos+SidLength]
+ pos += SidLength
+ e.GNO = int64(binary.LittleEndian.Uint64(data[pos:]))
+ pos += 8
+ if len(data) >= 42 {
+ if uint8(data[pos]) == LogicalTimestampTypeCode {
+ pos++
+ e.LastCommitted = int64(binary.LittleEndian.Uint64(data[pos:]))
+ pos += PartLogicalTimestampLength
+ e.SequenceNumber = int64(binary.LittleEndian.Uint64(data[pos:]))
+ }
+ }
return nil
}
@@ -297,6 +324,8 @@ func (e *GTIDEvent) Dump(w io.Writer) {
fmt.Fprintf(w, "Commit flag: %d\n", e.CommitFlag)
u, _ := uuid.FromBytes(e.SID)
fmt.Fprintf(w, "GTID_NEXT: %s:%d\n", u.String(), e.GNO)
+ fmt.Fprintf(w, "LAST_COMMITTED: %d\n", e.LastCommitted)
+ fmt.Fprintf(w, "SEQUENCE_NUMBER: %d\n", e.SequenceNumber)
fmt.Fprintln(w)
}
@@ -382,16 +411,16 @@ func (e *ExecuteLoadQueryEvent) Dump(w io.Writer) {
// case MARIADB_ANNOTATE_ROWS_EVENT:
// return "MariadbAnnotateRowsEvent"
-type MariadbAnnotaeRowsEvent struct {
+type MariadbAnnotateRowsEvent struct {
Query []byte
}
-func (e *MariadbAnnotaeRowsEvent) Decode(data []byte) error {
+func (e *MariadbAnnotateRowsEvent) Decode(data []byte) error {
e.Query = data
return nil
}
-func (e *MariadbAnnotaeRowsEvent) Dump(w io.Writer) {
+func (e *MariadbAnnotateRowsEvent) Dump(w io.Writer) {
fmt.Fprintf(w, "Query: %s\n", e.Query)
fmt.Fprintln(w)
}
@@ -424,7 +453,7 @@ func (e *MariadbGTIDEvent) Decode(data []byte) error {
}
func (e *MariadbGTIDEvent) Dump(w io.Writer) {
- fmt.Fprintf(w, "GTID: %s\n", e.GTID)
+ fmt.Fprintf(w, "GTID: %v\n", e.GTID)
fmt.Fprintln(w)
}
diff --git a/vendor/github.com/siddontang/go-mysql/replication/json_binary.go b/vendor/github.com/siddontang/go-mysql/replication/json_binary.go
index 24a9b27..6529f01 100644
--- a/vendor/github.com/siddontang/go-mysql/replication/json_binary.go
+++ b/vendor/github.com/siddontang/go-mysql/replication/json_binary.go
@@ -70,8 +70,13 @@ func jsonbGetValueEntrySize(isSmall bool) int {
// decodeJsonBinary decodes the JSON binary encoding data and returns
// the common JSON encoding data.
-func decodeJsonBinary(data []byte) ([]byte, error) {
- d := new(jsonBinaryDecoder)
+func (e *RowsEvent) decodeJsonBinary(data []byte) ([]byte, error) {
+ // Sometimes, we can insert a NULL JSON even we set the JSON field as NOT NULL.
+ // If we meet this case, we can return an empty slice.
+ if len(data) == 0 {
+ return []byte{}, nil
+ }
+ d := jsonBinaryDecoder{useDecimal: e.useDecimal}
if d.isDataShort(data, 1) {
return nil, d.err
@@ -86,7 +91,8 @@ func decodeJsonBinary(data []byte) ([]byte, error) {
}
type jsonBinaryDecoder struct {
- err error
+ useDecimal bool
+ err error
}
func (d *jsonBinaryDecoder) decodeValue(tp byte, data []byte) interface{} {
@@ -382,7 +388,7 @@ func (d *jsonBinaryDecoder) decodeDecimal(data []byte) interface{} {
precision := int(data[0])
scale := int(data[1])
- v, _, err := decodeDecimal(data[2:], precision, scale)
+ v, _, err := decodeDecimal(data[2:], precision, scale, d.useDecimal)
d.err = err
return v
@@ -463,7 +469,7 @@ func (d *jsonBinaryDecoder) decodeVariableLength(data []byte) (int, int) {
if v&0x80 == 0 {
if length > math.MaxUint32 {
- d.err = errors.Errorf("variable length %d must <= %d", length, math.MaxUint32)
+ d.err = errors.Errorf("variable length %d must <= %d", length, int64(math.MaxUint32))
return 0, 0
}
diff --git a/vendor/github.com/siddontang/go-mysql/replication/parser.go b/vendor/github.com/siddontang/go-mysql/replication/parser.go
index cfc97e4..6fe1cc0 100644
--- a/vendor/github.com/siddontang/go-mysql/replication/parser.go
+++ b/vendor/github.com/siddontang/go-mysql/replication/parser.go
@@ -2,13 +2,22 @@ package replication
import (
"bytes"
+ "encoding/binary"
"fmt"
+ "hash/crc32"
"io"
"os"
+ "sync/atomic"
+ "time"
"github.com/juju/errors"
)
+var (
+ // ErrChecksumMismatch indicates binlog checksum mismatch.
+ ErrChecksumMismatch = errors.New("binlog checksum mismatch, data may be corrupted")
+)
+
type BinlogParser struct {
format *FormatDescriptionEvent
@@ -16,6 +25,15 @@ type BinlogParser struct {
// for rawMode, we only parse FormatDescriptionEvent and RotateEvent
rawMode bool
+
+ parseTime bool
+ timestampStringLocation *time.Location
+
+ // used to start/stop processing
+ stopProcessing uint32
+
+ useDecimal bool
+ verifyChecksum bool
}
func NewBinlogParser() *BinlogParser {
@@ -26,6 +44,14 @@ func NewBinlogParser() *BinlogParser {
return p
}
+func (p *BinlogParser) Stop() {
+ atomic.StoreUint32(&p.stopProcessing, 1)
+}
+
+func (p *BinlogParser) Resume() {
+ atomic.StoreUint32(&p.stopProcessing, 0)
+}
+
func (p *BinlogParser) Reset() {
p.format = nil
}
@@ -48,64 +74,102 @@ func (p *BinlogParser) ParseFile(name string, offset int64, onEvent OnEventFunc)
if offset < 4 {
offset = 4
+ } else if offset > 4 {
+ // FORMAT_DESCRIPTION event should be read by default always (despite that fact passed offset may be higher than 4)
+ if _, err = f.Seek(4, os.SEEK_SET); err != nil {
+ return errors.Errorf("seek %s to %d error %v", name, offset, err)
+ }
+
+ if err = p.parseFormatDescriptionEvent(f, onEvent); err != nil {
+ return errors.Annotatef(err, "parse FormatDescriptionEvent")
+ }
}
if _, err = f.Seek(offset, os.SEEK_SET); err != nil {
return errors.Errorf("seek %s to %d error %v", name, offset, err)
}
- return p.parseReader(f, onEvent)
+ return p.ParseReader(f, onEvent)
}
-func (p *BinlogParser) parseReader(r io.Reader, onEvent OnEventFunc) error {
- p.Reset()
+func (p *BinlogParser) parseFormatDescriptionEvent(r io.Reader, onEvent OnEventFunc) error {
+ _, err := p.parseSingleEvent(r, onEvent)
+ return err
+}
+// ParseSingleEvent parses single binlog event and passes the event to onEvent function.
+func (p *BinlogParser) ParseSingleEvent(r io.Reader, onEvent OnEventFunc) (bool, error) {
+ return p.parseSingleEvent(r, onEvent)
+}
+
+func (p *BinlogParser) parseSingleEvent(r io.Reader, onEvent OnEventFunc) (bool, error) {
var err error
var n int64
+ var buf bytes.Buffer
+ if n, err = io.CopyN(&buf, r, EventHeaderSize); err == io.EOF {
+ return true, nil
+ } else if err != nil {
+ return false, errors.Errorf("get event header err %v, need %d but got %d", err, EventHeaderSize, n)
+ }
+
+ var h *EventHeader
+ h, err = p.parseHeader(buf.Bytes())
+ if err != nil {
+ return false, errors.Trace(err)
+ }
+
+ if h.EventSize <= uint32(EventHeaderSize) {
+ return false, errors.Errorf("invalid event header, event size is %d, too small", h.EventSize)
+ }
+ if n, err = io.CopyN(&buf, r, int64(h.EventSize-EventHeaderSize)); err != nil {
+ return false, errors.Errorf("get event err %v, need %d but got %d", err, h.EventSize, n)
+ }
+ if buf.Len() != int(h.EventSize) {
+ return false, errors.Errorf("invalid raw data size in event %s, need %d but got %d", h.EventType, h.EventSize, buf.Len())
+ }
+
+ rawData := buf.Bytes()
+ bodyLen := int(h.EventSize) - EventHeaderSize
+ body := rawData[EventHeaderSize:]
+ if len(body) != bodyLen {
+ return false, errors.Errorf("invalid body data size in event %s, need %d but got %d", h.EventType, bodyLen, len(body))
+ }
+
+ var e Event
+ e, err = p.parseEvent(h, body, rawData)
+ if err != nil {
+ if err == errMissingTableMapEvent {
+ return false, nil
+ }
+ return false, errors.Trace(err)
+ }
+
+ if err = onEvent(&BinlogEvent{RawData: rawData, Header: h, Event: e}); err != nil {
+ return false, errors.Trace(err)
+ }
+
+ return false, nil
+}
+
+func (p *BinlogParser) ParseReader(r io.Reader, onEvent OnEventFunc) error {
+
for {
- headBuf := make([]byte, EventHeaderSize)
-
- if _, err = io.ReadFull(r, headBuf); err == io.EOF {
- return nil
- } else if err != nil {
- return errors.Trace(err)
- }
-
- var h *EventHeader
- h, err = p.parseHeader(headBuf)
- if err != nil {
- return errors.Trace(err)
- }
-
- if h.EventSize <= uint32(EventHeaderSize) {
- return errors.Errorf("invalid event header, event size is %d, too small", h.EventSize)
-
- }
-
- var buf bytes.Buffer
- if n, err = io.CopyN(&buf, r, int64(h.EventSize)-int64(EventHeaderSize)); err != nil {
- return errors.Errorf("get event body err %v, need %d - %d, but got %d", err, h.EventSize, EventHeaderSize, n)
- }
-
- data := buf.Bytes()
- rawData := data
-
- eventLen := int(h.EventSize) - EventHeaderSize
-
- if len(data) != eventLen {
- return errors.Errorf("invalid data size %d in event %s, less event length %d", len(data), h.EventType, eventLen)
- }
-
- var e Event
- e, err = p.parseEvent(h, data)
- if err != nil {
+ if atomic.LoadUint32(&p.stopProcessing) == 1 {
break
}
- if err = onEvent(&BinlogEvent{rawData, h, e}); err != nil {
+ done, err := p.parseSingleEvent(r, onEvent)
+ if err != nil {
+ if err == errMissingTableMapEvent {
+ continue
+ }
return errors.Trace(err)
}
+
+ if done {
+ break
+ }
}
return nil
@@ -115,6 +179,22 @@ func (p *BinlogParser) SetRawMode(mode bool) {
p.rawMode = mode
}
+func (p *BinlogParser) SetParseTime(parseTime bool) {
+ p.parseTime = parseTime
+}
+
+func (p *BinlogParser) SetTimestampStringLocation(timestampStringLocation *time.Location) {
+ p.timestampStringLocation = timestampStringLocation
+}
+
+func (p *BinlogParser) SetUseDecimal(useDecimal bool) {
+ p.useDecimal = useDecimal
+}
+
+func (p *BinlogParser) SetVerifyChecksum(verify bool) {
+ p.verifyChecksum = verify
+}
+
func (p *BinlogParser) parseHeader(data []byte) (*EventHeader, error) {
h := new(EventHeader)
err := h.Decode(data)
@@ -125,7 +205,7 @@ func (p *BinlogParser) parseHeader(data []byte) (*EventHeader, error) {
return h, nil
}
-func (p *BinlogParser) parseEvent(h *EventHeader, data []byte) (Event, error) {
+func (p *BinlogParser) parseEvent(h *EventHeader, data []byte, rawData []byte) (Event, error) {
var e Event
if h.EventType == FORMAT_DESCRIPTION_EVENT {
@@ -133,7 +213,11 @@ func (p *BinlogParser) parseEvent(h *EventHeader, data []byte) (Event, error) {
e = p.format
} else {
if p.format != nil && p.format.ChecksumAlgorithm == BINLOG_CHECKSUM_ALG_CRC32 {
- data = data[0 : len(data)-4]
+ err := p.verifyCrc32Checksum(rawData)
+ if err != nil {
+ return nil, err
+ }
+ data = data[0 : len(data)-BinlogChecksumLength]
}
if h.EventType == ROTATE_EVENT {
@@ -166,12 +250,14 @@ func (p *BinlogParser) parseEvent(h *EventHeader, data []byte) (Event, error) {
e = &RowsQueryEvent{}
case GTID_EVENT:
e = >IDEvent{}
+ case ANONYMOUS_GTID_EVENT:
+ e = >IDEvent{}
case BEGIN_LOAD_QUERY_EVENT:
e = &BeginLoadQueryEvent{}
case EXECUTE_LOAD_QUERY_EVENT:
e = &ExecuteLoadQueryEvent{}
case MARIADB_ANNOTATE_ROWS_EVENT:
- e = &MariadbAnnotaeRowsEvent{}
+ e = &MariadbAnnotateRowsEvent{}
case MARIADB_BINLOG_CHECKPOINT_EVENT:
e = &MariadbBinlogCheckPointEvent{}
case MARIADB_GTID_LIST_EVENT:
@@ -206,7 +292,13 @@ func (p *BinlogParser) parseEvent(h *EventHeader, data []byte) (Event, error) {
return e, nil
}
-func (p *BinlogParser) parse(data []byte) (*BinlogEvent, error) {
+// Given the bytes for a a binary log event: return the decoded event.
+// With the exception of the FORMAT_DESCRIPTION_EVENT event type
+// there must have previously been passed a FORMAT_DESCRIPTION_EVENT
+// into the parser for this to work properly on any given event.
+// Passing a new FORMAT_DESCRIPTION_EVENT into the parser will replace
+// an existing one.
+func (p *BinlogParser) Parse(data []byte) (*BinlogEvent, error) {
rawData := data
h, err := p.parseHeader(data)
@@ -222,12 +314,32 @@ func (p *BinlogParser) parse(data []byte) (*BinlogEvent, error) {
return nil, fmt.Errorf("invalid data size %d in event %s, less event length %d", len(data), h.EventType, eventLen)
}
- e, err := p.parseEvent(h, data)
+ e, err := p.parseEvent(h, data, rawData)
if err != nil {
return nil, err
}
- return &BinlogEvent{rawData, h, e}, nil
+ return &BinlogEvent{RawData: rawData, Header: h, Event: e}, nil
+}
+
+func (p *BinlogParser) verifyCrc32Checksum(rawData []byte) error {
+ if !p.verifyChecksum {
+ return nil
+ }
+
+ calculatedPart := rawData[0 : len(rawData)-BinlogChecksumLength]
+ expectedChecksum := rawData[len(rawData)-BinlogChecksumLength:]
+
+ // mysql use zlib's CRC32 implementation, which uses polynomial 0xedb88320UL.
+ // reference: https://github.com/madler/zlib/blob/master/crc32.c
+ // https://github.com/madler/zlib/blob/master/doc/rfc1952.txt#L419
+ checksum := crc32.ChecksumIEEE(calculatedPart)
+ computed := make([]byte, BinlogChecksumLength)
+ binary.LittleEndian.PutUint32(computed, checksum)
+ if !bytes.Equal(expectedChecksum, computed) {
+ return ErrChecksumMismatch
+ }
+ return nil
}
func (p *BinlogParser) newRowsEvent(h *EventHeader) *RowsEvent {
@@ -240,6 +352,9 @@ func (p *BinlogParser) newRowsEvent(h *EventHeader) *RowsEvent {
e.needBitmap2 = false
e.tables = p.tables
+ e.parseTime = p.parseTime
+ e.timestampStringLocation = p.timestampStringLocation
+ e.useDecimal = p.useDecimal
switch h.EventType {
case WRITE_ROWS_EVENTv0:
diff --git a/vendor/github.com/siddontang/go-mysql/replication/parser_test.go b/vendor/github.com/siddontang/go-mysql/replication/parser_test.go
index 52f5f1f..d4efc98 100644
--- a/vendor/github.com/siddontang/go-mysql/replication/parser_test.go
+++ b/vendor/github.com/siddontang/go-mysql/replication/parser_test.go
@@ -29,7 +29,7 @@ func (t *testSyncerSuite) TestIndexOutOfRange(c *C) {
0x3065f: &TableMapEvent{tableIDSize: 6, TableID: 0x3065f, Flags: 0x1, Schema: []uint8{0x73, 0x65, 0x69, 0x75, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72}, Table: []uint8{0x63, 0x6f, 0x6e, 0x73, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x61, 0x6b, 0x6f, 0x75, 0x74, 0x5f, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72}, ColumnCount: 0xd, ColumnType: []uint8{0x3, 0x3, 0x3, 0x3, 0x1, 0x12, 0xf, 0xf, 0x12, 0xf, 0xf, 0x3, 0xf}, ColumnMeta: []uint16{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x180, 0x180, 0x0, 0x180, 0x180, 0x0, 0x2fd}, NullBitmap: []uint8{0xe0, 0x17}},
}
- _, err := parser.parse([]byte{
+ _, err := parser.Parse([]byte{
/* 0x00, */ 0xc1, 0x86, 0x8e, 0x55, 0x1e, 0xa5, 0x14, 0x80, 0xa, 0x55, 0x0, 0x0, 0x0, 0x7, 0xc,
0xbf, 0xe, 0x0, 0x0, 0x5f, 0x6, 0x3, 0x0, 0x0, 0x0, 0x1, 0x0, 0x2, 0x0, 0xd, 0xff,
0x0, 0x0, 0x19, 0x63, 0x7, 0x0, 0xca, 0x61, 0x5, 0x0, 0x5e, 0xf7, 0xc, 0x0, 0xf5, 0x7,
diff --git a/vendor/github.com/siddontang/go-mysql/replication/replication_test.go b/vendor/github.com/siddontang/go-mysql/replication/replication_test.go
index 82cedad..50fd1ee 100644
--- a/vendor/github.com/siddontang/go-mysql/replication/replication_test.go
+++ b/vendor/github.com/siddontang/go-mysql/replication/replication_test.go
@@ -1,15 +1,15 @@
package replication
import (
+ "context"
"flag"
"fmt"
"os"
+ "path"
"sync"
"testing"
"time"
- "golang.org/x/net/context"
-
. "github.com/pingcap/check"
uuid "github.com/satori/go.uuid"
"github.com/siddontang/go-mysql/client"
@@ -158,8 +158,8 @@ func (t *testSyncerSuite) testSync(c *C, s *BinlogStreamer) {
t.testExecute(c, "DROP TABLE IF EXISTS test_json_v2")
str = `CREATE TABLE test_json_v2 (
- id INT,
- c JSON,
+ id INT,
+ c JSON,
PRIMARY KEY (id)
) ENGINE=InnoDB`
@@ -230,6 +230,28 @@ func (t *testSyncerSuite) testSync(c *C, s *BinlogStreamer) {
}
}
+ str = `DROP TABLE IF EXISTS test_parse_time`
+ t.testExecute(c, str)
+
+ // Must allow zero time.
+ t.testExecute(c, `SET sql_mode=''`)
+ str = `CREATE TABLE test_parse_time (
+ a1 DATETIME,
+ a2 DATETIME(3),
+ a3 DATETIME(6),
+ b1 TIMESTAMP,
+ b2 TIMESTAMP(3) ,
+ b3 TIMESTAMP(6))`
+ t.testExecute(c, str)
+
+ t.testExecute(c, `INSERT INTO test_parse_time VALUES
+ ("2014-09-08 17:51:04.123456", "2014-09-08 17:51:04.123456", "2014-09-08 17:51:04.123456",
+ "2014-09-08 17:51:04.123456","2014-09-08 17:51:04.123456","2014-09-08 17:51:04.123456"),
+ ("0000-00-00 00:00:00.000000", "0000-00-00 00:00:00.000000", "0000-00-00 00:00:00.000000",
+ "0000-00-00 00:00:00.000000", "0000-00-00 00:00:00.000000", "0000-00-00 00:00:00.000000"),
+ ("2014-09-08 17:51:04.000456", "2014-09-08 17:51:04.000456", "2014-09-08 17:51:04.000456",
+ "2014-09-08 17:51:04.000456","2014-09-08 17:51:04.000456","2014-09-08 17:51:04.000456")`)
+
t.wg.Wait()
}
@@ -263,12 +285,13 @@ func (t *testSyncerSuite) setupTest(c *C, flavor string) {
}
cfg := BinlogSyncerConfig{
- ServerID: 100,
- Flavor: flavor,
- Host: *testHost,
- Port: port,
- User: "root",
- Password: "",
+ ServerID: 100,
+ Flavor: flavor,
+ Host: *testHost,
+ Port: port,
+ User: "root",
+ Password: "",
+ UseDecimal: true,
}
t.b = NewBinlogSyncer(cfg)
@@ -281,7 +304,7 @@ func (t *testSyncerSuite) testPositionSync(c *C) {
binFile, _ := r.GetString(0, 0)
binPos, _ := r.GetInt(0, 1)
- s, err := t.b.StartSync(mysql.Position{binFile, uint32(binPos)})
+ s, err := t.b.StartSync(mysql.Position{Name: binFile, Pos: uint32(binPos)})
c.Assert(err, IsNil)
// Test re-sync.
@@ -373,12 +396,15 @@ func (t *testSyncerSuite) TestMysqlBinlogCodec(c *C) {
t.testSync(c, nil)
}()
- os.RemoveAll("./var")
+ binlogDir := "./var"
- err := t.b.StartBackup("./var", mysql.Position{"", uint32(0)}, 2*time.Second)
+ os.RemoveAll(binlogDir)
+
+ err := t.b.StartBackup(binlogDir, mysql.Position{Name: "", Pos: uint32(0)}, 2*time.Second)
c.Assert(err, IsNil)
p := NewBinlogParser()
+ p.SetVerifyChecksum(true)
f := func(e *BinlogEvent) error {
if *testOutputLogs {
@@ -388,9 +414,15 @@ func (t *testSyncerSuite) TestMysqlBinlogCodec(c *C) {
return nil
}
- err = p.ParseFile("./var/mysql.000001", 0, f)
+ dir, err := os.Open(binlogDir)
+ c.Assert(err, IsNil)
+ defer dir.Close()
+
+ files, err := dir.Readdirnames(-1)
c.Assert(err, IsNil)
- err = p.ParseFile("./var/mysql.000002", 0, f)
- c.Assert(err, IsNil)
+ for _, file := range files {
+ err = p.ParseFile(path.Join(binlogDir, file), 0, f)
+ c.Assert(err, IsNil)
+ }
}
diff --git a/vendor/github.com/siddontang/go-mysql/replication/row_event.go b/vendor/github.com/siddontang/go-mysql/replication/row_event.go
index 85a3e43..9172f6e 100644
--- a/vendor/github.com/siddontang/go-mysql/replication/row_event.go
+++ b/vendor/github.com/siddontang/go-mysql/replication/row_event.go
@@ -10,11 +10,14 @@ import (
"time"
"github.com/juju/errors"
- "github.com/ngaut/log"
+ "github.com/shopspring/decimal"
+ "github.com/siddontang/go-log/log"
. "github.com/siddontang/go-mysql/mysql"
"github.com/siddontang/go/hack"
)
+var errMissingTableMapEvent = errors.New("invalid table id, no corresponding table map event")
+
type TableMapEvent struct {
tableIDSize int
@@ -68,7 +71,7 @@ func (e *TableMapEvent) Decode(data []byte) error {
var err error
var metaData []byte
- if metaData, _, n, err = LengthEnodedString(data[pos:]); err != nil {
+ if metaData, _, n, err = LengthEncodedString(data[pos:]); err != nil {
return errors.Trace(err)
}
@@ -78,11 +81,14 @@ func (e *TableMapEvent) Decode(data []byte) error {
pos += n
- if len(data[pos:]) != bitmapByteSize(int(e.ColumnCount)) {
+ nullBitmapSize := bitmapByteSize(int(e.ColumnCount))
+ if len(data[pos:]) < nullBitmapSize {
return io.EOF
}
- e.NullBitmap = data[pos:]
+ e.NullBitmap = data[pos : pos+nullBitmapSize]
+
+ // TODO: handle optional field meta
return nil
}
@@ -223,6 +229,10 @@ type RowsEvent struct {
//rows: invalid: int64, float64, bool, []byte, string
Rows [][]interface{}
+
+ parseTime bool
+ timestampStringLocation *time.Location
+ useDecimal bool
}
func (e *RowsEvent) Decode(data []byte) error {
@@ -257,7 +267,11 @@ func (e *RowsEvent) Decode(data []byte) error {
var ok bool
e.Table, ok = e.tables[e.TableID]
if !ok {
- return errors.Errorf("invalid table id %d, no correspond table map event", e.TableID)
+ if len(e.tables) > 0 {
+ return errors.Errorf("invalid table id %d, no corresponding table map event", e.TableID)
+ } else {
+ return errors.Annotatef(errMissingTableMapEvent, "table id %d", e.TableID)
+ }
}
var err error
@@ -336,6 +350,21 @@ func (e *RowsEvent) decodeRows(data []byte, table *TableMapEvent, bitmap []byte)
return pos, nil
}
+func (e *RowsEvent) parseFracTime(t interface{}) interface{} {
+ v, ok := t.(fracTime)
+ if !ok {
+ return t
+ }
+
+ if !e.parseTime {
+ // Don't parse time, return string directly
+ return v.String()
+ }
+
+ // return Golang time directly
+ return v.Time
+}
+
// see mysql sql/log_event.cc log_event_print_value
func (e *RowsEvent) decodeValue(data []byte, tp byte, meta uint16) (v interface{}, n int, err error) {
var length int = 0
@@ -378,7 +407,7 @@ func (e *RowsEvent) decodeValue(data []byte, tp byte, meta uint16) (v interface{
case MYSQL_TYPE_NEWDECIMAL:
prec := uint8(meta >> 8)
scale := uint8(meta & 0xFF)
- v, n, err = decodeDecimal(data, int(prec), int(scale))
+ v, n, err = decodeDecimal(data, int(prec), int(scale), e.useDecimal)
case MYSQL_TYPE_FLOAT:
n = 4
v = ParseBinaryFloat32(data)
@@ -396,7 +425,8 @@ func (e *RowsEvent) decodeValue(data []byte, tp byte, meta uint16) (v interface{
t := binary.LittleEndian.Uint32(data)
v = time.Unix(int64(t), 0)
case MYSQL_TYPE_TIMESTAMP2:
- v, n, err = decodeTimestamp2(data, meta)
+ v, n, err = decodeTimestamp2(data, meta, e.timestampStringLocation)
+ //v = e.parseFracTime(v)
case MYSQL_TYPE_DATETIME:
n = 8
i64 := binary.LittleEndian.Uint64(data)
@@ -416,6 +446,7 @@ func (e *RowsEvent) decodeValue(data []byte, tp byte, meta uint16) (v interface{
time.UTC).Format(TimeFormat)
case MYSQL_TYPE_DATETIME2:
v, n, err = decodeDatetime2(data, meta)
+ v = e.parseFracTime(v)
case MYSQL_TYPE_TIME:
n = 3
i32 := uint32(FixedLengthInt(data[0:3]))
@@ -449,7 +480,7 @@ func (e *RowsEvent) decodeValue(data []byte, tp byte, meta uint16) (v interface{
v = int64(data[0])
n = 1
case 2:
- v = int64(binary.BigEndian.Uint16(data))
+ v = int64(binary.LittleEndian.Uint16(data))
n = 2
default:
err = fmt.Errorf("Unknown ENUM packlen=%d", l)
@@ -458,7 +489,7 @@ func (e *RowsEvent) decodeValue(data []byte, tp byte, meta uint16) (v interface{
n = int(meta & 0xFF)
nbits := n * 8
- v, err = decodeBit(data, nbits, n)
+ v, err = littleDecodeBit(data, nbits, n)
case MYSQL_TYPE_BLOB:
v, n, err = decodeBlob(data, meta)
case MYSQL_TYPE_VARCHAR,
@@ -468,10 +499,10 @@ func (e *RowsEvent) decodeValue(data []byte, tp byte, meta uint16) (v interface{
case MYSQL_TYPE_STRING:
v, n = decodeString(data, length)
case MYSQL_TYPE_JSON:
- // Refer https://github.com/shyiko/mysql-binlog-connector-java/blob/8f9132ee773317e00313204beeae8ddcaa43c1b4/src/main/java/com/github/shyiko/mysql/binlog/event/deserialization/AbstractRowsEventDataDeserializer.java#L344
- length = int(binary.LittleEndian.Uint32(data[0:]))
+ // Refer: https://github.com/shyiko/mysql-binlog-connector-java/blob/master/src/main/java/com/github/shyiko/mysql/binlog/event/deserialization/AbstractRowsEventDataDeserializer.java#L404
+ length = int(FixedLengthInt(data[0:meta]))
n = length + int(meta)
- v, err = decodeJsonBinary(data[meta:n])
+ v, err = e.decodeJsonBinary(data[meta:n])
case MYSQL_TYPE_GEOMETRY:
// MySQL saves Geometry as Blob in binlog
// Seem that the binary format is SRID (4 bytes) + WKB, outer can use
@@ -515,7 +546,7 @@ func decodeDecimalDecompressValue(compIndx int, data []byte, mask uint8) (size i
return
}
-func decodeDecimal(data []byte, precision int, decimals int) (float64, int, error) {
+func decodeDecimal(data []byte, precision int, decimals int, useDecimal bool) (interface{}, int, error) {
//see python mysql replication and https://github.com/jeremycole/mysql_binlog
integral := (precision - decimals)
uncompIntegral := int(integral / digitsPerInteger)
@@ -568,6 +599,11 @@ func decodeDecimal(data []byte, precision int, decimals int) (float64, int, erro
pos += size
}
+ if useDecimal {
+ f, err := decimal.NewFromString(hack.String(res.Bytes()))
+ return f, pos, err
+ }
+
f, err := strconv.ParseFloat(hack.String(res.Bytes()), 64)
return f, pos, err
}
@@ -604,7 +640,39 @@ func decodeBit(data []byte, nbits int, length int) (value int64, err error) {
return
}
-func decodeTimestamp2(data []byte, dec uint16) (interface{}, int, error) {
+func littleDecodeBit(data []byte, nbits int, length int) (value int64, err error) {
+ if nbits > 1 {
+ switch length {
+ case 1:
+ value = int64(data[0])
+ case 2:
+ value = int64(binary.LittleEndian.Uint16(data))
+ case 3:
+ value = int64(FixedLengthInt(data[0:3]))
+ case 4:
+ value = int64(binary.LittleEndian.Uint32(data))
+ case 5:
+ value = int64(FixedLengthInt(data[0:5]))
+ case 6:
+ value = int64(FixedLengthInt(data[0:6]))
+ case 7:
+ value = int64(FixedLengthInt(data[0:7]))
+ case 8:
+ value = int64(binary.LittleEndian.Uint64(data))
+ default:
+ err = fmt.Errorf("invalid bit length %d", length)
+ }
+ } else {
+ if length != 1 {
+ err = fmt.Errorf("invalid bit length %d", length)
+ } else {
+ value = int64(data[0])
+ }
+ }
+ return
+}
+
+func decodeTimestamp2(data []byte, dec uint16, timestampStringLocation *time.Location) (interface{}, int, error) {
//get timestamp binary length
n := int(4 + (dec+1)/2)
sec := int64(binary.BigEndian.Uint32(data[0:4]))
@@ -619,7 +687,7 @@ func decodeTimestamp2(data []byte, dec uint16) (interface{}, int, error) {
}
if sec == 0 {
- return "0000-00-00 00:00:00", n, nil
+ return formatZeroTime(int(usec), int(dec)), n, nil
}
t := time.Unix(sec, usec*1000)
@@ -645,7 +713,7 @@ func decodeDatetime2(data []byte, dec uint16) (interface{}, int, error) {
}
if intPart == 0 {
- return "0000-00-00 00:00:00", n, nil
+ return formatZeroTime(int(frac), int(dec)), n, nil
}
tmp := intPart<<24 + frac
@@ -654,7 +722,7 @@ func decodeDatetime2(data []byte, dec uint16) (interface{}, int, error) {
tmp = -tmp
}
- var secPart int64 = tmp % (1 << 24)
+ // var secPart int64 = tmp % (1 << 24)
ymdhms := tmp >> 24
ymd := ymdhms >> 17
@@ -669,10 +737,14 @@ func decodeDatetime2(data []byte, dec uint16) (interface{}, int, error) {
minute := int((hms >> 6) % (1 << 6))
hour := int((hms >> 12))
- if secPart != 0 {
- return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%06d", year, month, day, hour, minute, second, secPart), n, nil // commented by Shlomi Noach. Yes I know about `git blame`
+ if frac != 0 {
+ return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%06d", year, month, day, hour, minute, second, frac), n, nil // commented by Shlomi Noach. Yes I know about `git blame`
}
return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second), n, nil // commented by Shlomi Noach. Yes I know about `git blame`
+ // return fracTime{
+ // Time: time.Date(year, time.Month(month), day, hour, minute, second, int(frac*1000), time.UTC),
+ // Dec: int(dec),
+ // }, n, nil
}
const TIMEF_OFS int64 = 0x800000000000
diff --git a/vendor/github.com/siddontang/go-mysql/replication/row_event_test.go b/vendor/github.com/siddontang/go-mysql/replication/row_event_test.go
index b1144c3..533a876 100644
--- a/vendor/github.com/siddontang/go-mysql/replication/row_event_test.go
+++ b/vendor/github.com/siddontang/go-mysql/replication/row_event_test.go
@@ -2,8 +2,10 @@ package replication
import (
"fmt"
+ "strconv"
. "github.com/pingcap/check"
+ "github.com/shopspring/decimal"
)
type testDecodeSuite struct{}
@@ -17,10 +19,10 @@ type decodeDecimalChecker struct {
func (_ *decodeDecimalChecker) Check(params []interface{}, names []string) (bool, string) {
var test int
val := struct {
- Value float64
+ Value decimal.Decimal
Pos int
Err error
- EValue float64
+ EValue decimal.Decimal
EPos int
EErr error
}{}
@@ -28,13 +30,13 @@ func (_ *decodeDecimalChecker) Check(params []interface{}, names []string) (bool
for i, name := range names {
switch name {
case "obtainedValue":
- val.Value, _ = params[i].(float64)
+ val.Value, _ = params[i].(decimal.Decimal)
case "obtainedPos":
val.Pos, _ = params[i].(int)
case "obtainedErr":
val.Err, _ = params[i].(error)
case "expectedValue":
- val.EValue, _ = params[i].(float64)
+ val.EValue, _ = params[i].(decimal.Decimal)
case "expectedPos":
val.EPos, _ = params[i].(int)
case "expectedErr":
@@ -50,7 +52,7 @@ func (_ *decodeDecimalChecker) Check(params []interface{}, names []string) (bool
if val.Pos != val.EPos {
return false, fmt.Sprintf(errorMsgFmt, "position", val.EPos, val.Pos)
}
- if val.Value != val.EValue {
+ if !val.Value.Equal(val.EValue) {
return false, fmt.Sprintf(errorMsgFmt, "value", val.EValue, val.Value)
}
return true, ""
@@ -66,7 +68,7 @@ func (_ *testDecodeSuite) TestDecodeDecimal(c *C) {
Data []byte
Precision int
Decimals int
- Expected float64
+ Expected string
ExpectedPos int
ExpectedErr error
}{
@@ -133,197 +135,202 @@ func (_ *testDecodeSuite) TestDecodeDecimal(c *C) {
| 17 | -99.99 | -1948 | -1948.140 | -1948.14 | -1948.140 | -1948.14 | -9.99999999999999 | -1948.1400000000 | -1948.14000 | -1948.14000000000000000000 | -1948.1400000000000000000000000 | 13 | 2 |
+----+--------+-------+-----------+-------------+-------------+----------------+-------------------+-----------------------+---------------------+---------------------------------+---------------------------------+------+-------+
*/
- {[]byte{117, 200, 127, 255}, 4, 2, float64(-10.55), 2, nil},
- {[]byte{127, 255, 244, 127, 245}, 5, 0, float64(-11), 3, nil},
- {[]byte{127, 245, 253, 217, 127, 255}, 7, 3, float64(-10.550), 4, nil},
- {[]byte{127, 255, 255, 245, 200, 127, 255}, 10, 2, float64(-10.55), 5, nil},
- {[]byte{127, 255, 255, 245, 253, 217, 127, 255}, 10, 3, float64(-10.550), 6, nil},
- {[]byte{127, 255, 255, 255, 245, 200, 118, 196}, 13, 2, float64(-10.55), 6, nil},
- {[]byte{118, 196, 101, 54, 0, 254, 121, 96, 127, 255}, 15, 14, float64(-9.99999999999999), 8, nil},
- {[]byte{127, 255, 255, 255, 245, 223, 55, 170, 127, 255, 127, 255}, 20, 10, float64(-10.5500000000), 10, nil},
- {[]byte{127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 245, 255, 41, 39, 127, 255}, 30, 5, float64(-10.55000), 15, nil},
- {[]byte{127, 255, 255, 255, 245, 223, 55, 170, 127, 255, 255, 255, 255, 255, 127, 255}, 30, 20, float64(-10.55000000000000000000), 14, nil},
- {[]byte{127, 255, 245, 223, 55, 170, 127, 255, 255, 255, 255, 255, 255, 255, 255, 4, 0}, 30, 25, float64(-10.5500000000000000000000000), 15, nil},
- {[]byte{128, 1, 128, 0}, 4, 2, float64(0.01), 2, nil},
- {[]byte{128, 0, 0, 128, 0}, 5, 0, float64(0), 3, nil},
- {[]byte{128, 0, 0, 12, 128, 0}, 7, 3, float64(0.012), 4, nil},
- {[]byte{128, 0, 0, 0, 1, 128, 0}, 10, 2, float64(0.01), 5, nil},
- {[]byte{128, 0, 0, 0, 0, 12, 128, 0}, 10, 3, float64(0.012), 6, nil},
- {[]byte{128, 0, 0, 0, 0, 1, 128, 0}, 13, 2, float64(0.01), 6, nil},
- {[]byte{128, 0, 188, 97, 78, 1, 96, 11, 128, 0}, 15, 14, float64(0.01234567890123), 8, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 188, 97, 78, 9, 128, 0}, 20, 10, float64(0.0123456789), 10, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 211, 128, 0}, 30, 5, float64(0.01235), 15, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 188, 97, 78, 53, 183, 191, 135, 89, 128, 0}, 30, 20, float64(0.01234567890123456789), 14, nil},
- {[]byte{128, 0, 0, 0, 188, 97, 78, 53, 183, 191, 135, 0, 135, 253, 217, 30, 0}, 30, 25, float64(0.0123456789012345678912345), 15, nil},
- {[]byte{227, 99, 128, 48}, 4, 2, float64(99.99), 2, nil},
- {[]byte{128, 48, 57, 167, 15}, 5, 0, float64(12345), 3, nil},
- {[]byte{167, 15, 3, 231, 128, 0}, 7, 3, float64(9999.999), 4, nil},
- {[]byte{128, 0, 48, 57, 0, 128, 0}, 10, 2, float64(12345.00), 5, nil},
- {[]byte{128, 0, 48, 57, 0, 0, 128, 0}, 10, 3, float64(12345.000), 6, nil},
- {[]byte{128, 0, 0, 48, 57, 0, 137, 59}, 13, 2, float64(12345.00), 6, nil},
- {[]byte{137, 59, 154, 201, 255, 1, 134, 159, 128, 0}, 15, 14, float64(9.99999999999999), 8, nil},
- {[]byte{128, 0, 0, 48, 57, 0, 0, 0, 0, 0, 128, 0}, 20, 10, float64(12345.0000000000), 10, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 57, 0, 0, 0, 128, 0}, 30, 5, float64(12345.00000), 15, nil},
- {[]byte{128, 0, 0, 48, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 48}, 30, 20, float64(12345.00000000000000000000), 14, nil},
- {[]byte{128, 48, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0}, 30, 25, float64(12345.0000000000000000000000000), 15, nil},
- {[]byte{227, 99, 128, 48}, 4, 2, float64(99.99), 2, nil},
- {[]byte{128, 48, 57, 167, 15}, 5, 0, float64(12345), 3, nil},
- {[]byte{167, 15, 3, 231, 128, 0}, 7, 3, float64(9999.999), 4, nil},
- {[]byte{128, 0, 48, 57, 0, 128, 0}, 10, 2, float64(12345.00), 5, nil},
- {[]byte{128, 0, 48, 57, 0, 0, 128, 0}, 10, 3, float64(12345.000), 6, nil},
- {[]byte{128, 0, 0, 48, 57, 0, 137, 59}, 13, 2, float64(12345.00), 6, nil},
- {[]byte{137, 59, 154, 201, 255, 1, 134, 159, 128, 0}, 15, 14, float64(9.99999999999999), 8, nil},
- {[]byte{128, 0, 0, 48, 57, 0, 0, 0, 0, 0, 128, 0}, 20, 10, float64(12345.0000000000), 10, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 57, 0, 0, 0, 128, 0}, 30, 5, float64(12345.00000), 15, nil},
- {[]byte{128, 0, 0, 48, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 48}, 30, 20, float64(12345.00000000000000000000), 14, nil},
- {[]byte{128, 48, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0}, 30, 25, float64(12345.0000000000000000000000000), 15, nil},
- {[]byte{227, 99, 128, 0}, 4, 2, float64(99.99), 2, nil},
- {[]byte{128, 0, 123, 128, 123}, 5, 0, float64(123), 3, nil},
- {[]byte{128, 123, 1, 194, 128, 0}, 7, 3, float64(123.450), 4, nil},
- {[]byte{128, 0, 0, 123, 45, 128, 0}, 10, 2, float64(123.45), 5, nil},
- {[]byte{128, 0, 0, 123, 1, 194, 128, 0}, 10, 3, float64(123.450), 6, nil},
- {[]byte{128, 0, 0, 0, 123, 45, 137, 59}, 13, 2, float64(123.45), 6, nil},
- {[]byte{137, 59, 154, 201, 255, 1, 134, 159, 128, 0}, 15, 14, float64(9.99999999999999), 8, nil},
- {[]byte{128, 0, 0, 0, 123, 26, 210, 116, 128, 0, 128, 0}, 20, 10, float64(123.4500000000), 10, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 0, 175, 200, 128, 0}, 30, 5, float64(123.45000), 15, nil},
- {[]byte{128, 0, 0, 0, 123, 26, 210, 116, 128, 0, 0, 0, 0, 0, 128, 0}, 30, 20, float64(123.45000000000000000000), 14, nil},
- {[]byte{128, 0, 123, 26, 210, 116, 128, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0}, 30, 25, float64(123.4500000000000000000000000), 15, nil},
- {[]byte{28, 156, 127, 255}, 4, 2, float64(-99.99), 2, nil},
- {[]byte{127, 255, 132, 127, 132}, 5, 0, float64(-123), 3, nil},
- {[]byte{127, 132, 254, 61, 127, 255}, 7, 3, float64(-123.450), 4, nil},
- {[]byte{127, 255, 255, 132, 210, 127, 255}, 10, 2, float64(-123.45), 5, nil},
- {[]byte{127, 255, 255, 132, 254, 61, 127, 255}, 10, 3, float64(-123.450), 6, nil},
- {[]byte{127, 255, 255, 255, 132, 210, 118, 196}, 13, 2, float64(-123.45), 6, nil},
- {[]byte{118, 196, 101, 54, 0, 254, 121, 96, 127, 255}, 15, 14, float64(-9.99999999999999), 8, nil},
- {[]byte{127, 255, 255, 255, 132, 229, 45, 139, 127, 255, 127, 255}, 20, 10, float64(-123.4500000000), 10, nil},
- {[]byte{127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 132, 255, 80, 55, 127, 255}, 30, 5, float64(-123.45000), 15, nil},
- {[]byte{127, 255, 255, 255, 132, 229, 45, 139, 127, 255, 255, 255, 255, 255, 127, 255}, 30, 20, float64(-123.45000000000000000000), 14, nil},
- {[]byte{127, 255, 132, 229, 45, 139, 127, 255, 255, 255, 255, 255, 255, 255, 255, 20, 0}, 30, 25, float64(-123.4500000000000000000000000), 15, nil},
- {[]byte{128, 0, 128, 0}, 4, 2, float64(0.00), 2, nil},
- {[]byte{128, 0, 0, 128, 0}, 5, 0, float64(0), 3, nil},
- {[]byte{128, 0, 0, 0, 128, 0}, 7, 3, float64(0.000), 4, nil},
- {[]byte{128, 0, 0, 0, 0, 128, 0}, 10, 2, float64(0.00), 5, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 128, 0}, 10, 3, float64(0.000), 6, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 128, 0}, 13, 2, float64(0.00), 6, nil},
- {[]byte{128, 0, 1, 226, 58, 0, 0, 99, 128, 0}, 15, 14, float64(0.00012345000099), 8, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 1, 226, 58, 0, 128, 0}, 20, 10, float64(0.0001234500), 10, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 128, 0}, 30, 5, float64(0.00012), 15, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 1, 226, 58, 0, 15, 18, 2, 0, 128, 0}, 30, 20, float64(0.00012345000098765000), 14, nil},
- {[]byte{128, 0, 0, 0, 1, 226, 58, 0, 15, 18, 2, 0, 0, 0, 0, 15, 0}, 30, 25, float64(0.0001234500009876500000000), 15, nil},
- {[]byte{128, 0, 128, 0}, 4, 2, float64(0.00), 2, nil},
- {[]byte{128, 0, 0, 128, 0}, 5, 0, float64(0), 3, nil},
- {[]byte{128, 0, 0, 0, 128, 0}, 7, 3, float64(0.000), 4, nil},
- {[]byte{128, 0, 0, 0, 0, 128, 0}, 10, 2, float64(0.00), 5, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 128, 0}, 10, 3, float64(0.000), 6, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 128, 0}, 13, 2, float64(0.00), 6, nil},
- {[]byte{128, 0, 1, 226, 58, 0, 0, 99, 128, 0}, 15, 14, float64(0.00012345000099), 8, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 1, 226, 58, 0, 128, 0}, 20, 10, float64(0.0001234500), 10, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 128, 0}, 30, 5, float64(0.00012), 15, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 1, 226, 58, 0, 15, 18, 2, 0, 128, 0}, 30, 20, float64(0.00012345000098765000), 14, nil},
- {[]byte{128, 0, 0, 0, 1, 226, 58, 0, 15, 18, 2, 0, 0, 0, 0, 22, 0}, 30, 25, float64(0.0001234500009876500000000), 15, nil},
- {[]byte{128, 12, 128, 0}, 4, 2, float64(0.12), 2, nil},
- {[]byte{128, 0, 0, 128, 0}, 5, 0, float64(0), 3, nil},
- {[]byte{128, 0, 0, 123, 128, 0}, 7, 3, float64(0.123), 4, nil},
- {[]byte{128, 0, 0, 0, 12, 128, 0}, 10, 2, float64(0.12), 5, nil},
- {[]byte{128, 0, 0, 0, 0, 123, 128, 0}, 10, 3, float64(0.123), 6, nil},
- {[]byte{128, 0, 0, 0, 0, 12, 128, 7}, 13, 2, float64(0.12), 6, nil},
- {[]byte{128, 7, 91, 178, 144, 1, 129, 205, 128, 0}, 15, 14, float64(0.12345000098765), 8, nil},
- {[]byte{128, 0, 0, 0, 0, 7, 91, 178, 145, 0, 128, 0}, 20, 10, float64(0.1234500010), 10, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 57, 128, 0}, 30, 5, float64(0.12345), 15, nil},
- {[]byte{128, 0, 0, 0, 0, 7, 91, 178, 144, 58, 222, 87, 208, 0, 128, 0}, 30, 20, float64(0.12345000098765000000), 14, nil},
- {[]byte{128, 0, 0, 7, 91, 178, 144, 58, 222, 87, 208, 0, 0, 0, 0, 30, 0}, 30, 25, float64(0.1234500009876500000000000), 15, nil},
- {[]byte{128, 0, 128, 0}, 4, 2, float64(0.00), 2, nil},
- {[]byte{128, 0, 0, 128, 0}, 5, 0, float64(0), 3, nil},
- {[]byte{128, 0, 0, 0, 128, 0}, 7, 3, float64(0.000), 4, nil},
- {[]byte{128, 0, 0, 0, 0, 128, 0}, 10, 2, float64(0.00), 5, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 128, 0}, 10, 3, float64(0.000), 6, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 127, 255}, 13, 2, float64(0.00), 6, nil},
- {[]byte{127, 255, 255, 255, 243, 255, 121, 59, 127, 255}, 15, 14, float64(-0.00000001234500), 8, nil},
- {[]byte{127, 255, 255, 255, 255, 255, 255, 255, 243, 252, 128, 0}, 20, 10, float64(-0.0000000123), 10, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 255}, 30, 5, float64(0.00000), 15, nil},
- {[]byte{127, 255, 255, 255, 255, 255, 255, 255, 243, 235, 111, 183, 93, 178, 127, 255}, 30, 20, float64(-0.00000001234500009877), 14, nil},
- {[]byte{127, 255, 255, 255, 255, 255, 243, 235, 111, 183, 93, 255, 139, 69, 47, 30, 0}, 30, 25, float64(-0.0000000123450000987650000), 15, nil},
- {[]byte{227, 99, 129, 134}, 4, 2, float64(99.99), 2, nil},
- {[]byte{129, 134, 159, 167, 15}, 5, 0, float64(99999), 3, nil},
- {[]byte{167, 15, 3, 231, 133, 245}, 7, 3, float64(9999.999), 4, nil},
- {[]byte{133, 245, 224, 255, 99, 128, 152}, 10, 2, float64(99999999.99), 5, nil},
- {[]byte{128, 152, 150, 127, 3, 231, 227, 59}, 10, 3, float64(9999999.999), 6, nil},
- {[]byte{227, 59, 154, 201, 255, 99, 137, 59}, 13, 2, float64(99999999999.99), 6, nil},
- {[]byte{137, 59, 154, 201, 255, 1, 134, 159, 137, 59}, 15, 14, float64(9.99999999999999), 8, nil},
- {[]byte{137, 59, 154, 201, 255, 59, 154, 201, 255, 9, 128, 0}, 20, 10, float64(9999999999.9999999999), 10, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 4, 210, 29, 205, 139, 148, 0, 195, 80, 137, 59}, 30, 5, float64(1234500009876.50000), 15, nil},
- {[]byte{137, 59, 154, 201, 255, 59, 154, 201, 255, 59, 154, 201, 255, 99, 129, 134}, 30, 20, float64(9999999999.99999999999999999999), 14, nil},
- {[]byte{129, 134, 159, 59, 154, 201, 255, 59, 154, 201, 255, 0, 152, 150, 127, 30, 0}, 30, 25, float64(99999.9999999999999999999999999), 15, nil},
- {[]byte{227, 99, 129, 134}, 4, 2, float64(99.99), 2, nil},
- {[]byte{129, 134, 159, 167, 15}, 5, 0, float64(99999), 3, nil},
- {[]byte{167, 15, 3, 231, 133, 245}, 7, 3, float64(9999.999), 4, nil},
- {[]byte{133, 245, 224, 255, 99, 128, 152}, 10, 2, float64(99999999.99), 5, nil},
- {[]byte{128, 152, 150, 127, 3, 231, 128, 6}, 10, 3, float64(9999999.999), 6, nil},
- {[]byte{128, 6, 159, 107, 199, 11, 137, 59}, 13, 2, float64(111111111.11), 6, nil},
- {[]byte{137, 59, 154, 201, 255, 1, 134, 159, 128, 6}, 15, 14, float64(9.99999999999999), 8, nil},
- {[]byte{128, 6, 159, 107, 199, 6, 142, 119, 128, 0, 128, 0}, 20, 10, float64(111111111.1100000000), 10, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 6, 159, 107, 199, 0, 42, 248, 128, 6}, 30, 5, float64(111111111.11000), 15, nil},
- {[]byte{128, 6, 159, 107, 199, 6, 142, 119, 128, 0, 0, 0, 0, 0, 129, 134}, 30, 20, float64(111111111.11000000000000000000), 14, nil},
- {[]byte{129, 134, 159, 59, 154, 201, 255, 59, 154, 201, 255, 0, 152, 150, 127, 10, 0}, 30, 25, float64(99999.9999999999999999999999999), 15, nil},
- {[]byte{128, 1, 128, 0}, 4, 2, float64(0.01), 2, nil},
- {[]byte{128, 0, 0, 128, 0}, 5, 0, float64(0), 3, nil},
- {[]byte{128, 0, 0, 10, 128, 0}, 7, 3, float64(0.010), 4, nil},
- {[]byte{128, 0, 0, 0, 1, 128, 0}, 10, 2, float64(0.01), 5, nil},
- {[]byte{128, 0, 0, 0, 0, 10, 128, 0}, 10, 3, float64(0.010), 6, nil},
- {[]byte{128, 0, 0, 0, 0, 1, 128, 0}, 13, 2, float64(0.01), 6, nil},
- {[]byte{128, 0, 152, 150, 128, 0, 0, 0, 128, 0}, 15, 14, float64(0.01000000000000), 8, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 152, 150, 128, 0, 128, 0}, 20, 10, float64(0.0100000000), 10, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 232, 128, 0}, 30, 5, float64(0.01000), 15, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 152, 150, 128, 0, 0, 0, 0, 0, 128, 0}, 30, 20, float64(0.01000000000000000000), 14, nil},
- {[]byte{128, 0, 0, 0, 152, 150, 128, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0}, 30, 25, float64(0.0100000000000000000000000), 15, nil},
- {[]byte{227, 99, 128, 0}, 4, 2, float64(99.99), 2, nil},
- {[]byte{128, 0, 123, 128, 123}, 5, 0, float64(123), 3, nil},
- {[]byte{128, 123, 1, 144, 128, 0}, 7, 3, float64(123.400), 4, nil},
- {[]byte{128, 0, 0, 123, 40, 128, 0}, 10, 2, float64(123.40), 5, nil},
- {[]byte{128, 0, 0, 123, 1, 144, 128, 0}, 10, 3, float64(123.400), 6, nil},
- {[]byte{128, 0, 0, 0, 123, 40, 137, 59}, 13, 2, float64(123.40), 6, nil},
- {[]byte{137, 59, 154, 201, 255, 1, 134, 159, 128, 0}, 15, 14, float64(9.99999999999999), 8, nil},
- {[]byte{128, 0, 0, 0, 123, 23, 215, 132, 0, 0, 128, 0}, 20, 10, float64(123.4000000000), 10, nil},
- {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 0, 156, 64, 128, 0}, 30, 5, float64(123.40000), 15, nil},
- {[]byte{128, 0, 0, 0, 123, 23, 215, 132, 0, 0, 0, 0, 0, 0, 128, 0}, 30, 20, float64(123.40000000000000000000), 14, nil},
- {[]byte{128, 0, 123, 23, 215, 132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0}, 30, 25, float64(123.4000000000000000000000000), 15, nil},
- {[]byte{28, 156, 127, 253}, 4, 2, float64(-99.99), 2, nil},
- {[]byte{127, 253, 204, 125, 205}, 5, 0, float64(-563), 3, nil},
- {[]byte{125, 205, 253, 187, 127, 255}, 7, 3, float64(-562.580), 4, nil},
- {[]byte{127, 255, 253, 205, 197, 127, 255}, 10, 2, float64(-562.58), 5, nil},
- {[]byte{127, 255, 253, 205, 253, 187, 127, 255}, 10, 3, float64(-562.580), 6, nil},
- {[]byte{127, 255, 255, 253, 205, 197, 118, 196}, 13, 2, float64(-562.58), 6, nil},
- {[]byte{118, 196, 101, 54, 0, 254, 121, 96, 127, 255}, 15, 14, float64(-9.99999999999999), 8, nil},
- {[]byte{127, 255, 255, 253, 205, 221, 109, 230, 255, 255, 127, 255}, 20, 10, float64(-562.5800000000), 10, nil},
- {[]byte{127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253, 205, 255, 29, 111, 127, 255}, 30, 5, float64(-562.58000), 15, nil},
- {[]byte{127, 255, 255, 253, 205, 221, 109, 230, 255, 255, 255, 255, 255, 255, 127, 253}, 30, 20, float64(-562.58000000000000000000), 14, nil},
- {[]byte{127, 253, 205, 221, 109, 230, 255, 255, 255, 255, 255, 255, 255, 255, 255, 13, 0}, 30, 25, float64(-562.5800000000000000000000000), 15, nil},
- {[]byte{28, 156, 127, 241}, 4, 2, float64(-99.99), 2, nil},
- {[]byte{127, 241, 140, 113, 140}, 5, 0, float64(-3699), 3, nil},
- {[]byte{113, 140, 255, 245, 127, 255}, 7, 3, float64(-3699.010), 4, nil},
- {[]byte{127, 255, 241, 140, 254, 127, 255}, 10, 2, float64(-3699.01), 5, nil},
- {[]byte{127, 255, 241, 140, 255, 245, 127, 255}, 10, 3, float64(-3699.010), 6, nil},
- {[]byte{127, 255, 255, 241, 140, 254, 118, 196}, 13, 2, float64(-3699.01), 6, nil},
- {[]byte{118, 196, 101, 54, 0, 254, 121, 96, 127, 255}, 15, 14, float64(-9.99999999999999), 8, nil},
- {[]byte{127, 255, 255, 241, 140, 255, 103, 105, 127, 255, 127, 255}, 20, 10, float64(-3699.0100000000), 10, nil},
- {[]byte{127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 241, 140, 255, 252, 23, 127, 255}, 30, 5, float64(-3699.01000), 15, nil},
- {[]byte{127, 255, 255, 241, 140, 255, 103, 105, 127, 255, 255, 255, 255, 255, 127, 241}, 30, 20, float64(-3699.01000000000000000000), 14, nil},
- {[]byte{127, 241, 140, 255, 103, 105, 127, 255, 255, 255, 255, 255, 255, 255, 255, 13, 0}, 30, 25, float64(-3699.0100000000000000000000000), 15, nil},
- {[]byte{28, 156, 127, 248}, 4, 2, float64(-99.99), 2, nil},
- {[]byte{127, 248, 99, 120, 99}, 5, 0, float64(-1948), 3, nil},
- {[]byte{120, 99, 255, 115, 127, 255}, 7, 3, float64(-1948.140), 4, nil},
- {[]byte{127, 255, 248, 99, 241, 127, 255}, 10, 2, float64(-1948.14), 5, nil},
- {[]byte{127, 255, 248, 99, 255, 115, 127, 255}, 10, 3, float64(-1948.140), 6, nil},
- {[]byte{127, 255, 255, 248, 99, 241, 118, 196}, 13, 2, float64(-1948.14), 6, nil},
- {[]byte{118, 196, 101, 54, 0, 254, 121, 96, 127, 255}, 15, 14, float64(-9.99999999999999), 8, nil},
- {[]byte{127, 255, 255, 248, 99, 247, 167, 196, 255, 255, 127, 255}, 20, 10, float64(-1948.1400000000), 10, nil},
- {[]byte{127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 248, 99, 255, 201, 79, 127, 255}, 30, 5, float64(-1948.14000), 15, nil},
- {[]byte{127, 255, 255, 248, 99, 247, 167, 196, 255, 255, 255, 255, 255, 255, 127, 248}, 30, 20, float64(-1948.14000000000000000000), 14, nil},
- {[]byte{127, 248, 99, 247, 167, 196, 255, 255, 255, 255, 255, 255, 255, 255, 255, 13, 0}, 30, 25, float64(-1948.1400000000000000000000000), 15, nil},
+ {[]byte{117, 200, 127, 255}, 4, 2, "-10.55", 2, nil},
+ {[]byte{127, 255, 244, 127, 245}, 5, 0, "-11", 3, nil},
+ {[]byte{127, 245, 253, 217, 127, 255}, 7, 3, "-10.550", 4, nil},
+ {[]byte{127, 255, 255, 245, 200, 127, 255}, 10, 2, "-10.55", 5, nil},
+ {[]byte{127, 255, 255, 245, 253, 217, 127, 255}, 10, 3, "-10.550", 6, nil},
+ {[]byte{127, 255, 255, 255, 245, 200, 118, 196}, 13, 2, "-10.55", 6, nil},
+ {[]byte{118, 196, 101, 54, 0, 254, 121, 96, 127, 255}, 15, 14, "-9.99999999999999", 8, nil},
+ {[]byte{127, 255, 255, 255, 245, 223, 55, 170, 127, 255, 127, 255}, 20, 10, "-10.5500000000", 10, nil},
+ {[]byte{127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 245, 255, 41, 39, 127, 255}, 30, 5, "-10.55000", 15, nil},
+ {[]byte{127, 255, 255, 255, 245, 223, 55, 170, 127, 255, 255, 255, 255, 255, 127, 255}, 30, 20, "-10.55000000000000000000", 14, nil},
+ {[]byte{127, 255, 245, 223, 55, 170, 127, 255, 255, 255, 255, 255, 255, 255, 255, 4, 0}, 30, 25, "-10.5500000000000000000000000", 15, nil},
+ {[]byte{128, 1, 128, 0}, 4, 2, "0.01", 2, nil},
+ {[]byte{128, 0, 0, 128, 0}, 5, 0, "0", 3, nil},
+ {[]byte{128, 0, 0, 12, 128, 0}, 7, 3, "0.012", 4, nil},
+ {[]byte{128, 0, 0, 0, 1, 128, 0}, 10, 2, "0.01", 5, nil},
+ {[]byte{128, 0, 0, 0, 0, 12, 128, 0}, 10, 3, "0.012", 6, nil},
+ {[]byte{128, 0, 0, 0, 0, 1, 128, 0}, 13, 2, "0.01", 6, nil},
+ {[]byte{128, 0, 188, 97, 78, 1, 96, 11, 128, 0}, 15, 14, "0.01234567890123", 8, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 188, 97, 78, 9, 128, 0}, 20, 10, "0.0123456789", 10, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 211, 128, 0}, 30, 5, "0.01235", 15, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 188, 97, 78, 53, 183, 191, 135, 89, 128, 0}, 30, 20, "0.01234567890123456789", 14, nil},
+ {[]byte{128, 0, 0, 0, 188, 97, 78, 53, 183, 191, 135, 0, 135, 253, 217, 30, 0}, 30, 25, "0.0123456789012345678912345", 15, nil},
+ {[]byte{227, 99, 128, 48}, 4, 2, "99.99", 2, nil},
+ {[]byte{128, 48, 57, 167, 15}, 5, 0, "12345", 3, nil},
+ {[]byte{167, 15, 3, 231, 128, 0}, 7, 3, "9999.999", 4, nil},
+ {[]byte{128, 0, 48, 57, 0, 128, 0}, 10, 2, "12345.00", 5, nil},
+ {[]byte{128, 0, 48, 57, 0, 0, 128, 0}, 10, 3, "12345.000", 6, nil},
+ {[]byte{128, 0, 0, 48, 57, 0, 137, 59}, 13, 2, "12345.00", 6, nil},
+ {[]byte{137, 59, 154, 201, 255, 1, 134, 159, 128, 0}, 15, 14, "9.99999999999999", 8, nil},
+ {[]byte{128, 0, 0, 48, 57, 0, 0, 0, 0, 0, 128, 0}, 20, 10, "12345.0000000000", 10, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 57, 0, 0, 0, 128, 0}, 30, 5, "12345.00000", 15, nil},
+ {[]byte{128, 0, 0, 48, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 48}, 30, 20, "12345.00000000000000000000", 14, nil},
+ {[]byte{128, 48, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0}, 30, 25, "12345.0000000000000000000000000", 15, nil},
+ {[]byte{227, 99, 128, 48}, 4, 2, "99.99", 2, nil},
+ {[]byte{128, 48, 57, 167, 15}, 5, 0, "12345", 3, nil},
+ {[]byte{167, 15, 3, 231, 128, 0}, 7, 3, "9999.999", 4, nil},
+ {[]byte{128, 0, 48, 57, 0, 128, 0}, 10, 2, "12345.00", 5, nil},
+ {[]byte{128, 0, 48, 57, 0, 0, 128, 0}, 10, 3, "12345.000", 6, nil},
+ {[]byte{128, 0, 0, 48, 57, 0, 137, 59}, 13, 2, "12345.00", 6, nil},
+ {[]byte{137, 59, 154, 201, 255, 1, 134, 159, 128, 0}, 15, 14, "9.99999999999999", 8, nil},
+ {[]byte{128, 0, 0, 48, 57, 0, 0, 0, 0, 0, 128, 0}, 20, 10, "12345.0000000000", 10, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 57, 0, 0, 0, 128, 0}, 30, 5, "12345.00000", 15, nil},
+ {[]byte{128, 0, 0, 48, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 48}, 30, 20, "12345.00000000000000000000", 14, nil},
+ {[]byte{128, 48, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0}, 30, 25, "12345.0000000000000000000000000", 15, nil},
+ {[]byte{227, 99, 128, 0}, 4, 2, "99.99", 2, nil},
+ {[]byte{128, 0, 123, 128, 123}, 5, 0, "123", 3, nil},
+ {[]byte{128, 123, 1, 194, 128, 0}, 7, 3, "123.450", 4, nil},
+ {[]byte{128, 0, 0, 123, 45, 128, 0}, 10, 2, "123.45", 5, nil},
+ {[]byte{128, 0, 0, 123, 1, 194, 128, 0}, 10, 3, "123.450", 6, nil},
+ {[]byte{128, 0, 0, 0, 123, 45, 137, 59}, 13, 2, "123.45", 6, nil},
+ {[]byte{137, 59, 154, 201, 255, 1, 134, 159, 128, 0}, 15, 14, "9.99999999999999", 8, nil},
+ {[]byte{128, 0, 0, 0, 123, 26, 210, 116, 128, 0, 128, 0}, 20, 10, "123.4500000000", 10, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 0, 175, 200, 128, 0}, 30, 5, "123.45000", 15, nil},
+ {[]byte{128, 0, 0, 0, 123, 26, 210, 116, 128, 0, 0, 0, 0, 0, 128, 0}, 30, 20, "123.45000000000000000000", 14, nil},
+ {[]byte{128, 0, 123, 26, 210, 116, 128, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0}, 30, 25, "123.4500000000000000000000000", 15, nil},
+ {[]byte{28, 156, 127, 255}, 4, 2, "-99.99", 2, nil},
+ {[]byte{127, 255, 132, 127, 132}, 5, 0, "-123", 3, nil},
+ {[]byte{127, 132, 254, 61, 127, 255}, 7, 3, "-123.450", 4, nil},
+ {[]byte{127, 255, 255, 132, 210, 127, 255}, 10, 2, "-123.45", 5, nil},
+ {[]byte{127, 255, 255, 132, 254, 61, 127, 255}, 10, 3, "-123.450", 6, nil},
+ {[]byte{127, 255, 255, 255, 132, 210, 118, 196}, 13, 2, "-123.45", 6, nil},
+ {[]byte{118, 196, 101, 54, 0, 254, 121, 96, 127, 255}, 15, 14, "-9.99999999999999", 8, nil},
+ {[]byte{127, 255, 255, 255, 132, 229, 45, 139, 127, 255, 127, 255}, 20, 10, "-123.4500000000", 10, nil},
+ {[]byte{127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 132, 255, 80, 55, 127, 255}, 30, 5, "-123.45000", 15, nil},
+ {[]byte{127, 255, 255, 255, 132, 229, 45, 139, 127, 255, 255, 255, 255, 255, 127, 255}, 30, 20, "-123.45000000000000000000", 14, nil},
+ {[]byte{127, 255, 132, 229, 45, 139, 127, 255, 255, 255, 255, 255, 255, 255, 255, 20, 0}, 30, 25, "-123.4500000000000000000000000", 15, nil},
+ {[]byte{128, 0, 128, 0}, 4, 2, "0.00", 2, nil},
+ {[]byte{128, 0, 0, 128, 0}, 5, 0, "0", 3, nil},
+ {[]byte{128, 0, 0, 0, 128, 0}, 7, 3, "0.000", 4, nil},
+ {[]byte{128, 0, 0, 0, 0, 128, 0}, 10, 2, "0.00", 5, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 128, 0}, 10, 3, "0.000", 6, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 128, 0}, 13, 2, "0.00", 6, nil},
+ {[]byte{128, 0, 1, 226, 58, 0, 0, 99, 128, 0}, 15, 14, "0.00012345000099", 8, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 1, 226, 58, 0, 128, 0}, 20, 10, "0.0001234500", 10, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 128, 0}, 30, 5, "0.00012", 15, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 1, 226, 58, 0, 15, 18, 2, 0, 128, 0}, 30, 20, "0.00012345000098765000", 14, nil},
+ {[]byte{128, 0, 0, 0, 1, 226, 58, 0, 15, 18, 2, 0, 0, 0, 0, 15, 0}, 30, 25, "0.0001234500009876500000000", 15, nil},
+ {[]byte{128, 0, 128, 0}, 4, 2, "0.00", 2, nil},
+ {[]byte{128, 0, 0, 128, 0}, 5, 0, "0", 3, nil},
+ {[]byte{128, 0, 0, 0, 128, 0}, 7, 3, "0.000", 4, nil},
+ {[]byte{128, 0, 0, 0, 0, 128, 0}, 10, 2, "0.00", 5, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 128, 0}, 10, 3, "0.000", 6, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 128, 0}, 13, 2, "0.00", 6, nil},
+ {[]byte{128, 0, 1, 226, 58, 0, 0, 99, 128, 0}, 15, 14, "0.00012345000099", 8, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 1, 226, 58, 0, 128, 0}, 20, 10, "0.0001234500", 10, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 128, 0}, 30, 5, "0.00012", 15, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 1, 226, 58, 0, 15, 18, 2, 0, 128, 0}, 30, 20, "0.00012345000098765000", 14, nil},
+ {[]byte{128, 0, 0, 0, 1, 226, 58, 0, 15, 18, 2, 0, 0, 0, 0, 22, 0}, 30, 25, "0.0001234500009876500000000", 15, nil},
+ {[]byte{128, 12, 128, 0}, 4, 2, "0.12", 2, nil},
+ {[]byte{128, 0, 0, 128, 0}, 5, 0, "0", 3, nil},
+ {[]byte{128, 0, 0, 123, 128, 0}, 7, 3, "0.123", 4, nil},
+ {[]byte{128, 0, 0, 0, 12, 128, 0}, 10, 2, "0.12", 5, nil},
+ {[]byte{128, 0, 0, 0, 0, 123, 128, 0}, 10, 3, "0.123", 6, nil},
+ {[]byte{128, 0, 0, 0, 0, 12, 128, 7}, 13, 2, "0.12", 6, nil},
+ {[]byte{128, 7, 91, 178, 144, 1, 129, 205, 128, 0}, 15, 14, "0.12345000098765", 8, nil},
+ {[]byte{128, 0, 0, 0, 0, 7, 91, 178, 145, 0, 128, 0}, 20, 10, "0.1234500010", 10, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 57, 128, 0}, 30, 5, "0.12345", 15, nil},
+ {[]byte{128, 0, 0, 0, 0, 7, 91, 178, 144, 58, 222, 87, 208, 0, 128, 0}, 30, 20, "0.12345000098765000000", 14, nil},
+ {[]byte{128, 0, 0, 7, 91, 178, 144, 58, 222, 87, 208, 0, 0, 0, 0, 30, 0}, 30, 25, "0.1234500009876500000000000", 15, nil},
+ {[]byte{128, 0, 128, 0}, 4, 2, "0.00", 2, nil},
+ {[]byte{128, 0, 0, 128, 0}, 5, 0, "0", 3, nil},
+ {[]byte{128, 0, 0, 0, 128, 0}, 7, 3, "0.000", 4, nil},
+ {[]byte{128, 0, 0, 0, 0, 128, 0}, 10, 2, "0.00", 5, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 128, 0}, 10, 3, "0.000", 6, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 127, 255}, 13, 2, "0.00", 6, nil},
+ {[]byte{127, 255, 255, 255, 243, 255, 121, 59, 127, 255}, 15, 14, "-0.00000001234500", 8, nil},
+ {[]byte{127, 255, 255, 255, 255, 255, 255, 255, 243, 252, 128, 0}, 20, 10, "-0.0000000123", 10, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 255}, 30, 5, "0.00000", 15, nil},
+ {[]byte{127, 255, 255, 255, 255, 255, 255, 255, 243, 235, 111, 183, 93, 178, 127, 255}, 30, 20, "-0.00000001234500009877", 14, nil},
+ {[]byte{127, 255, 255, 255, 255, 255, 243, 235, 111, 183, 93, 255, 139, 69, 47, 30, 0}, 30, 25, "-0.0000000123450000987650000", 15, nil},
+ {[]byte{227, 99, 129, 134}, 4, 2, "99.99", 2, nil},
+ {[]byte{129, 134, 159, 167, 15}, 5, 0, "99999", 3, nil},
+ {[]byte{167, 15, 3, 231, 133, 245}, 7, 3, "9999.999", 4, nil},
+ {[]byte{133, 245, 224, 255, 99, 128, 152}, 10, 2, "99999999.99", 5, nil},
+ {[]byte{128, 152, 150, 127, 3, 231, 227, 59}, 10, 3, "9999999.999", 6, nil},
+ {[]byte{227, 59, 154, 201, 255, 99, 137, 59}, 13, 2, "99999999999.99", 6, nil},
+ {[]byte{137, 59, 154, 201, 255, 1, 134, 159, 137, 59}, 15, 14, "9.99999999999999", 8, nil},
+ {[]byte{137, 59, 154, 201, 255, 59, 154, 201, 255, 9, 128, 0}, 20, 10, "9999999999.9999999999", 10, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 4, 210, 29, 205, 139, 148, 0, 195, 80, 137, 59}, 30, 5, "1234500009876.50000", 15, nil},
+ {[]byte{137, 59, 154, 201, 255, 59, 154, 201, 255, 59, 154, 201, 255, 99, 129, 134}, 30, 20, "9999999999.99999999999999999999", 14, nil},
+ {[]byte{129, 134, 159, 59, 154, 201, 255, 59, 154, 201, 255, 0, 152, 150, 127, 30, 0}, 30, 25, "99999.9999999999999999999999999", 15, nil},
+ {[]byte{227, 99, 129, 134}, 4, 2, "99.99", 2, nil},
+ {[]byte{129, 134, 159, 167, 15}, 5, 0, "99999", 3, nil},
+ {[]byte{167, 15, 3, 231, 133, 245}, 7, 3, "9999.999", 4, nil},
+ {[]byte{133, 245, 224, 255, 99, 128, 152}, 10, 2, "99999999.99", 5, nil},
+ {[]byte{128, 152, 150, 127, 3, 231, 128, 6}, 10, 3, "9999999.999", 6, nil},
+ {[]byte{128, 6, 159, 107, 199, 11, 137, 59}, 13, 2, "111111111.11", 6, nil},
+ {[]byte{137, 59, 154, 201, 255, 1, 134, 159, 128, 6}, 15, 14, "9.99999999999999", 8, nil},
+ {[]byte{128, 6, 159, 107, 199, 6, 142, 119, 128, 0, 128, 0}, 20, 10, "111111111.1100000000", 10, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 6, 159, 107, 199, 0, 42, 248, 128, 6}, 30, 5, "111111111.11000", 15, nil},
+ {[]byte{128, 6, 159, 107, 199, 6, 142, 119, 128, 0, 0, 0, 0, 0, 129, 134}, 30, 20, "111111111.11000000000000000000", 14, nil},
+ {[]byte{129, 134, 159, 59, 154, 201, 255, 59, 154, 201, 255, 0, 152, 150, 127, 10, 0}, 30, 25, "99999.9999999999999999999999999", 15, nil},
+ {[]byte{128, 1, 128, 0}, 4, 2, "0.01", 2, nil},
+ {[]byte{128, 0, 0, 128, 0}, 5, 0, "0", 3, nil},
+ {[]byte{128, 0, 0, 10, 128, 0}, 7, 3, "0.010", 4, nil},
+ {[]byte{128, 0, 0, 0, 1, 128, 0}, 10, 2, "0.01", 5, nil},
+ {[]byte{128, 0, 0, 0, 0, 10, 128, 0}, 10, 3, "0.010", 6, nil},
+ {[]byte{128, 0, 0, 0, 0, 1, 128, 0}, 13, 2, "0.01", 6, nil},
+ {[]byte{128, 0, 152, 150, 128, 0, 0, 0, 128, 0}, 15, 14, "0.01000000000000", 8, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 152, 150, 128, 0, 128, 0}, 20, 10, "0.0100000000", 10, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 232, 128, 0}, 30, 5, "0.01000", 15, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 152, 150, 128, 0, 0, 0, 0, 0, 128, 0}, 30, 20, "0.01000000000000000000", 14, nil},
+ {[]byte{128, 0, 0, 0, 152, 150, 128, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0}, 30, 25, "0.0100000000000000000000000", 15, nil},
+ {[]byte{227, 99, 128, 0}, 4, 2, "99.99", 2, nil},
+ {[]byte{128, 0, 123, 128, 123}, 5, 0, "123", 3, nil},
+ {[]byte{128, 123, 1, 144, 128, 0}, 7, 3, "123.400", 4, nil},
+ {[]byte{128, 0, 0, 123, 40, 128, 0}, 10, 2, "123.40", 5, nil},
+ {[]byte{128, 0, 0, 123, 1, 144, 128, 0}, 10, 3, "123.400", 6, nil},
+ {[]byte{128, 0, 0, 0, 123, 40, 137, 59}, 13, 2, "123.40", 6, nil},
+ {[]byte{137, 59, 154, 201, 255, 1, 134, 159, 128, 0}, 15, 14, "9.99999999999999", 8, nil},
+ {[]byte{128, 0, 0, 0, 123, 23, 215, 132, 0, 0, 128, 0}, 20, 10, "123.4000000000", 10, nil},
+ {[]byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 0, 156, 64, 128, 0}, 30, 5, "123.40000", 15, nil},
+ {[]byte{128, 0, 0, 0, 123, 23, 215, 132, 0, 0, 0, 0, 0, 0, 128, 0}, 30, 20, "123.40000000000000000000", 14, nil},
+ {[]byte{128, 0, 123, 23, 215, 132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0}, 30, 25, "123.4000000000000000000000000", 15, nil},
+ {[]byte{28, 156, 127, 253}, 4, 2, "-99.99", 2, nil},
+ {[]byte{127, 253, 204, 125, 205}, 5, 0, "-563", 3, nil},
+ {[]byte{125, 205, 253, 187, 127, 255}, 7, 3, "-562.580", 4, nil},
+ {[]byte{127, 255, 253, 205, 197, 127, 255}, 10, 2, "-562.58", 5, nil},
+ {[]byte{127, 255, 253, 205, 253, 187, 127, 255}, 10, 3, "-562.580", 6, nil},
+ {[]byte{127, 255, 255, 253, 205, 197, 118, 196}, 13, 2, "-562.58", 6, nil},
+ {[]byte{118, 196, 101, 54, 0, 254, 121, 96, 127, 255}, 15, 14, "-9.99999999999999", 8, nil},
+ {[]byte{127, 255, 255, 253, 205, 221, 109, 230, 255, 255, 127, 255}, 20, 10, "-562.5800000000", 10, nil},
+ {[]byte{127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253, 205, 255, 29, 111, 127, 255}, 30, 5, "-562.58000", 15, nil},
+ {[]byte{127, 255, 255, 253, 205, 221, 109, 230, 255, 255, 255, 255, 255, 255, 127, 253}, 30, 20, "-562.58000000000000000000", 14, nil},
+ {[]byte{127, 253, 205, 221, 109, 230, 255, 255, 255, 255, 255, 255, 255, 255, 255, 13, 0}, 30, 25, "-562.5800000000000000000000000", 15, nil},
+ {[]byte{28, 156, 127, 241}, 4, 2, "-99.99", 2, nil},
+ {[]byte{127, 241, 140, 113, 140}, 5, 0, "-3699", 3, nil},
+ {[]byte{113, 140, 255, 245, 127, 255}, 7, 3, "-3699.010", 4, nil},
+ {[]byte{127, 255, 241, 140, 254, 127, 255}, 10, 2, "-3699.01", 5, nil},
+ {[]byte{127, 255, 241, 140, 255, 245, 127, 255}, 10, 3, "-3699.010", 6, nil},
+ {[]byte{127, 255, 255, 241, 140, 254, 118, 196}, 13, 2, "-3699.01", 6, nil},
+ {[]byte{118, 196, 101, 54, 0, 254, 121, 96, 127, 255}, 15, 14, "-9.99999999999999", 8, nil},
+ {[]byte{127, 255, 255, 241, 140, 255, 103, 105, 127, 255, 127, 255}, 20, 10, "-3699.0100000000", 10, nil},
+ {[]byte{127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 241, 140, 255, 252, 23, 127, 255}, 30, 5, "-3699.01000", 15, nil},
+ {[]byte{127, 255, 255, 241, 140, 255, 103, 105, 127, 255, 255, 255, 255, 255, 127, 241}, 30, 20, "-3699.01000000000000000000", 14, nil},
+ {[]byte{127, 241, 140, 255, 103, 105, 127, 255, 255, 255, 255, 255, 255, 255, 255, 13, 0}, 30, 25, "-3699.0100000000000000000000000", 15, nil},
+ {[]byte{28, 156, 127, 248}, 4, 2, "-99.99", 2, nil},
+ {[]byte{127, 248, 99, 120, 99}, 5, 0, "-1948", 3, nil},
+ {[]byte{120, 99, 255, 115, 127, 255}, 7, 3, "-1948.140", 4, nil},
+ {[]byte{127, 255, 248, 99, 241, 127, 255}, 10, 2, "-1948.14", 5, nil},
+ {[]byte{127, 255, 248, 99, 255, 115, 127, 255}, 10, 3, "-1948.140", 6, nil},
+ {[]byte{127, 255, 255, 248, 99, 241, 118, 196}, 13, 2, "-1948.14", 6, nil},
+ {[]byte{118, 196, 101, 54, 0, 254, 121, 96, 127, 255}, 15, 14, "-9.99999999999999", 8, nil},
+ {[]byte{127, 255, 255, 248, 99, 247, 167, 196, 255, 255, 127, 255}, 20, 10, "-1948.1400000000", 10, nil},
+ {[]byte{127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 248, 99, 255, 201, 79, 127, 255}, 30, 5, "-1948.14000", 15, nil},
+ {[]byte{127, 255, 255, 248, 99, 247, 167, 196, 255, 255, 255, 255, 255, 255, 127, 248}, 30, 20, "-1948.14000000000000000000", 14, nil},
+ {[]byte{127, 248, 99, 247, 167, 196, 255, 255, 255, 255, 255, 255, 255, 255, 255, 13, 0}, 30, 25, "-1948.1400000000000000000000000", 15, nil},
}
for i, tc := range testcases {
- value, pos, err := decodeDecimal(tc.Data, tc.Precision, tc.Decimals)
- c.Assert(value, DecodeDecimalsEquals, pos, err, tc.Expected, tc.ExpectedPos, tc.ExpectedErr, i)
+ value, pos, err := decodeDecimal(tc.Data, tc.Precision, tc.Decimals, false)
+ expectedFloat, _ := strconv.ParseFloat(tc.Expected, 64)
+ c.Assert(value.(float64), DecodeDecimalsEquals, pos, err, expectedFloat, tc.ExpectedPos, tc.ExpectedErr, i)
+
+ value, pos, err = decodeDecimal(tc.Data, tc.Precision, tc.Decimals, true)
+ expectedDecimal, _ := decimal.NewFromString(tc.Expected)
+ c.Assert(value.(decimal.Decimal), DecodeDecimalsEquals, pos, err, expectedDecimal, tc.ExpectedPos, tc.ExpectedErr, i)
}
}
@@ -386,6 +393,25 @@ func (_ *testDecodeSuite) TestParseRowPanic(c *C) {
c.Assert(rows.Rows[0][0], Equals, int32(16270))
}
+type simpleDecimalEqualsChecker struct {
+ *CheckerInfo
+}
+
+var SimpleDecimalEqualsChecker Checker = &simpleDecimalEqualsChecker{
+ &CheckerInfo{Name: "Equals", Params: []string{"obtained", "expected"}},
+}
+
+func (checker *simpleDecimalEqualsChecker) Check(params []interface{}, names []string) (result bool, error string) {
+ defer func() {
+ if v := recover(); v != nil {
+ result = false
+ error = fmt.Sprint(v)
+ }
+ }()
+
+ return params[0].(decimal.Decimal).Equal(params[1].(decimal.Decimal)), ""
+}
+
func (_ *testDecodeSuite) TestParseJson(c *C) {
// Table format:
// mysql> desc t10;
@@ -405,7 +431,6 @@ func (_ *testDecodeSuite) TestParseJson(c *C) {
// INSERT INTO `t10` (`c1`, `c2`) VALUES ('{"key1": "value1", "key2": "value2"}', 1);
// test json deserialization
// INSERT INTO `t10`(`c1`,`c2`) VALUES ('{"text":"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac"}',101);
-
tableMapEventData := []byte("m\x00\x00\x00\x00\x00\x01\x00\x04test\x00\x03t10\x00\x02\xf5\xf6\x03\x04\n\x00\x03")
tableMapEvent := new(TableMapEvent)
@@ -442,3 +467,196 @@ func (_ *testDecodeSuite) TestParseJson(c *C) {
c.Assert(rows.Rows[0][1], Equals, float64(101))
}
}
+func (_ *testDecodeSuite) TestParseJsonDecimal(c *C) {
+ // Table format:
+ // mysql> desc t10;
+ // +-------+---------------+------+-----+---------+-------+
+ // | Field | Type | Null | Key | Default | Extra |
+ // +-------+---------------+------+-----+---------+-------+
+ // | c1 | json | YES | | NULL | |
+ // | c2 | decimal(10,0) | YES | | NULL | |
+ // +-------+---------------+------+-----+---------+-------+
+
+ // CREATE TABLE `t10` (
+ // `c1` json DEFAULT NULL,
+ // `c2` decimal(10,0)
+ // ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+ // INSERT INTO `t10` (`c2`) VALUES (1);
+ // INSERT INTO `t10` (`c1`, `c2`) VALUES ('{"key1": "value1", "key2": "value2"}', 1);
+ // test json deserialization
+ // INSERT INTO `t10`(`c1`,`c2`) VALUES ('{"text":"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac"}',101);
+ tableMapEventData := []byte("m\x00\x00\x00\x00\x00\x01\x00\x04test\x00\x03t10\x00\x02\xf5\xf6\x03\x04\n\x00\x03")
+
+ tableMapEvent := new(TableMapEvent)
+ tableMapEvent.tableIDSize = 6
+ err := tableMapEvent.Decode(tableMapEventData)
+ c.Assert(err, IsNil)
+
+ rows := RowsEvent{useDecimal: true}
+ rows.tableIDSize = 6
+ rows.tables = make(map[uint64]*TableMapEvent)
+ rows.tables[tableMapEvent.TableID] = tableMapEvent
+ rows.Version = 2
+
+ tbls := [][]byte{
+ []byte("m\x00\x00\x00\x00\x00\x01\x00\x02\x00\x02\xff\xfd\x80\x00\x00\x00\x01"),
+ []byte("m\x00\x00\x00\x00\x00\x01\x00\x02\x00\x02\xff\xfc)\x00\x00\x00\x00\x02\x00(\x00\x12\x00\x04\x00\x16\x00\x04\x00\f\x1a\x00\f!\x00key1key2\x06value1\x06value2\x80\x00\x00\x00\x01"),
+ }
+
+ for _, tbl := range tbls {
+ rows.Rows = nil
+ err = rows.Decode(tbl)
+ c.Assert(err, IsNil)
+ c.Assert(rows.Rows[0][1], SimpleDecimalEqualsChecker, decimal.NewFromFloat(1))
+ }
+
+ longTbls := [][]byte{
+ []byte("m\x00\x00\x00\x00\x00\x01\x00\x02\x00\x02\xff\xfc\xd0\n\x00\x00\x00\x01\x00\xcf\n\v\x00\x04\x00\f\x0f\x00text\xbe\x15Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac\x80\x00\x00\x00e"),
+ }
+
+ for _, ltbl := range longTbls {
+ rows.Rows = nil
+ err = rows.Decode(ltbl)
+ c.Assert(err, IsNil)
+ c.Assert(rows.Rows[0][1], SimpleDecimalEqualsChecker, decimal.NewFromFloat(101))
+ }
+}
+
+func (_ *testDecodeSuite) TestEnum(c *C) {
+ // mysql> desc aenum;
+ // +-------+-------------------------------------------+------+-----+---------+-------+
+ // | Field | Type | Null | Key | Default | Extra |
+ // +-------+-------------------------------------------+------+-----+---------+-------+
+ // | id | int(11) | YES | | NULL | |
+ // | aset | enum('0','1','2','3','4','5','6','7','8') | YES | | NULL | |
+ // +-------+-------------------------------------------+------+-----+---------+-------+
+ // 2 rows in set (0.00 sec)
+ //
+ // insert into aenum(id, aset) values(1, '0');
+ tableMapEventData := []byte("\x42\x0f\x00\x00\x00\x00\x01\x00\x05\x74\x74\x65\x73\x74\x00\x05")
+ tableMapEventData = append(tableMapEventData, []byte("\x61\x65\x6e\x75\x6d\x00\x02\x03\xfe\x02\xf7\x01\x03")...)
+ tableMapEvent := new(TableMapEvent)
+ tableMapEvent.tableIDSize = 6
+ err := tableMapEvent.Decode(tableMapEventData)
+ c.Assert(err, IsNil)
+
+ rows := new(RowsEvent)
+ rows.tableIDSize = 6
+ rows.tables = make(map[uint64]*TableMapEvent)
+ rows.tables[tableMapEvent.TableID] = tableMapEvent
+ rows.Version = 2
+
+ data := []byte("\x42\x0f\x00\x00\x00\x00\x01\x00\x02\x00\x02\xff\xfc\x01\x00\x00\x00\x01")
+
+ rows.Rows = nil
+ err = rows.Decode(data)
+ c.Assert(err, IsNil)
+ c.Assert(rows.Rows[0][1], Equals, int64(1))
+}
+
+func (_ *testDecodeSuite) TestMultiBytesEnum(c *C) {
+ // CREATE TABLE numbers (
+ // id int auto_increment,
+ // num ENUM( '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '100', '101', '102', '103', '104', '105', '106', '107', '108', '109', '110', '111', '112', '113', '114', '115', '116', '117', '118', '119', '120', '121', '122', '123', '124', '125', '126', '127', '128', '129', '130', '131', '132', '133', '134', '135', '136', '137', '138', '139', '140', '141', '142', '143', '144', '145', '146', '147', '148', '149', '150', '151', '152', '153', '154', '155', '156', '157', '158', '159', '160', '161', '162', '163', '164', '165', '166', '167', '168', '169', '170', '171', '172', '173', '174', '175', '176', '177', '178', '179', '180', '181', '182', '183', '184', '185', '186', '187', '188', '189', '190', '191', '192', '193', '194', '195', '196', '197', '198', '199', '200', '201', '202', '203', '204', '205', '206', '207', '208', '209', '210', '211', '212', '213', '214', '215', '216', '217', '218', '219', '220', '221', '222', '223', '224', '225', '226', '227', '228', '229', '230', '231', '232', '233', '234', '235', '236', '237', '238', '239', '240', '241', '242', '243', '244', '245', '246', '247', '248', '249', '250', '251', '252', '253', '254', '255','256','257'
+
+ // ),
+ // primary key(id)
+ // );
+
+ //
+ // insert into numbers(num) values ('0'), ('256');
+ tableMapEventData := []byte("\x84\x0f\x00\x00\x00\x00\x01\x00\x05\x74\x74\x65\x73\x74\x00\x07")
+ tableMapEventData = append(tableMapEventData, []byte("\x6e\x75\x6d\x62\x65\x72\x73\x00\x02\x03\xfe\x02\xf7\x02\x02")...)
+ tableMapEvent := new(TableMapEvent)
+ tableMapEvent.tableIDSize = 6
+ err := tableMapEvent.Decode(tableMapEventData)
+ c.Assert(err, IsNil)
+
+ rows := new(RowsEvent)
+ rows.tableIDSize = 6
+ rows.tables = make(map[uint64]*TableMapEvent)
+ rows.tables[tableMapEvent.TableID] = tableMapEvent
+ rows.Version = 2
+
+ data := []byte("\x84\x0f\x00\x00\x00\x00\x01\x00\x02\x00\x02\xff\xfc\x01\x00\x00\x00\x01\x00\xfc\x02\x00\x00\x00\x01\x01")
+
+ rows.Rows = nil
+ err = rows.Decode(data)
+ c.Assert(err, IsNil)
+ c.Assert(rows.Rows[0][1], Equals, int64(1))
+ c.Assert(rows.Rows[1][1], Equals, int64(257))
+}
+
+func (_ *testDecodeSuite) TestSet(c *C) {
+ // mysql> desc aset;
+ // +--------+---------------------------------------------------------------------------------------+------+-----+---------+-------+
+ // | Field | Type | Null | Key | Default | Extra |
+ // +--------+---------------------------------------------------------------------------------------+------+-----+---------+-------+
+ // | id | int(11) | YES | | NULL | |
+ // | region | set('1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18') | YES | | NULL | |
+ // +--------+---------------------------------------------------------------------------------------+------+-----+---------+-------+
+ // 2 rows in set (0.00 sec)
+ //
+ // insert into aset(id, region) values(1, '1,3');
+
+ tableMapEventData := []byte("\xe7\x0e\x00\x00\x00\x00\x01\x00\x05\x74\x74\x65\x73\x74\x00\x04")
+ tableMapEventData = append(tableMapEventData, []byte("\x61\x73\x65\x74\x00\x02\x03\xfe\x02\xf8\x03\x03")...)
+ tableMapEvent := new(TableMapEvent)
+ tableMapEvent.tableIDSize = 6
+ err := tableMapEvent.Decode(tableMapEventData)
+ c.Assert(err, IsNil)
+
+ rows := new(RowsEvent)
+ rows.tableIDSize = 6
+ rows.tables = make(map[uint64]*TableMapEvent)
+ rows.tables[tableMapEvent.TableID] = tableMapEvent
+ rows.Version = 2
+
+ data := []byte("\xe7\x0e\x00\x00\x00\x00\x01\x00\x02\x00\x02\xff\xfc\x01\x00\x00\x00\x05\x00\x00")
+
+ rows.Rows = nil
+ err = rows.Decode(data)
+ c.Assert(err, IsNil)
+ c.Assert(rows.Rows[0][1], Equals, int64(5))
+}
+
+func (_ *testDecodeSuite) TestJsonNull(c *C) {
+ // Table:
+ // desc hj_order_preview
+ // +------------------+------------+------+-----+-------------------+----------------+
+ // | Field | Type | Null | Key | Default | Extra |
+ // +------------------+------------+------+-----+-------------------+----------------+
+ // | id | int(13) | NO | PRI | | auto_increment |
+ // | buyer_id | bigint(13) | NO | | | |
+ // | order_sn | bigint(13) | NO | | | |
+ // | order_detail | json | NO | | | |
+ // | is_del | tinyint(1) | NO | | 0 | |
+ // | add_time | int(13) | NO | | | |
+ // | last_update_time | timestamp | NO | | CURRENT_TIMESTAMP | |
+ // +------------------+------------+------+-----+-------------------+----------------+
+ // insert into hj_order_preview
+ // (id, buyer_id, order_sn, is_del, add_time, last_update_time)
+ // values (1, 95891865464386, 13376222192996417, 0, 1479983995, 1479983995)
+
+ tableMapEventData := []byte("r\x00\x00\x00\x00\x00\x01\x00\x04test\x00\x10hj_order_preview\x00\a\x03\b\b\xf5\x01\x03\x11\x02\x04\x00\x00")
+
+ tableMapEvent := new(TableMapEvent)
+ tableMapEvent.tableIDSize = 6
+ err := tableMapEvent.Decode(tableMapEventData)
+ c.Assert(err, IsNil)
+
+ rows := new(RowsEvent)
+ rows.tableIDSize = 6
+ rows.tables = make(map[uint64]*TableMapEvent)
+ rows.tables[tableMapEvent.TableID] = tableMapEvent
+ rows.Version = 2
+
+ data :=
+ []byte("r\x00\x00\x00\x00\x00\x01\x00\x02\x00\a\xff\x80\x01\x00\x00\x00B\ue4d06W\x00\x00A\x10@l\x9a\x85/\x00\x00\x00\x00\x00\x00{\xc36X\x00\x00\x00\x00")
+
+ rows.Rows = nil
+ err = rows.Decode(data)
+ c.Assert(err, IsNil)
+ c.Assert(rows.Rows[0][3], HasLen, 0)
+}
diff --git a/vendor/github.com/siddontang/go-mysql/replication/time.go b/vendor/github.com/siddontang/go-mysql/replication/time.go
new file mode 100644
index 0000000..bd27c4e
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/replication/time.go
@@ -0,0 +1,49 @@
+package replication
+
+import (
+ "fmt"
+ "strings"
+ "time"
+)
+
+var (
+ fracTimeFormat []string
+)
+
+// fracTime is a help structure wrapping Golang Time.
+type fracTime struct {
+ time.Time
+
+ // Dec must in [0, 6]
+ Dec int
+
+ timestampStringLocation *time.Location
+}
+
+func (t fracTime) String() string {
+ tt := t.Time
+ if t.timestampStringLocation != nil {
+ tt = tt.In(t.timestampStringLocation)
+ }
+ return tt.Format(fracTimeFormat[t.Dec])
+}
+
+func formatZeroTime(frac int, dec int) string {
+ if dec == 0 {
+ return "0000-00-00 00:00:00"
+ }
+
+ s := fmt.Sprintf("0000-00-00 00:00:00.%06d", frac)
+
+ // dec must < 6, if frac is 924000, but dec is 3, we must output 924 here.
+ return s[0 : len(s)-(6-dec)]
+}
+
+func init() {
+ fracTimeFormat = make([]string, 7)
+ fracTimeFormat[0] = "2006-01-02 15:04:05"
+
+ for i := 1; i <= 6; i++ {
+ fracTimeFormat[i] = fmt.Sprintf("2006-01-02 15:04:05.%s", strings.Repeat("0", i))
+ }
+}
diff --git a/vendor/github.com/siddontang/go-mysql/replication/time_test.go b/vendor/github.com/siddontang/go-mysql/replication/time_test.go
new file mode 100644
index 0000000..3a06aaf
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/replication/time_test.go
@@ -0,0 +1,70 @@
+package replication
+
+import (
+ "time"
+
+ . "github.com/pingcap/check"
+)
+
+type testTimeSuite struct{}
+
+var _ = Suite(&testTimeSuite{})
+
+func (s *testTimeSuite) TestTime(c *C) {
+ tbls := []struct {
+ year int
+ month int
+ day int
+ hour int
+ min int
+ sec int
+ microSec int
+ frac int
+ expected string
+ }{
+ {2000, 1, 1, 1, 1, 1, 1, 0, "2000-01-01 01:01:01"},
+ {2000, 1, 1, 1, 1, 1, 1, 1, "2000-01-01 01:01:01.0"},
+ {2000, 1, 1, 1, 1, 1, 1, 6, "2000-01-01 01:01:01.000001"},
+ }
+
+ for _, t := range tbls {
+ t1 := fracTime{time.Date(t.year, time.Month(t.month), t.day, t.hour, t.min, t.sec, t.microSec*1000, time.UTC), t.frac, nil}
+ c.Assert(t1.String(), Equals, t.expected)
+ }
+
+ zeroTbls := []struct {
+ frac int
+ dec int
+ expected string
+ }{
+ {0, 1, "0000-00-00 00:00:00.0"},
+ {1, 1, "0000-00-00 00:00:00.0"},
+ {123, 3, "0000-00-00 00:00:00.000"},
+ {123000, 3, "0000-00-00 00:00:00.123"},
+ {123, 6, "0000-00-00 00:00:00.000123"},
+ {123000, 6, "0000-00-00 00:00:00.123000"},
+ }
+
+ for _, t := range zeroTbls {
+ c.Assert(formatZeroTime(t.frac, t.dec), Equals, t.expected)
+ }
+}
+
+func (s *testTimeSuite) TestTimeStringLocation(c *C) {
+ t := fracTime{
+ time.Date(2018, time.Month(7), 30, 10, 0, 0, 0, time.FixedZone("EST", -5*3600)),
+ 0,
+ nil,
+ }
+
+ c.Assert(t.String(), Equals, "2018-07-30 10:00:00")
+
+ t = fracTime{
+ time.Date(2018, time.Month(7), 30, 10, 0, 0, 0, time.FixedZone("EST", -5*3600)),
+ 0,
+ time.UTC,
+ }
+ c.Assert(t.String(), Equals, "2018-07-30 15:00:00")
+}
+
+var _ = Suite(&testTimeSuite{})
diff --git a/vendor/github.com/siddontang/go-mysql/schema/schema.go b/vendor/github.com/siddontang/go-mysql/schema/schema.go
index 86d2128..c98b9ac 100644
--- a/vendor/github.com/siddontang/go-mysql/schema/schema.go
+++ b/vendor/github.com/siddontang/go-mysql/schema/schema.go
@@ -5,6 +5,7 @@
package schema
import (
+ "database/sql"
"fmt"
"strings"
@@ -12,6 +13,11 @@ import (
"github.com/siddontang/go-mysql/mysql"
)
+var ErrTableNotExist = errors.New("table is not exist")
+var ErrMissingTableMeta = errors.New("missing table meta")
+var HAHealthCheckSchema = "mysql.ha_health_check"
+
+// Different column type
const (
TYPE_NUMBER = iota + 1 // tinyint, smallint, mediumint, int, bigint, year
TYPE_FLOAT // float, double
@@ -24,12 +30,16 @@ const (
TYPE_TIME // time
TYPE_BIT // bit
TYPE_JSON // json
+ TYPE_DECIMAL // decimal
)
type TableColumn struct {
Name string
Type int
+ Collation string
+ RawType string
IsAuto bool
+ IsUnsigned bool
EnumValues []string
SetValues []string
}
@@ -47,22 +57,24 @@ type Table struct {
Columns []TableColumn
Indexes []*Index
PKColumns []int
+
+ UnsignedColumns []int
}
func (ta *Table) String() string {
return fmt.Sprintf("%s.%s", ta.Schema, ta.Name)
}
-func (ta *Table) AddColumn(name string, columnType string, extra string) {
+func (ta *Table) AddColumn(name string, columnType string, collation string, extra string) {
index := len(ta.Columns)
- ta.Columns = append(ta.Columns, TableColumn{Name: name})
+ ta.Columns = append(ta.Columns, TableColumn{Name: name, Collation: collation})
+ ta.Columns[index].RawType = columnType
- if strings.Contains(columnType, "int") || strings.HasPrefix(columnType, "year") {
- ta.Columns[index].Type = TYPE_NUMBER
- } else if strings.HasPrefix(columnType, "float") ||
- strings.HasPrefix(columnType, "double") ||
- strings.HasPrefix(columnType, "decimal") {
+ if strings.HasPrefix(columnType, "float") ||
+ strings.HasPrefix(columnType, "double") {
ta.Columns[index].Type = TYPE_FLOAT
+ } else if strings.HasPrefix(columnType, "decimal") {
+ ta.Columns[index].Type = TYPE_DECIMAL
} else if strings.HasPrefix(columnType, "enum") {
ta.Columns[index].Type = TYPE_ENUM
ta.Columns[index].EnumValues = strings.Split(strings.Replace(
@@ -93,10 +105,17 @@ func (ta *Table) AddColumn(name string, columnType string, extra string) {
ta.Columns[index].Type = TYPE_BIT
} else if strings.HasPrefix(columnType, "json") {
ta.Columns[index].Type = TYPE_JSON
+ } else if strings.Contains(columnType, "int") || strings.HasPrefix(columnType, "year") {
+ ta.Columns[index].Type = TYPE_NUMBER
} else {
ta.Columns[index].Type = TYPE_STRING
}
+ if strings.Contains(columnType, "unsigned") || strings.Contains(columnType, "zerofill") {
+ ta.Columns[index].IsUnsigned = true
+ ta.UnsignedColumns = append(ta.UnsignedColumns, index)
+ }
+
if extra == "auto_increment" {
ta.Columns[index].IsAuto = true
}
@@ -142,6 +161,35 @@ func (idx *Index) FindColumn(name string) int {
return -1
}
+func IsTableExist(conn mysql.Executer, schema string, name string) (bool, error) {
+ query := fmt.Sprintf("SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '%s' and TABLE_NAME = '%s' LIMIT 1", schema, name)
+ r, err := conn.Execute(query)
+ if err != nil {
+ return false, errors.Trace(err)
+ }
+
+ return r.RowNumber() == 1, nil
+}
+
+func NewTableFromSqlDB(conn *sql.DB, schema string, name string) (*Table, error) {
+ ta := &Table{
+ Schema: schema,
+ Name: name,
+ Columns: make([]TableColumn, 0, 16),
+ Indexes: make([]*Index, 0, 8),
+ }
+
+ if err := ta.fetchColumnsViaSqlDB(conn); err != nil {
+ return nil, errors.Trace(err)
+ }
+
+ if err := ta.fetchIndexesViaSqlDB(conn); err != nil {
+ return nil, errors.Trace(err)
+ }
+
+ return ta, nil
+}
+
func NewTable(conn mysql.Executer, schema string, name string) (*Table, error) {
ta := &Table{
Schema: schema,
@@ -151,18 +199,18 @@ func NewTable(conn mysql.Executer, schema string, name string) (*Table, error) {
}
if err := ta.fetchColumns(conn); err != nil {
- return nil, err
+ return nil, errors.Trace(err)
}
if err := ta.fetchIndexes(conn); err != nil {
- return nil, err
+ return nil, errors.Trace(err)
}
return ta, nil
}
func (ta *Table) fetchColumns(conn mysql.Executer) error {
- r, err := conn.Execute(fmt.Sprintf("describe `%s`.`%s`", ta.Schema, ta.Name))
+ r, err := conn.Execute(fmt.Sprintf("show full columns from `%s`.`%s`", ta.Schema, ta.Name))
if err != nil {
return errors.Trace(err)
}
@@ -170,14 +218,39 @@ func (ta *Table) fetchColumns(conn mysql.Executer) error {
for i := 0; i < r.RowNumber(); i++ {
name, _ := r.GetString(i, 0)
colType, _ := r.GetString(i, 1)
- extra, _ := r.GetString(i, 5)
+ collation, _ := r.GetString(i, 2)
+ extra, _ := r.GetString(i, 6)
- ta.AddColumn(name, colType, extra)
+ ta.AddColumn(name, colType, collation, extra)
}
return nil
}
+func (ta *Table) fetchColumnsViaSqlDB(conn *sql.DB) error {
+ r, err := conn.Query(fmt.Sprintf("show full columns from `%s`.`%s`", ta.Schema, ta.Name))
+ if err != nil {
+ return errors.Trace(err)
+ }
+
+ defer r.Close()
+
+ var unusedVal interface{}
+ unused := &unusedVal
+
+ for r.Next() {
+ var name, colType, extra string
+ var collation sql.NullString
+ err := r.Scan(&name, &colType, &collation, &unused, &unused, &unused, &extra, &unused, &unused)
+ if err != nil {
+ return errors.Trace(err)
+ }
+ ta.AddColumn(name, colType, collation.String, extra)
+ }
+
+ return r.Err()
+}
+
func (ta *Table) fetchIndexes(conn mysql.Executer) error {
r, err := conn.Execute(fmt.Sprintf("show index from `%s`.`%s`", ta.Schema, ta.Name))
if err != nil {
@@ -197,6 +270,87 @@ func (ta *Table) fetchIndexes(conn mysql.Executer) error {
currentIndex.AddColumn(colName, cardinality)
}
+ return ta.fetchPrimaryKeyColumns()
+
+}
+
+func (ta *Table) fetchIndexesViaSqlDB(conn *sql.DB) error {
+ r, err := conn.Query(fmt.Sprintf("show index from `%s`.`%s`", ta.Schema, ta.Name))
+ if err != nil {
+ return errors.Trace(err)
+ }
+
+ defer r.Close()
+
+ var currentIndex *Index
+ currentName := ""
+
+ var unusedVal interface{}
+ unused := &unusedVal
+
+ for r.Next() {
+ var indexName, colName string
+ var cardinality interface{}
+
+ err := r.Scan(
+ &unused,
+ &unused,
+ &indexName,
+ &unused,
+ &colName,
+ &unused,
+ &cardinality,
+ &unused,
+ &unused,
+ &unused,
+ &unused,
+ &unused,
+ &unused,
+ )
+ if err != nil {
+ return errors.Trace(err)
+ }
+
+ if currentName != indexName {
+ currentIndex = ta.AddIndex(indexName)
+ currentName = indexName
+ }
+
+ c := toUint64(cardinality)
+ currentIndex.AddColumn(colName, c)
+ }
+
+ return ta.fetchPrimaryKeyColumns()
+}
+
+func toUint64(i interface{}) uint64 {
+ switch i := i.(type) {
+ case int:
+ return uint64(i)
+ case int8:
+ return uint64(i)
+ case int16:
+ return uint64(i)
+ case int32:
+ return uint64(i)
+ case int64:
+ return uint64(i)
+ case uint:
+ return uint64(i)
+ case uint8:
+ return uint64(i)
+ case uint16:
+ return uint64(i)
+ case uint32:
+ return uint64(i)
+ case uint64:
+ return uint64(i)
+ }
+
+ return 0
+}
+
+func (ta *Table) fetchPrimaryKeyColumns() error {
if len(ta.Indexes) == 0 {
return nil
}
@@ -213,3 +367,32 @@ func (ta *Table) fetchIndexes(conn mysql.Executer) error {
return nil
}
+
+// Get primary keys in one row for a table, a table may use multi fields as the PK
+func (ta *Table) GetPKValues(row []interface{}) ([]interface{}, error) {
+ indexes := ta.PKColumns
+ if len(indexes) == 0 {
+ return nil, errors.Errorf("table %s has no PK", ta)
+ } else if len(ta.Columns) != len(row) {
+ return nil, errors.Errorf("table %s has %d columns, but row data %v len is %d", ta,
+ len(ta.Columns), row, len(row))
+ }
+
+ values := make([]interface{}, 0, len(indexes))
+
+ for _, index := range indexes {
+ values = append(values, row[index])
+ }
+
+ return values, nil
+}
+
+// Get term column's value
+func (ta *Table) GetColumnValue(column string, row []interface{}) (interface{}, error) {
+ index := ta.FindColumn(column)
+ if index == -1 {
+ return nil, errors.Errorf("table %s has no column name %s", ta, column)
+ }
+
+ return row[index], nil
+}
diff --git a/vendor/github.com/siddontang/go-mysql/schema/schema_test.go b/vendor/github.com/siddontang/go-mysql/schema/schema_test.go
index 327c622..c5bafe1 100644
--- a/vendor/github.com/siddontang/go-mysql/schema/schema_test.go
+++ b/vendor/github.com/siddontang/go-mysql/schema/schema_test.go
@@ -1,12 +1,14 @@
package schema
import (
+ "database/sql"
"flag"
"fmt"
"testing"
. "github.com/pingcap/check"
"github.com/siddontang/go-mysql/client"
+ _ "github.com/siddontang/go-mysql/driver"
)
// use docker mysql for test
@@ -17,7 +19,8 @@ func Test(t *testing.T) {
}
type schemaTestSuite struct {
- conn *client.Conn
+ conn *client.Conn
+ sqlDB *sql.DB
}
var _ = Suite(&schemaTestSuite{})
@@ -26,12 +29,19 @@ func (s *schemaTestSuite) SetUpSuite(c *C) {
var err error
s.conn, err = client.Connect(fmt.Sprintf("%s:%d", *host, 3306), "root", "", "test")
c.Assert(err, IsNil)
+
+ s.sqlDB, err = sql.Open("mysql", fmt.Sprintf("root:@%s:3306", *host))
+ c.Assert(err, IsNil)
}
func (s *schemaTestSuite) TearDownSuite(c *C) {
if s.conn != nil {
s.conn.Close()
}
+
+ if s.sqlDB != nil {
+ s.sqlDB.Close()
+ }
}
func (s *schemaTestSuite) TestSchema(c *C) {
@@ -44,10 +54,14 @@ func (s *schemaTestSuite) TestSchema(c *C) {
id1 INT,
id2 INT,
name VARCHAR(256),
- e ENUM("a", "b", "c"),
+ status ENUM('appointing','serving','abnormal','stop','noaftermarket','finish','financial_audit'),
se SET('a', 'b', 'c'),
f FLOAT,
d DECIMAL(2, 1),
+ uint INT UNSIGNED,
+ zfint INT ZEROFILL,
+ name_ucs VARCHAR(256) CHARACTER SET ucs2,
+ name_utf8 VARCHAR(256) CHARACTER SET utf8,
PRIMARY KEY(id2, id),
UNIQUE (id1),
INDEX name_idx (name)
@@ -60,15 +74,25 @@ func (s *schemaTestSuite) TestSchema(c *C) {
ta, err := NewTable(s.conn, "test", "schema_test")
c.Assert(err, IsNil)
- c.Assert(ta.Columns, HasLen, 8)
+ c.Assert(ta.Columns, HasLen, 12)
c.Assert(ta.Indexes, HasLen, 3)
c.Assert(ta.PKColumns, DeepEquals, []int{2, 0})
c.Assert(ta.Indexes[0].Columns, HasLen, 2)
c.Assert(ta.Indexes[0].Name, Equals, "PRIMARY")
c.Assert(ta.Indexes[2].Name, Equals, "name_idx")
- c.Assert(ta.Columns[4].EnumValues, DeepEquals, []string{"a", "b", "c"})
+ c.Assert(ta.Columns[4].EnumValues, DeepEquals, []string{"appointing", "serving", "abnormal", "stop", "noaftermarket", "finish", "financial_audit"})
c.Assert(ta.Columns[5].SetValues, DeepEquals, []string{"a", "b", "c"})
- c.Assert(ta.Columns[7].Type, Equals, TYPE_FLOAT)
+ c.Assert(ta.Columns[7].Type, Equals, TYPE_DECIMAL)
+ c.Assert(ta.Columns[0].IsUnsigned, IsFalse)
+ c.Assert(ta.Columns[8].IsUnsigned, IsTrue)
+ c.Assert(ta.Columns[9].IsUnsigned, IsTrue)
+ c.Assert(ta.Columns[10].Collation, Matches, "^ucs2.*")
+ c.Assert(ta.Columns[11].Collation, Matches, "^utf8.*")
+
+ taSqlDb, err := NewTableFromSqlDB(s.sqlDB, "test", "schema_test")
+ c.Assert(err, IsNil)
+
+ c.Assert(taSqlDb, DeepEquals, ta)
}
func (s *schemaTestSuite) TestQuoteSchema(c *C) {
diff --git a/vendor/github.com/siddontang/go-mysql/server/auth.go b/vendor/github.com/siddontang/go-mysql/server/auth.go
index b66ea4e..0eb54a6 100644
--- a/vendor/github.com/siddontang/go-mysql/server/auth.go
+++ b/vendor/github.com/siddontang/go-mysql/server/auth.go
@@ -2,118 +2,173 @@ package server
import (
"bytes"
- "encoding/binary"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha1"
+ "crypto/sha256"
+ "crypto/tls"
+ "fmt"
+ "github.com/juju/errors"
. "github.com/siddontang/go-mysql/mysql"
)
-func (c *Conn) writeInitialHandshake() error {
- capability := CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG |
- CLIENT_CONNECT_WITH_DB | CLIENT_PROTOCOL_41 |
- CLIENT_TRANSACTIONS | CLIENT_SECURE_CONNECTION
+var ErrAccessDenied = errors.New("access denied")
- data := make([]byte, 4, 128)
+func (c *Conn) compareAuthData(authPluginName string, clientAuthData []byte) error {
+ switch authPluginName {
+ case AUTH_NATIVE_PASSWORD:
+ if err := c.acquirePassword(); err != nil {
+ return err
+ }
+ return c.compareNativePasswordAuthData(clientAuthData, c.password)
- //min version 10
- data = append(data, 10)
+ case AUTH_CACHING_SHA2_PASSWORD:
+ if err := c.compareCacheSha2PasswordAuthData(clientAuthData); err != nil {
+ return err
+ }
+ if c.cachingSha2FullAuth {
+ return c.handleAuthSwitchResponse()
+ }
+ return nil
- //server version[00]
- data = append(data, ServerVersion...)
- data = append(data, 0)
+ case AUTH_SHA256_PASSWORD:
+ if err := c.acquirePassword(); err != nil {
+ return err
+ }
+ cont, err := c.handlePublicKeyRetrieval(clientAuthData)
+ if err != nil {
+ return err
+ }
+ if !cont {
+ return nil
+ }
+ return c.compareSha256PasswordAuthData(clientAuthData, c.password)
- //connection id
- data = append(data, byte(c.connectionID), byte(c.connectionID>>8), byte(c.connectionID>>16), byte(c.connectionID>>24))
-
- //auth-plugin-data-part-1
- data = append(data, c.salt[0:8]...)
-
- //filter [00]
- data = append(data, 0)
-
- //capability flag lower 2 bytes, using default capability here
- data = append(data, byte(capability), byte(capability>>8))
-
- //charset, utf-8 default
- data = append(data, uint8(DEFAULT_COLLATION_ID))
-
- //status
- data = append(data, byte(c.status), byte(c.status>>8))
-
- //below 13 byte may not be used
- //capability flag upper 2 bytes, using default capability here
- data = append(data, byte(capability>>16), byte(capability>>24))
-
- //filter [0x15], for wireshark dump, value is 0x15
- data = append(data, 0x15)
-
- //reserved 10 [00]
- data = append(data, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
-
- //auth-plugin-data-part-2
- data = append(data, c.salt[8:]...)
-
- //filter [00]
- data = append(data, 0)
-
- return c.WritePacket(data)
+ default:
+ return errors.Errorf("unknown authentication plugin name '%s'", authPluginName)
+ }
}
-func (c *Conn) readHandshakeResponse(password string) error {
- data, err := c.ReadPacket()
-
+func (c *Conn) acquirePassword() error {
+ password, found, err := c.credentialProvider.GetCredential(c.user)
if err != nil {
return err
}
-
- pos := 0
-
- //capability
- c.capability = binary.LittleEndian.Uint32(data[:4])
- pos += 4
-
- //skip max packet size
- pos += 4
-
- //charset, skip, if you want to use another charset, use set names
- //c.collation = CollationId(data[pos])
- pos++
-
- //skip reserved 23[00]
- pos += 23
-
- //user name
- user := string(data[pos : pos+bytes.IndexByte(data[pos:], 0)])
- pos += len(user) + 1
-
- if c.user != user {
- return NewDefaultError(ER_NO_SUCH_USER, user, c.RemoteAddr().String())
+ if !found {
+ return NewDefaultError(ER_NO_SUCH_USER, c.user, c.RemoteAddr().String())
}
-
- //auth length and auth
- authLen := int(data[pos])
- pos++
- auth := data[pos : pos+authLen]
-
- checkAuth := CalcPassword(c.salt, []byte(password))
-
- if !bytes.Equal(auth, checkAuth) {
- return NewDefaultError(ER_ACCESS_DENIED_ERROR, c.RemoteAddr().String(), c.user, "Yes")
- }
-
- pos += authLen
-
- if c.capability|CLIENT_CONNECT_WITH_DB > 0 {
- if len(data[pos:]) == 0 {
- return nil
- }
-
- db := string(data[pos : pos+bytes.IndexByte(data[pos:], 0)])
- pos += len(db) + 1
-
- if err = c.h.UseDB(db); err != nil {
- return err
- }
- }
-
+ c.password = password
+ return nil
+}
+
+func scrambleValidation(cached, nonce, scramble []byte) bool {
+ // SHA256(SHA256(SHA256(STORED_PASSWORD)), NONCE)
+ crypt := sha256.New()
+ crypt.Write(cached)
+ crypt.Write(nonce)
+ message2 := crypt.Sum(nil)
+ // SHA256(PASSWORD)
+ if len(message2) != len(scramble) {
+ return false
+ }
+ for i := range message2 {
+ message2[i] ^= scramble[i]
+ }
+ // SHA256(SHA256(PASSWORD)
+ crypt.Reset()
+ crypt.Write(message2)
+ m := crypt.Sum(nil)
+ return bytes.Equal(m, cached)
+}
+
+func (c *Conn) compareNativePasswordAuthData(clientAuthData []byte, password string) error {
+ if bytes.Equal(CalcPassword(c.salt, []byte(c.password)), clientAuthData) {
+ return nil
+ }
+ return ErrAccessDenied
+}
+
+func (c *Conn) compareSha256PasswordAuthData(clientAuthData []byte, password string) error {
+ // Empty passwords are not hashed, but sent as empty string
+ if len(clientAuthData) == 0 {
+ if password == "" {
+ return nil
+ }
+ return ErrAccessDenied
+ }
+ if tlsConn, ok := c.Conn.Conn.(*tls.Conn); ok {
+ if !tlsConn.ConnectionState().HandshakeComplete {
+ return errors.New("incomplete TSL handshake")
+ }
+ // connection is SSL/TLS, client should send plain password
+ // deal with the trailing \NUL added for plain text password received
+ if l := len(clientAuthData); l != 0 && clientAuthData[l-1] == 0x00 {
+ clientAuthData = clientAuthData[:l-1]
+ }
+ if bytes.Equal(clientAuthData, []byte(password)) {
+ return nil
+ }
+ return ErrAccessDenied
+ } else {
+ // client should send encrypted password
+ // decrypt
+ dbytes, err := rsa.DecryptOAEP(sha1.New(), rand.Reader, (c.serverConf.tlsConfig.Certificates[0].PrivateKey).(*rsa.PrivateKey), clientAuthData, nil)
+ if err != nil {
+ return err
+ }
+ plain := make([]byte, len(password)+1)
+ copy(plain, password)
+ for i := range plain {
+ j := i % len(c.salt)
+ plain[i] ^= c.salt[j]
+ }
+ if bytes.Equal(plain, dbytes) {
+ return nil
+ }
+ return ErrAccessDenied
+ }
+}
+
+func (c *Conn) compareCacheSha2PasswordAuthData(clientAuthData []byte) error {
+ // Empty passwords are not hashed, but sent as empty string
+ if len(clientAuthData) == 0 {
+ if err := c.acquirePassword(); err != nil {
+ return err
+ }
+ if c.password == "" {
+ return nil
+ }
+ return ErrAccessDenied
+ }
+ // the caching of 'caching_sha2_password' in MySQL, see: https://dev.mysql.com/worklog/task/?id=9591
+ if _, ok := c.credentialProvider.(*InMemoryProvider); ok {
+ // since we have already kept the password in memory and calculate the scramble is not that high of cost, we eliminate
+ // the caching part. So our server will never ask the client to do a full authentication via RSA key exchange and it appears
+ // like the auth will always hit the cache.
+ if err := c.acquirePassword(); err != nil {
+ return err
+ }
+ if bytes.Equal(CalcCachingSha2Password(c.salt, c.password), clientAuthData) {
+ // 'fast' auth: write "More data" packet (first byte == 0x01) with the second byte = 0x03
+ return c.writeAuthMoreDataFastAuth()
+ }
+ return ErrAccessDenied
+ }
+ // other type of credential provider, we use the cache
+ cached, ok := c.serverConf.cacheShaPassword.Load(fmt.Sprintf("%s@%s", c.user, c.Conn.LocalAddr()))
+ if ok {
+ // Scramble validation
+ if scrambleValidation(cached.([]byte), c.salt, clientAuthData) {
+ // 'fast' auth: write "More data" packet (first byte == 0x01) with the second byte = 0x03
+ return c.writeAuthMoreDataFastAuth()
+ }
+ return ErrAccessDenied
+ }
+ // cache miss, do full auth
+ if err := c.writeAuthMoreDataFullAuth(); err != nil {
+ return err
+ }
+ c.cachingSha2FullAuth = true
return nil
}
diff --git a/vendor/github.com/siddontang/go-mysql/server/auth_switch_response.go b/vendor/github.com/siddontang/go-mysql/server/auth_switch_response.go
new file mode 100644
index 0000000..038acff
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/server/auth_switch_response.go
@@ -0,0 +1,133 @@
+package server
+
+import (
+ "bytes"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha1"
+ "crypto/sha256"
+ "crypto/tls"
+ "fmt"
+
+ "github.com/juju/errors"
+ . "github.com/siddontang/go-mysql/mysql"
+)
+
+func (c *Conn) handleAuthSwitchResponse() error {
+ authData, err := c.readAuthSwitchRequestResponse()
+ if err != nil {
+ return err
+ }
+
+ switch c.authPluginName {
+ case AUTH_NATIVE_PASSWORD:
+ if err := c.acquirePassword(); err != nil {
+ return err
+ }
+ if !bytes.Equal(CalcPassword(c.salt, []byte(c.password)), authData) {
+ return ErrAccessDenied
+ }
+ return nil
+
+ case AUTH_CACHING_SHA2_PASSWORD:
+ if !c.cachingSha2FullAuth {
+ // Switched auth method but no MoreData packet send yet
+ if err := c.compareCacheSha2PasswordAuthData(authData); err != nil {
+ return err
+ } else {
+ if c.cachingSha2FullAuth {
+ return c.handleAuthSwitchResponse()
+ }
+ return nil
+ }
+ }
+ // AuthMoreData packet already sent, do full auth
+ if err := c.handleCachingSha2PasswordFullAuth(authData); err != nil {
+ return err
+ }
+ c.writeCachingSha2Cache()
+ return nil
+
+ case AUTH_SHA256_PASSWORD:
+ cont, err := c.handlePublicKeyRetrieval(authData)
+ if err != nil {
+ return err
+ }
+ if !cont {
+ return nil
+ }
+ if err := c.acquirePassword(); err != nil {
+ return err
+ }
+ return c.compareSha256PasswordAuthData(authData, c.password)
+
+ default:
+ return errors.Errorf("unknown authentication plugin name '%s'", c.authPluginName)
+ }
+}
+
+func (c *Conn) handleCachingSha2PasswordFullAuth(authData []byte) error {
+ if err := c.acquirePassword(); err != nil {
+ return err
+ }
+ if tlsConn, ok := c.Conn.Conn.(*tls.Conn); ok {
+ if !tlsConn.ConnectionState().HandshakeComplete {
+ return errors.New("incomplete TSL handshake")
+ }
+ // connection is SSL/TLS, client should send plain password
+ // deal with the trailing \NUL added for plain text password received
+ if l := len(authData); l != 0 && authData[l-1] == 0x00 {
+ authData = authData[:l-1]
+ }
+ if bytes.Equal(authData, []byte(c.password)) {
+ return nil
+ }
+ return ErrAccessDenied
+ } else {
+ // client either request for the public key or send the encrypted password
+ if len(authData) == 1 && authData[0] == 0x02 {
+ // send the public key
+ if err := c.writeAuthMoreDataPubkey(); err != nil {
+ return err
+ }
+ // read the encrypted password
+ var err error
+ if authData, err = c.readAuthSwitchRequestResponse(); err != nil {
+ return err
+ }
+ }
+ // the encrypted password
+ // decrypt
+ dbytes, err := rsa.DecryptOAEP(sha1.New(), rand.Reader, (c.serverConf.tlsConfig.Certificates[0].PrivateKey).(*rsa.PrivateKey), authData, nil)
+ if err != nil {
+ return err
+ }
+ plain := make([]byte, len(c.password)+1)
+ copy(plain, c.password)
+ for i := range plain {
+ j := i % len(c.salt)
+ plain[i] ^= c.salt[j]
+ }
+ if bytes.Equal(plain, dbytes) {
+ return nil
+ }
+ return ErrAccessDenied
+ }
+}
+
+func (c *Conn) writeCachingSha2Cache() {
+ // write cache
+ if c.password == "" {
+ return
+ }
+ // SHA256(PASSWORD)
+ crypt := sha256.New()
+ crypt.Write([]byte(c.password))
+ m1 := crypt.Sum(nil)
+ // SHA256(SHA256(PASSWORD))
+ crypt.Reset()
+ crypt.Write(m1)
+ m2 := crypt.Sum(nil)
+ // caching_sha2_password will maintain an in-memory hash of `user`@`host` => SHA256(SHA256(PASSWORD))
+ c.serverConf.cacheShaPassword.Store(fmt.Sprintf("%s@%s", c.user, c.Conn.LocalAddr()), m2)
+}
diff --git a/vendor/github.com/siddontang/go-mysql/server/caching_sha2_cache_test.go b/vendor/github.com/siddontang/go-mysql/server/caching_sha2_cache_test.go
new file mode 100644
index 0000000..a8139eb
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/server/caching_sha2_cache_test.go
@@ -0,0 +1,233 @@
+package server
+
+import (
+ "database/sql"
+ "fmt"
+ "net"
+ "strings"
+ "sync"
+ "testing"
+ "time"
+
+ _ "github.com/go-sql-driver/mysql"
+ "github.com/juju/errors"
+ . "github.com/pingcap/check"
+ "github.com/siddontang/go-log/log"
+ "github.com/siddontang/go-mysql/mysql"
+ "github.com/siddontang/go-mysql/test_util/test_keys"
+)
+
+var delay = 50
+
+// test caching for 'caching_sha2_password'
+// NOTE the idea here is to plugin a throttled credential provider so that the first connection (cache miss) will take longer time
+// than the second connection (cache hit). Remember to set the password for MySQL user otherwise it won't cache empty password.
+func TestCachingSha2Cache(t *testing.T) {
+ log.SetLevel(log.LevelDebug)
+
+ remoteProvider := &RemoteThrottleProvider{NewInMemoryProvider(), delay + 50}
+ remoteProvider.AddUser(*testUser, *testPassword)
+ cacheServer := NewServer("8.0.12", mysql.DEFAULT_COLLATION_ID, mysql.AUTH_CACHING_SHA2_PASSWORD, test_keys.PubPem, tlsConf)
+
+ // no TLS
+ Suite(&cacheTestSuite{
+ server: cacheServer,
+ credProvider: remoteProvider,
+ tlsPara: "false",
+ })
+
+ TestingT(t)
+}
+
+func TestCachingSha2CacheTLS(t *testing.T) {
+ log.SetLevel(log.LevelDebug)
+
+ remoteProvider := &RemoteThrottleProvider{NewInMemoryProvider(), delay + 50}
+ remoteProvider.AddUser(*testUser, *testPassword)
+ cacheServer := NewServer("8.0.12", mysql.DEFAULT_COLLATION_ID, mysql.AUTH_CACHING_SHA2_PASSWORD, test_keys.PubPem, tlsConf)
+
+ // TLS
+ Suite(&cacheTestSuite{
+ server: cacheServer,
+ credProvider: remoteProvider,
+ tlsPara: "skip-verify",
+ })
+
+ TestingT(t)
+}
+
+type RemoteThrottleProvider struct {
+ *InMemoryProvider
+ delay int // in milliseconds
+}
+
+func (m *RemoteThrottleProvider) GetCredential(username string) (password string, found bool, err error) {
+ time.Sleep(time.Millisecond * time.Duration(m.delay))
+ return m.InMemoryProvider.GetCredential(username)
+}
+
+type cacheTestSuite struct {
+ server *Server
+ credProvider CredentialProvider
+ tlsPara string
+
+ db *sql.DB
+
+ l net.Listener
+}
+
+func (s *cacheTestSuite) SetUpSuite(c *C) {
+ var err error
+
+ s.l, err = net.Listen("tcp", *testAddr)
+ c.Assert(err, IsNil)
+
+ go s.onAccept(c)
+
+ time.Sleep(30 * time.Millisecond)
+}
+
+func (s *cacheTestSuite) TearDownSuite(c *C) {
+ if s.l != nil {
+ s.l.Close()
+ }
+}
+
+func (s *cacheTestSuite) onAccept(c *C) {
+ for {
+ conn, err := s.l.Accept()
+ if err != nil {
+ return
+ }
+
+ go s.onConn(conn, c)
+ }
+}
+
+func (s *cacheTestSuite) onConn(conn net.Conn, c *C) {
+ //co, err := NewConn(conn, *testUser, *testPassword, &testHandler{s})
+ co, err := NewCustomizedConn(conn, s.server, s.credProvider, &testCacheHandler{s})
+ c.Assert(err, IsNil)
+ for {
+ err = co.HandleCommand()
+ if err != nil {
+ return
+ }
+ }
+}
+
+func (s *cacheTestSuite) runSelect(c *C) {
+ var a int64
+ var b string
+
+ err := s.db.QueryRow("SELECT a, b FROM tbl WHERE id=1").Scan(&a, &b)
+ c.Assert(err, IsNil)
+ c.Assert(a, Equals, int64(1))
+ c.Assert(b, Equals, "hello world")
+}
+
+func (s *cacheTestSuite) TestCache(c *C) {
+ // first connection
+ t1 := time.Now()
+ var err error
+ s.db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?tls=%s", *testUser, *testPassword, *testAddr, *testDB, s.tlsPara))
+ c.Assert(err, IsNil)
+ s.db.SetMaxIdleConns(4)
+ s.runSelect(c)
+ t2 := time.Now()
+
+ d1 := int(t2.Sub(t1).Nanoseconds() / 1e6)
+ //log.Debugf("first connection took %d milliseconds", d1)
+
+ c.Assert(d1, GreaterEqual, delay)
+
+ if s.db != nil {
+ s.db.Close()
+ }
+
+ // second connection
+ t3 := time.Now()
+ s.db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?tls=%s", *testUser, *testPassword, *testAddr, *testDB, s.tlsPara))
+ c.Assert(err, IsNil)
+ s.db.SetMaxIdleConns(4)
+ s.runSelect(c)
+ t4 := time.Now()
+
+ d2 := int(t4.Sub(t3).Nanoseconds() / 1e6)
+ //log.Debugf("second connection took %d milliseconds", d2)
+
+ c.Assert(d2, Less, delay)
+ if s.db != nil {
+ s.db.Close()
+ }
+
+ s.server.cacheShaPassword = &sync.Map{}
+}
+
+type testCacheHandler struct {
+ s *cacheTestSuite
+}
+
+func (h *testCacheHandler) UseDB(dbName string) error {
+ return nil
+}
+
+func (h *testCacheHandler) handleQuery(query string, binary bool) (*mysql.Result, error) {
+ ss := strings.Split(query, " ")
+ switch strings.ToLower(ss[0]) {
+ case "select":
+ var r *mysql.Resultset
+ var err error
+ //for handle go mysql driver select @@max_allowed_packet
+ if strings.Contains(strings.ToLower(query), "max_allowed_packet") {
+ r, err = mysql.BuildSimpleResultset([]string{"@@max_allowed_packet"}, [][]interface{}{
+ {mysql.MaxPayloadLen},
+ }, binary)
+ } else {
+ r, err = mysql.BuildSimpleResultset([]string{"a", "b"}, [][]interface{}{
+ {1, "hello world"},
+ }, binary)
+ }
+
+ if err != nil {
+ return nil, errors.Trace(err)
+ } else {
+ return &mysql.Result{0, 0, 0, r}, nil
+ }
+ case "insert":
+ return &mysql.Result{0, 1, 0, nil}, nil
+ case "delete":
+ return &mysql.Result{0, 0, 1, nil}, nil
+ case "update":
+ return &mysql.Result{0, 0, 1, nil}, nil
+ case "replace":
+ return &mysql.Result{0, 0, 1, nil}, nil
+ default:
+ return nil, fmt.Errorf("invalid query %s", query)
+ }
+
+ return nil, nil
+}
+
+func (h *testCacheHandler) HandleQuery(query string) (*mysql.Result, error) {
+ return h.handleQuery(query, false)
+}
+
+func (h *testCacheHandler) HandleFieldList(table string, fieldWildcard string) ([]*mysql.Field, error) {
+ return nil, nil
+}
+func (h *testCacheHandler) HandleStmtPrepare(sql string) (params int, columns int, ctx interface{}, err error) {
+ return 0, 0, nil, nil
+}
+
+func (h *testCacheHandler) HandleStmtClose(context interface{}) error {
+ return nil
+}
+
+func (h *testCacheHandler) HandleStmtExecute(ctx interface{}, query string, args []interface{}) (*mysql.Result, error) {
+ return h.handleQuery(query, true)
+}
+
+func (h *testCacheHandler) HandleOtherCommand(cmd byte, data []byte) error {
+ return mysql.NewError(mysql.ER_UNKNOWN_ERROR, fmt.Sprintf("command %d is not supported now", cmd))
+}
diff --git a/vendor/github.com/siddontang/go-mysql/server/command.go b/vendor/github.com/siddontang/go-mysql/server/command.go
index fb7dcdf..6c8d13a 100644
--- a/vendor/github.com/siddontang/go-mysql/server/command.go
+++ b/vendor/github.com/siddontang/go-mysql/server/command.go
@@ -11,8 +11,8 @@ import (
type Handler interface {
//handle COM_INIT_DB command, you can check whether the dbName is valid, or other.
UseDB(dbName string) error
- //handle COM_QUERY comamnd, like SELECT, INSERT, UPDATE, etc...
- //If Result has a Resultset (SELECT, SHOW, etc...), we will send this as the repsonse, otherwise, we will send Result
+ //handle COM_QUERY command, like SELECT, INSERT, UPDATE, etc...
+ //If Result has a Resultset (SELECT, SHOW, etc...), we will send this as the response, otherwise, we will send Result
HandleQuery(query string) (*Result, error)
//handle COM_FILED_LIST command
HandleFieldList(table string, fieldWildcard string) ([]*Field, error)
@@ -25,6 +25,9 @@ type Handler interface {
//handle COM_STMT_CLOSE, context is the previous one set in prepare
//this handler has no response
HandleStmtClose(context interface{}) error
+ //handle any other command that is not currently handled by the library,
+ //default implementation for this method will return an ER_UNKNOWN_ERROR
+ HandleOtherCommand(cmd byte, data []byte) error
}
func (c *Conn) HandleCommand() error {
@@ -119,8 +122,7 @@ func (c *Conn) dispatch(data []byte) interface{} {
return r
}
default:
- msg := fmt.Sprintf("command %d is not supported now", cmd)
- return NewError(ER_UNKNOWN_ERROR, msg)
+ return c.h.HandleOtherCommand(cmd, data)
}
return fmt.Errorf("command %d is not handled correctly", cmd)
@@ -149,3 +151,10 @@ func (h EmptyHandler) HandleStmtExecute(context interface{}, query string, args
func (h EmptyHandler) HandleStmtClose(context interface{}) error {
return nil
}
+
+func (h EmptyHandler) HandleOtherCommand(cmd byte, data []byte) error {
+ return NewError(
+ ER_UNKNOWN_ERROR,
+ fmt.Sprintf("command %d is not supported now", cmd),
+ )
+}
diff --git a/vendor/github.com/siddontang/go-mysql/server/command_test.go b/vendor/github.com/siddontang/go-mysql/server/command_test.go
new file mode 100644
index 0000000..34b034e
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/server/command_test.go
@@ -0,0 +1,4 @@
+package server
+
+// Ensure EmptyHandler implements Handler interface or cause compile time error
+var _ Handler = EmptyHandler{}
diff --git a/vendor/github.com/siddontang/go-mysql/server/conn.go b/vendor/github.com/siddontang/go-mysql/server/conn.go
index d6ea846..a279b93 100644
--- a/vendor/github.com/siddontang/go-mysql/server/conn.go
+++ b/vendor/github.com/siddontang/go-mysql/server/conn.go
@@ -15,15 +15,17 @@ import (
type Conn struct {
*packet.Conn
- capability uint32
+ serverConf *Server
+ capability uint32
+ authPluginName string
+ connectionID uint32
+ status uint16
+ salt []byte // should be 8 + 12 for auth-plugin-data-part-1 and auth-plugin-data-part-2
- connectionID uint32
-
- status uint16
-
- user string
-
- salt []byte
+ credentialProvider CredentialProvider
+ user string
+ password string
+ cachingSha2FullAuth bool
h Handler
@@ -35,23 +37,23 @@ type Conn struct {
var baseConnID uint32 = 10000
+// create connection with default server settings
func NewConn(conn net.Conn, user string, password string, h Handler) (*Conn, error) {
- c := new(Conn)
-
- c.h = h
-
- c.user = user
- c.Conn = packet.NewConn(conn)
-
- c.connectionID = atomic.AddUint32(&baseConnID, 1)
-
- c.stmts = make(map[uint32]*Stmt)
-
- c.salt, _ = RandomBuf(20)
-
+ p := NewInMemoryProvider()
+ p.AddUser(user, password)
+ salt, _ := RandomBuf(20)
+ c := &Conn{
+ Conn: packet.NewConn(conn),
+ serverConf: defaultServer,
+ credentialProvider: p,
+ h: h,
+ connectionID: atomic.AddUint32(&baseConnID, 1),
+ stmts: make(map[uint32]*Stmt),
+ salt: salt,
+ }
c.closed.Set(false)
- if err := c.handshake(password); err != nil {
+ if err := c.handshake(); err != nil {
c.Close()
return nil, err
}
@@ -59,14 +61,38 @@ func NewConn(conn net.Conn, user string, password string, h Handler) (*Conn, err
return c, nil
}
-func (c *Conn) handshake(password string) error {
+// create connection with customized server settings
+func NewCustomizedConn(conn net.Conn, serverConf *Server, p CredentialProvider, h Handler) (*Conn, error) {
+ salt, _ := RandomBuf(20)
+ c := &Conn{
+ Conn: packet.NewConn(conn),
+ serverConf: serverConf,
+ credentialProvider: p,
+ h: h,
+ connectionID: atomic.AddUint32(&baseConnID, 1),
+ stmts: make(map[uint32]*Stmt),
+ salt: salt,
+ }
+ c.closed.Set(false)
+
+ if err := c.handshake(); err != nil {
+ c.Close()
+ return nil, err
+ }
+
+ return c, nil
+}
+
+func (c *Conn) handshake() error {
if err := c.writeInitialHandshake(); err != nil {
return err
}
- if err := c.readHandshakeResponse(password); err != nil {
+ if err := c.readHandshakeResponse(); err != nil {
+ if err == ErrAccessDenied {
+ err = NewDefaultError(ER_ACCESS_DENIED_ERROR, c.user, c.LocalAddr().String(), "Yes")
+ }
c.writeError(err)
-
return err
}
diff --git a/vendor/github.com/siddontang/go-mysql/server/credential_provider.go b/vendor/github.com/siddontang/go-mysql/server/credential_provider.go
new file mode 100644
index 0000000..3d44eb0
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/server/credential_provider.go
@@ -0,0 +1,45 @@
+package server
+
+import "sync"
+
+// interface for user credential provider
+// hint: can be extended for more functionality
+// =================================IMPORTANT NOTE===============================
+// if the password in a third-party credential provider could be updated at runtime, we have to invalidate the caching
+// for 'caching_sha2_password' by calling 'func (s *Server)InvalidateCache(string, string)'.
+type CredentialProvider interface {
+ // check if the user exists
+ CheckUsername(username string) (bool, error)
+ // get user credential
+ GetCredential(username string) (password string, found bool, err error)
+}
+
+func NewInMemoryProvider() *InMemoryProvider {
+ return &InMemoryProvider{
+ userPool: sync.Map{},
+ }
+}
+
+// implements a in memory credential provider
+type InMemoryProvider struct {
+ userPool sync.Map // username -> password
+}
+
+func (m *InMemoryProvider) CheckUsername(username string) (found bool, err error) {
+ _, ok := m.userPool.Load(username)
+ return ok, nil
+}
+
+func (m *InMemoryProvider) GetCredential(username string) (password string, found bool, err error) {
+ v, ok := m.userPool.Load(username)
+ if !ok {
+ return "", false, nil
+ }
+ return v.(string), true, nil
+}
+
+func (m *InMemoryProvider) AddUser(username, password string) {
+ m.userPool.Store(username, password)
+}
+
+type Provider InMemoryProvider
diff --git a/vendor/github.com/siddontang/go-mysql/server/example/server_example.go b/vendor/github.com/siddontang/go-mysql/server/example/server_example.go
new file mode 100644
index 0000000..1efa1a3
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/server/example/server_example.go
@@ -0,0 +1,51 @@
+package main
+
+import (
+ "net"
+
+ "github.com/siddontang/go-log/log"
+ "github.com/siddontang/go-mysql/mysql"
+ "github.com/siddontang/go-mysql/server"
+ "github.com/siddontang/go-mysql/test_util/test_keys"
+
+ "crypto/tls"
+ "time"
+)
+
+type RemoteThrottleProvider struct {
+ *server.InMemoryProvider
+ delay int // in milliseconds
+}
+
+func (m *RemoteThrottleProvider) GetCredential(username string) (password string, found bool, err error) {
+ time.Sleep(time.Millisecond * time.Duration(m.delay))
+ return m.InMemoryProvider.GetCredential(username)
+}
+
+func main() {
+ l, _ := net.Listen("tcp", "127.0.0.1:3306")
+ // user either the in-memory credential provider or the remote credential provider (you can implement your own)
+ //inMemProvider := server.NewInMemoryProvider()
+ //inMemProvider.AddUser("root", "123")
+ remoteProvider := &RemoteThrottleProvider{server.NewInMemoryProvider(), 10 + 50}
+ remoteProvider.AddUser("root", "123")
+ var tlsConf = server.NewServerTLSConfig(test_keys.CaPem, test_keys.CertPem, test_keys.KeyPem, tls.VerifyClientCertIfGiven)
+ for {
+ c, _ := l.Accept()
+ go func() {
+ // Create a connection with user root and an empty password.
+ // You can use your own handler to handle command here.
+ svr := server.NewServer("8.0.12", mysql.DEFAULT_COLLATION_ID, mysql.AUTH_CACHING_SHA2_PASSWORD, test_keys.PubPem, tlsConf)
+ conn, err := server.NewCustomizedConn(c, svr, remoteProvider, server.EmptyHandler{})
+
+ if err != nil {
+ log.Errorf("Connection error: %v", err)
+ return
+ }
+
+ for {
+ conn.HandleCommand()
+ }
+ }()
+ }
+}
diff --git a/vendor/github.com/siddontang/go-mysql/server/handshake_resp.go b/vendor/github.com/siddontang/go-mysql/server/handshake_resp.go
new file mode 100644
index 0000000..79af6f2
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/server/handshake_resp.go
@@ -0,0 +1,190 @@
+package server
+
+import (
+ "bytes"
+ "crypto/tls"
+ "encoding/binary"
+
+ "github.com/juju/errors"
+ . "github.com/siddontang/go-mysql/mysql"
+)
+
+func (c *Conn) readHandshakeResponse() error {
+ data, pos, err := c.readFirstPart()
+ if err != nil {
+ return err
+ }
+ if pos, err = c.readUserName(data, pos); err != nil {
+ return err
+ }
+ authData, authLen, pos, err := c.readAuthData(data, pos)
+ if err != nil {
+ return err
+ }
+
+ pos += authLen
+
+ if pos, err = c.readDb(data, pos); err != nil {
+ return err
+ }
+
+ pos = c.readPluginName(data, pos)
+
+ cont, err := c.handleAuthMatch(authData, pos)
+ if err != nil {
+ return err
+ }
+ if !cont {
+ return nil
+ }
+
+ // ignore connect attrs for now, the proxy does not support passing attrs to actual MySQL server
+
+ // try to authenticate the client
+ return c.compareAuthData(c.authPluginName, authData)
+}
+
+func (c *Conn) readFirstPart() ([]byte, int, error) {
+ data, err := c.ReadPacket()
+ if err != nil {
+ return nil, 0, err
+ }
+
+ pos := 0
+
+ // check CLIENT_PROTOCOL_41
+ if uint32(binary.LittleEndian.Uint16(data[:2]))&CLIENT_PROTOCOL_41 == 0 {
+ return nil, 0, errors.New("CLIENT_PROTOCOL_41 compatible client is required")
+ }
+
+ //capability
+ c.capability = binary.LittleEndian.Uint32(data[:4])
+ if c.capability&CLIENT_SECURE_CONNECTION == 0 {
+ return nil, 0, errors.New("CLIENT_SECURE_CONNECTION compatible client is required")
+ }
+ pos += 4
+
+ //skip max packet size
+ pos += 4
+
+ //charset, skip, if you want to use another charset, use set names
+ //c.collation = CollationId(data[pos])
+ pos++
+
+ //skip reserved 23[00]
+ pos += 23
+
+ // is this a SSLRequest packet?
+ if len(data) == (4 + 4 + 1 + 23) {
+ if c.serverConf.capability&CLIENT_SSL == 0 {
+ return nil, 0, errors.Errorf("The host '%s' does not support SSL connections", c.RemoteAddr().String())
+ }
+ // switch to TLS
+ tlsConn := tls.Server(c.Conn.Conn, c.serverConf.tlsConfig)
+ if err := tlsConn.Handshake(); err != nil {
+ return nil, 0, err
+ }
+ c.Conn.Conn = tlsConn
+
+ // mysql handshake again
+ return c.readFirstPart()
+ }
+ return data, pos, nil
+}
+
+func (c *Conn) readUserName(data []byte, pos int) (int, error) {
+ //user name
+ user := string(data[pos : pos+bytes.IndexByte(data[pos:], 0x00)])
+ pos += len(user) + 1
+ c.user = user
+ return pos, nil
+}
+
+func (c *Conn) readDb(data []byte, pos int) (int, error) {
+ if c.capability&CLIENT_CONNECT_WITH_DB != 0 {
+ if len(data[pos:]) == 0 {
+ return pos, nil
+ }
+
+ db := string(data[pos : pos+bytes.IndexByte(data[pos:], 0x00)])
+ pos += len(db) + 1
+
+ if err := c.h.UseDB(db); err != nil {
+ return 0, err
+ }
+ }
+ return pos, nil
+}
+
+func (c *Conn) readPluginName(data []byte, pos int) int {
+ if c.capability&CLIENT_PLUGIN_AUTH != 0 {
+ c.authPluginName = string(data[pos : pos+bytes.IndexByte(data[pos:], 0x00)])
+ pos += len(c.authPluginName)
+ } else {
+ // The method used is Native Authentication if both CLIENT_PROTOCOL_41 and CLIENT_SECURE_CONNECTION are set,
+ // but CLIENT_PLUGIN_AUTH is not set, so we fallback to 'mysql_native_password'
+ c.authPluginName = AUTH_NATIVE_PASSWORD
+ }
+ return pos
+}
+
+func (c *Conn) readAuthData(data []byte, pos int) ([]byte, int, int, error) {
+ // length encoded data
+ var auth []byte
+ var authLen int
+ if c.capability&CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA != 0 {
+ authData, isNULL, readBytes, err := LengthEncodedString(data[pos:])
+ if err != nil {
+ return nil, 0, 0, err
+ }
+ if isNULL {
+ // no auth length and no auth data, just \NUL, considered invalid auth data, and reject connection as MySQL does
+ return nil, 0, 0, NewDefaultError(ER_ACCESS_DENIED_ERROR, c.LocalAddr().String(), c.user, "Yes")
+ }
+ auth = authData
+ authLen = readBytes
+ } else {
+ //auth length and auth
+ authLen = int(data[pos])
+ pos++
+ auth = data[pos : pos+authLen]
+ if authLen == 0 {
+ // skip the next \NUL in case the password is empty
+ pos++
+ }
+ }
+ return auth, authLen, pos, nil
+}
+
+// Public Key Retrieval
+// See: https://dev.mysql.com/doc/internals/en/public-key-retrieval.html
+func (c *Conn) handlePublicKeyRetrieval(authData []byte) (bool, error) {
+ // if the client use 'sha256_password' auth method, and request for a public key
+ // we send back a keyfile with Protocol::AuthMoreData
+ if c.authPluginName == AUTH_SHA256_PASSWORD && len(authData) == 1 && authData[0] == 0x01 {
+ if c.serverConf.capability&CLIENT_SSL == 0 {
+ return false, errors.New("server does not support SSL: CLIENT_SSL not enabled")
+ }
+ if err := c.writeAuthMoreDataPubkey(); err != nil {
+ return false, err
+ }
+
+ return false, c.handleAuthSwitchResponse()
+ }
+ return true, nil
+}
+
+func (c *Conn) handleAuthMatch(authData []byte, pos int) (bool, error) {
+ // if the client responds the handshake with a different auth method, the server will send the AuthSwitchRequest packet
+ // to the client to ask the client to switch.
+
+ if c.authPluginName != c.serverConf.defaultAuthMethod {
+ if err := c.writeAuthSwitchRequest(c.serverConf.defaultAuthMethod); err != nil {
+ return false, err
+ }
+ c.authPluginName = c.serverConf.defaultAuthMethod
+ // handle AuthSwitchResponse
+ return false, c.handleAuthSwitchResponse()
+ }
+ return true, nil
+}
diff --git a/vendor/github.com/siddontang/go-mysql/server/initial_handshake.go b/vendor/github.com/siddontang/go-mysql/server/initial_handshake.go
new file mode 100644
index 0000000..312ac2b
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/server/initial_handshake.go
@@ -0,0 +1,57 @@
+package server
+
+// see: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_v10.html
+func (c *Conn) writeInitialHandshake() error {
+ data := make([]byte, 4)
+
+ //min version 10
+ data = append(data, 10)
+
+ //server version[00]
+ data = append(data, c.serverConf.serverVersion...)
+ data = append(data, 0x00)
+
+ //connection id
+ data = append(data, byte(c.connectionID), byte(c.connectionID>>8), byte(c.connectionID>>16), byte(c.connectionID>>24))
+
+ //auth-plugin-data-part-1
+ data = append(data, c.salt[0:8]...)
+
+ //filter 0x00 byte, terminating the first part of a scramble
+ data = append(data, 0x00)
+
+ defaultFlag := c.serverConf.capability
+ //capability flag lower 2 bytes, using default capability here
+ data = append(data, byte(defaultFlag), byte(defaultFlag>>8))
+
+ //charset
+ data = append(data, c.serverConf.collationId)
+
+ //status
+ data = append(data, byte(c.status), byte(c.status>>8))
+
+ //capability flag upper 2 bytes, using default capability here
+ data = append(data, byte(defaultFlag>>16), byte(defaultFlag>>24))
+
+ // server supports CLIENT_PLUGIN_AUTH and CLIENT_SECURE_CONNECTION
+ data = append(data, byte(8+12+1))
+
+ //reserved 10 [00]
+ data = append(data, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+
+ //auth-plugin-data-part-2
+ data = append(data, c.salt[8:]...)
+ // second part of the password cipher [mininum 13 bytes],
+ // where len=MAX(13, length of auth-plugin-data - 8)
+ // add \NUL to terminate the string
+ data = append(data, 0x00)
+
+ // auth plugin name
+ data = append(data, c.serverConf.defaultAuthMethod...)
+
+ // EOF if MySQL version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2)
+ // \NUL otherwise, so we use \NUL
+ data = append(data, 0)
+
+ return c.WritePacket(data)
+}
diff --git a/vendor/github.com/siddontang/go-mysql/server/resp.go b/vendor/github.com/siddontang/go-mysql/server/resp.go
index 1123032..db86323 100644
--- a/vendor/github.com/siddontang/go-mysql/server/resp.go
+++ b/vendor/github.com/siddontang/go-mysql/server/resp.go
@@ -62,6 +62,59 @@ func (c *Conn) writeEOF() error {
return c.WritePacket(data)
}
+// see: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_switch_request.html
+func (c *Conn) writeAuthSwitchRequest(newAuthPluginName string) error {
+ data := make([]byte, 4)
+ data = append(data, EOF_HEADER)
+ data = append(data, []byte(newAuthPluginName)...)
+ data = append(data, 0x00)
+ rnd, err := RandomBuf(20)
+ if err != nil {
+ return err
+ }
+ // new auth data
+ c.salt = rnd
+ data = append(data, c.salt...)
+ // the online doc states it's a string.EOF, however, the actual MySQL server add a \NUL to the end, without it, the
+ // official MySQL client will fail.
+ data = append(data, 0x00)
+ return c.WritePacket(data)
+}
+
+// see: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_switch_response.html
+func (c *Conn) readAuthSwitchRequestResponse() ([]byte, error) {
+ data, err := c.ReadPacket()
+ if err != nil {
+ return nil, err
+ }
+ if len(data) == 1 && data[0] == 0x00 {
+ // \NUL
+ return make([]byte, 0), nil
+ }
+ return data, nil
+}
+
+func (c *Conn) writeAuthMoreDataPubkey() error {
+ data := make([]byte, 4)
+ data = append(data, MORE_DATE_HEADER)
+ data = append(data, c.serverConf.pubKey...)
+ return c.WritePacket(data)
+}
+
+func (c *Conn) writeAuthMoreDataFullAuth() error {
+ data := make([]byte, 4)
+ data = append(data, MORE_DATE_HEADER)
+ data = append(data, CACHE_SHA2_FULL_AUTH)
+ return c.WritePacket(data)
+}
+
+func (c *Conn) writeAuthMoreDataFastAuth() error {
+ data := make([]byte, 4)
+ data = append(data, MORE_DATE_HEADER)
+ data = append(data, CACHE_SHA2_FAST_AUTH)
+ return c.WritePacket(data)
+}
+
func (c *Conn) writeResultset(r *Resultset) error {
columnLen := PutLengthEncodedInt(uint64(len(r.Fields)))
diff --git a/vendor/github.com/siddontang/go-mysql/server/server_conf.go b/vendor/github.com/siddontang/go-mysql/server/server_conf.go
new file mode 100644
index 0000000..353595c
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/server/server_conf.go
@@ -0,0 +1,103 @@
+package server
+
+import (
+ "crypto/tls"
+ "fmt"
+ "sync"
+
+ . "github.com/siddontang/go-mysql/mysql"
+)
+
+var defaultServer = NewDefaultServer()
+
+// Defines a basic MySQL server with configs.
+//
+// We do not aim at implementing the whole MySQL connection suite to have the best compatibilities for the clients.
+// The MySQL server can be configured to switch auth methods covering 'mysql_old_password', 'mysql_native_password',
+// 'mysql_clear_password', 'authentication_windows_client', 'sha256_password', 'caching_sha2_password', etc.
+//
+// However, since some old auth methods are considered broken with security issues. MySQL major versions like 5.7 and 8.0 default to
+// 'mysql_native_password' or 'caching_sha2_password', and most MySQL clients should have already supported at least one of the three auth
+// methods 'mysql_native_password', 'caching_sha2_password', and 'sha256_password'. Thus here we will only support these three
+// auth methods, and use 'mysql_native_password' as default for maximum compatibility with the clients and leave the other two as
+// config options.
+//
+// The MySQL doc states that 'mysql_old_password' will be used if 'CLIENT_PROTOCOL_41' or 'CLIENT_SECURE_CONNECTION' flag is not set.
+// We choose to drop the support for insecure 'mysql_old_password' auth method and require client capability 'CLIENT_PROTOCOL_41' and 'CLIENT_SECURE_CONNECTION'
+// are set. Besides, if 'CLIENT_PLUGIN_AUTH' is not set, we fallback to 'mysql_native_password' auth method.
+type Server struct {
+ serverVersion string // e.g. "8.0.12"
+ protocolVersion int // minimal 10
+ capability uint32 // server capability flag
+ collationId uint8
+ defaultAuthMethod string // default authentication method, 'mysql_native_password'
+ pubKey []byte
+ tlsConfig *tls.Config
+ cacheShaPassword *sync.Map // 'user@host' -> SHA256(SHA256(PASSWORD))
+}
+
+// New mysql server with default settings.
+//
+// NOTES:
+// TLS support will be enabled by default with auto-generated CA and server certificates (however, you can still use
+// non-TLS connection). By default, it will verify the client certificate if present. You can enable TLS support on
+// the client side without providing a client-side certificate. So only when you need the server to verify client
+// identity for maximum security, you need to set a signed certificate for the client.
+func NewDefaultServer() *Server {
+ caPem, caKey := generateCA()
+ certPem, keyPem := generateAndSignRSACerts(caPem, caKey)
+ tlsConf := NewServerTLSConfig(caPem, certPem, keyPem, tls.VerifyClientCertIfGiven)
+ return &Server{
+ serverVersion: "5.7.0",
+ protocolVersion: 10,
+ capability: CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_CONNECT_WITH_DB | CLIENT_PROTOCOL_41 |
+ CLIENT_TRANSACTIONS | CLIENT_SECURE_CONNECTION | CLIENT_PLUGIN_AUTH | CLIENT_SSL | CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA,
+ collationId: DEFAULT_COLLATION_ID,
+ defaultAuthMethod: AUTH_NATIVE_PASSWORD,
+ pubKey: getPublicKeyFromCert(certPem),
+ tlsConfig: tlsConf,
+ cacheShaPassword: new(sync.Map),
+ }
+}
+
+// New mysql server with customized settings.
+//
+// NOTES:
+// You can control the authentication methods and TLS settings here.
+// For auth method, you can specify one of the supported methods 'mysql_native_password', 'caching_sha2_password', and 'sha256_password'.
+// The specified auth method will be enforced by the server in the connection phase. That means, client will be asked to switch auth method
+// if the supplied auth method is different from the server default.
+// And for TLS support, you can specify self-signed or CA-signed certificates and decide whether the client needs to provide
+// a signed or unsigned certificate to provide different level of security.
+func NewServer(serverVersion string, collationId uint8, defaultAuthMethod string, pubKey []byte, tlsConfig *tls.Config) *Server {
+ if !isAuthMethodSupported(defaultAuthMethod) {
+ panic(fmt.Sprintf("server authentication method '%s' is not supported", defaultAuthMethod))
+ }
+
+ //if !isAuthMethodAllowedByServer(defaultAuthMethod, allowedAuthMethods) {
+ // panic(fmt.Sprintf("default auth method is not one of the allowed auth methods"))
+ //}
+ var capFlag = CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_CONNECT_WITH_DB | CLIENT_PROTOCOL_41 |
+ CLIENT_TRANSACTIONS | CLIENT_SECURE_CONNECTION | CLIENT_PLUGIN_AUTH | CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA
+ if tlsConfig != nil {
+ capFlag |= CLIENT_SSL
+ }
+ return &Server{
+ serverVersion: serverVersion,
+ protocolVersion: 10,
+ capability: capFlag,
+ collationId: collationId,
+ defaultAuthMethod: defaultAuthMethod,
+ pubKey: pubKey,
+ tlsConfig: tlsConfig,
+ cacheShaPassword: new(sync.Map),
+ }
+}
+
+func isAuthMethodSupported(authMethod string) bool {
+ return authMethod == AUTH_NATIVE_PASSWORD || authMethod == AUTH_CACHING_SHA2_PASSWORD || authMethod == AUTH_SHA256_PASSWORD
+}
+
+func (s *Server) InvalidateCache(username string, host string) {
+ s.cacheShaPassword.Delete(fmt.Sprintf("%s@%s", username, host))
+}
diff --git a/vendor/github.com/siddontang/go-mysql/server/server_test.go b/vendor/github.com/siddontang/go-mysql/server/server_test.go
index 54bcf05..1f427fd 100644
--- a/vendor/github.com/siddontang/go-mysql/server/server_test.go
+++ b/vendor/github.com/siddontang/go-mysql/server/server_test.go
@@ -1,6 +1,7 @@
package server
import (
+ "crypto/tls"
"database/sql"
"flag"
"fmt"
@@ -12,110 +13,90 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/juju/errors"
. "github.com/pingcap/check"
- mysql "github.com/siddontang/go-mysql/mysql"
+ "github.com/siddontang/go-log/log"
+ "github.com/siddontang/go-mysql/mysql"
+ "github.com/siddontang/go-mysql/test_util/test_keys"
)
var testAddr = flag.String("addr", "127.0.0.1:4000", "MySQL proxy server address")
var testUser = flag.String("user", "root", "MySQL user")
-var testPassword = flag.String("pass", "", "MySQL password")
+var testPassword = flag.String("pass", "123456", "MySQL password")
var testDB = flag.String("db", "test", "MySQL test database")
+var tlsConf = NewServerTLSConfig(test_keys.CaPem, test_keys.CertPem, test_keys.KeyPem, tls.VerifyClientCertIfGiven)
+
+func prepareServerConf() []*Server {
+ // add default server without TLS
+ var servers = []*Server{
+ // with default TLS
+ NewDefaultServer(),
+ // for key exchange, CLIENT_SSL must be enabled for the server and if the connection is not secured with TLS
+ // server permits MYSQL_NATIVE_PASSWORD only
+ NewServer("8.0.12", mysql.DEFAULT_COLLATION_ID, mysql.AUTH_NATIVE_PASSWORD, test_keys.PubPem, tlsConf),
+ NewServer("8.0.12", mysql.DEFAULT_COLLATION_ID, mysql.AUTH_NATIVE_PASSWORD, test_keys.PubPem, tlsConf),
+ // server permits SHA256_PASSWORD only
+ NewServer("8.0.12", mysql.DEFAULT_COLLATION_ID, mysql.AUTH_SHA256_PASSWORD, test_keys.PubPem, tlsConf),
+ // server permits CACHING_SHA2_PASSWORD only
+ NewServer("8.0.12", mysql.DEFAULT_COLLATION_ID, mysql.AUTH_CACHING_SHA2_PASSWORD, test_keys.PubPem, tlsConf),
+
+ // test auth switch: server permits SHA256_PASSWORD only but sent different method MYSQL_NATIVE_PASSWORD in handshake response
+ NewServer("8.0.12", mysql.DEFAULT_COLLATION_ID, mysql.AUTH_NATIVE_PASSWORD, test_keys.PubPem, tlsConf),
+ // test auth switch: server permits CACHING_SHA2_PASSWORD only but sent different method MYSQL_NATIVE_PASSWORD in handshake response
+ NewServer("8.0.12", mysql.DEFAULT_COLLATION_ID, mysql.AUTH_NATIVE_PASSWORD, test_keys.PubPem, tlsConf),
+ // test auth switch: server permits CACHING_SHA2_PASSWORD only but sent different method SHA256_PASSWORD in handshake response
+ NewServer("8.0.12", mysql.DEFAULT_COLLATION_ID, mysql.AUTH_SHA256_PASSWORD, test_keys.PubPem, tlsConf),
+ // test auth switch: server permits MYSQL_NATIVE_PASSWORD only but sent different method SHA256_PASSWORD in handshake response
+ NewServer("8.0.12", mysql.DEFAULT_COLLATION_ID, mysql.AUTH_SHA256_PASSWORD, test_keys.PubPem, tlsConf),
+ // test auth switch: server permits SHA256_PASSWORD only but sent different method CACHING_SHA2_PASSWORD in handshake response
+ NewServer("8.0.12", mysql.DEFAULT_COLLATION_ID, mysql.AUTH_CACHING_SHA2_PASSWORD, test_keys.PubPem, tlsConf),
+ // test auth switch: server permits MYSQL_NATIVE_PASSWORD only but sent different method CACHING_SHA2_PASSWORD in handshake response
+ NewServer("8.0.12", mysql.DEFAULT_COLLATION_ID, mysql.AUTH_CACHING_SHA2_PASSWORD, test_keys.PubPem, tlsConf),
+ }
+ return servers
+}
+
func Test(t *testing.T) {
+ log.SetLevel(log.LevelDebug)
+
+ // general tests
+ inMemProvider := NewInMemoryProvider()
+ inMemProvider.AddUser(*testUser, *testPassword)
+
+ servers := prepareServerConf()
+ //no TLS
+ for _, svr := range servers {
+ Suite(&serverTestSuite{
+ server: svr,
+ credProvider: inMemProvider,
+ tlsPara: "false",
+ })
+ }
+
+ // TLS if server supports
+ for _, svr := range servers {
+ if svr.tlsConfig != nil {
+ Suite(&serverTestSuite{
+ server: svr,
+ credProvider: inMemProvider,
+ tlsPara: "skip-verify",
+ })
+ }
+ }
+
TestingT(t)
}
type serverTestSuite struct {
+ server *Server
+ credProvider CredentialProvider
+
+ tlsPara string
+
db *sql.DB
l net.Listener
}
-var _ = Suite(&serverTestSuite{})
-
-type testHandler struct {
- s *serverTestSuite
-}
-
-func (h *testHandler) UseDB(dbName string) error {
- return nil
-}
-
-func (h *testHandler) handleQuery(query string, binary bool) (*mysql.Result, error) {
- ss := strings.Split(query, " ")
- switch strings.ToLower(ss[0]) {
- case "select":
- var r *mysql.Resultset
- var err error
- //for handle go mysql driver select @@max_allowed_packet
- if strings.Contains(strings.ToLower(query), "max_allowed_packet") {
- r, err = mysql.BuildSimpleResultset([]string{"@@max_allowed_packet"}, [][]interface{}{
- []interface{}{mysql.MaxPayloadLen},
- }, binary)
- } else {
- r, err = mysql.BuildSimpleResultset([]string{"a", "b"}, [][]interface{}{
- []interface{}{1, "hello world"},
- }, binary)
- }
-
- if err != nil {
- return nil, errors.Trace(err)
- } else {
- return &mysql.Result{0, 0, 0, r}, nil
- }
- case "insert":
- return &mysql.Result{0, 1, 0, nil}, nil
- case "delete":
- return &mysql.Result{0, 0, 1, nil}, nil
- case "update":
- return &mysql.Result{0, 0, 1, nil}, nil
- case "replace":
- return &mysql.Result{0, 0, 1, nil}, nil
- default:
- return nil, fmt.Errorf("invalid query %s", query)
- }
-
- return nil, nil
-}
-
-func (h *testHandler) HandleQuery(query string) (*mysql.Result, error) {
- return h.handleQuery(query, false)
-}
-
-func (h *testHandler) HandleFieldList(table string, fieldWildcard string) ([]*mysql.Field, error) {
- return nil, nil
-}
-func (h *testHandler) HandleStmtPrepare(sql string) (params int, columns int, ctx interface{}, err error) {
- ss := strings.Split(sql, " ")
- switch strings.ToLower(ss[0]) {
- case "select":
- params = 1
- columns = 2
- case "insert":
- params = 2
- columns = 0
- case "replace":
- params = 2
- columns = 0
- case "update":
- params = 1
- columns = 0
- case "delete":
- params = 1
- columns = 0
- default:
- err = fmt.Errorf("invalid prepare %s", sql)
- }
- return params, columns, nil, err
-}
-
-func (h *testHandler) HandleStmtClose(context interface{}) error {
- return nil
-}
-
-func (h *testHandler) HandleStmtExecute(ctx interface{}, query string, args []interface{}) (*mysql.Result, error) {
- return h.handleQuery(query, true)
-}
-
func (s *serverTestSuite) SetUpSuite(c *C) {
var err error
@@ -124,9 +105,9 @@ func (s *serverTestSuite) SetUpSuite(c *C) {
go s.onAccept(c)
- time.Sleep(500 * time.Millisecond)
+ time.Sleep(20 * time.Millisecond)
- s.db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s", *testUser, *testPassword, *testAddr, *testDB))
+ s.db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?tls=%s", *testUser, *testPassword, *testAddr, *testDB, s.tlsPara))
c.Assert(err, IsNil)
s.db.SetMaxIdleConns(4)
@@ -154,9 +135,10 @@ func (s *serverTestSuite) onAccept(c *C) {
}
func (s *serverTestSuite) onConn(conn net.Conn, c *C) {
- co, err := NewConn(conn, *testUser, *testPassword, &testHandler{s})
+ //co, err := NewConn(conn, *testUser, *testPassword, &testHandler{s})
+ co, err := NewCustomizedConn(conn, s.server, s.credProvider, &testHandler{s})
c.Assert(err, IsNil)
-
+ // set SSL if defined
for {
err = co.HandleCommand()
if err != nil {
@@ -228,3 +210,91 @@ func (s *serverTestSuite) TestStmtExec(c *C) {
i, _ = r.RowsAffected()
c.Assert(i, Equals, int64(1))
}
+
+type testHandler struct {
+ s *serverTestSuite
+}
+
+func (h *testHandler) UseDB(dbName string) error {
+ return nil
+}
+
+func (h *testHandler) handleQuery(query string, binary bool) (*mysql.Result, error) {
+ ss := strings.Split(query, " ")
+ switch strings.ToLower(ss[0]) {
+ case "select":
+ var r *mysql.Resultset
+ var err error
+ //for handle go mysql driver select @@max_allowed_packet
+ if strings.Contains(strings.ToLower(query), "max_allowed_packet") {
+ r, err = mysql.BuildSimpleResultset([]string{"@@max_allowed_packet"}, [][]interface{}{
+ {mysql.MaxPayloadLen},
+ }, binary)
+ } else {
+ r, err = mysql.BuildSimpleResultset([]string{"a", "b"}, [][]interface{}{
+ {1, "hello world"},
+ }, binary)
+ }
+
+ if err != nil {
+ return nil, errors.Trace(err)
+ } else {
+ return &mysql.Result{0, 0, 0, r}, nil
+ }
+ case "insert":
+ return &mysql.Result{0, 1, 0, nil}, nil
+ case "delete":
+ return &mysql.Result{0, 0, 1, nil}, nil
+ case "update":
+ return &mysql.Result{0, 0, 1, nil}, nil
+ case "replace":
+ return &mysql.Result{0, 0, 1, nil}, nil
+ default:
+ return nil, fmt.Errorf("invalid query %s", query)
+ }
+
+ return nil, nil
+}
+
+func (h *testHandler) HandleQuery(query string) (*mysql.Result, error) {
+ return h.handleQuery(query, false)
+}
+
+func (h *testHandler) HandleFieldList(table string, fieldWildcard string) ([]*mysql.Field, error) {
+ return nil, nil
+}
+func (h *testHandler) HandleStmtPrepare(sql string) (params int, columns int, ctx interface{}, err error) {
+ ss := strings.Split(sql, " ")
+ switch strings.ToLower(ss[0]) {
+ case "select":
+ params = 1
+ columns = 2
+ case "insert":
+ params = 2
+ columns = 0
+ case "replace":
+ params = 2
+ columns = 0
+ case "update":
+ params = 1
+ columns = 0
+ case "delete":
+ params = 1
+ columns = 0
+ default:
+ err = fmt.Errorf("invalid prepare %s", sql)
+ }
+ return params, columns, nil, err
+}
+
+func (h *testHandler) HandleStmtClose(context interface{}) error {
+ return nil
+}
+
+func (h *testHandler) HandleStmtExecute(ctx interface{}, query string, args []interface{}) (*mysql.Result, error) {
+ return h.handleQuery(query, true)
+}
+
+func (h *testHandler) HandleOtherCommand(cmd byte, data []byte) error {
+ return mysql.NewError(mysql.ER_UNKNOWN_ERROR, fmt.Sprintf("command %d is not supported now", cmd))
+}
\ No newline at end of file
diff --git a/vendor/github.com/siddontang/go-mysql/server/ssl.go b/vendor/github.com/siddontang/go-mysql/server/ssl.go
new file mode 100644
index 0000000..1f8a9ed
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/server/ssl.go
@@ -0,0 +1,133 @@
+package server
+
+import (
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/tls"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/pem"
+ "math/big"
+ "time"
+)
+
+// generate TLS config for server side
+// controlling the security level by authType
+func NewServerTLSConfig(caPem, certPem, keyPem []byte, authType tls.ClientAuthType) *tls.Config {
+ pool := x509.NewCertPool()
+ if !pool.AppendCertsFromPEM(caPem) {
+ panic("failed to add ca PEM")
+ }
+
+ cert, err := tls.X509KeyPair(certPem, keyPem)
+ if err != nil {
+ panic(err)
+ }
+
+ config := &tls.Config{
+ ClientAuth: authType,
+ Certificates: []tls.Certificate{cert},
+ ClientCAs: pool,
+ }
+ return config
+}
+
+// extract RSA public key from certificate
+func getPublicKeyFromCert(certPem []byte) []byte {
+ block, _ := pem.Decode(certPem)
+ crt, err := x509.ParseCertificate(block.Bytes)
+ if err != nil {
+ panic(err)
+ }
+ pubKey, err := x509.MarshalPKIXPublicKey(crt.PublicKey.(*rsa.PublicKey))
+ if err != nil {
+ panic(err)
+ }
+ return pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubKey})
+}
+
+// generate and sign RSA certificates with given CA
+// see: https://fale.io/blog/2017/06/05/create-a-pki-in-golang/
+func generateAndSignRSACerts(caPem, caKey []byte) ([]byte, []byte) {
+ // Load CA
+ catls, err := tls.X509KeyPair(caPem, caKey)
+ if err != nil {
+ panic(err)
+ }
+ ca, err := x509.ParseCertificate(catls.Certificate[0])
+ if err != nil {
+ panic(err)
+ }
+
+ // use the CA to sign certificates
+ serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
+ serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
+ if err != nil {
+ panic(err)
+ }
+ cert := &x509.Certificate{
+ SerialNumber: serialNumber,
+ Subject: pkix.Name{
+ Organization: []string{"ORGANIZATION_NAME"},
+ Country: []string{"COUNTRY_CODE"},
+ Province: []string{"PROVINCE"},
+ Locality: []string{"CITY"},
+ StreetAddress: []string{"ADDRESS"},
+ PostalCode: []string{"POSTAL_CODE"},
+ },
+ NotBefore: time.Now(),
+ NotAfter: time.Now().AddDate(10, 0, 0),
+ SubjectKeyId: []byte{1, 2, 3, 4, 6},
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
+ KeyUsage: x509.KeyUsageDigitalSignature,
+ }
+ priv, _ := rsa.GenerateKey(rand.Reader, 2048)
+
+ // sign the certificate
+ cert_b, err := x509.CreateCertificate(rand.Reader, ca, cert, &priv.PublicKey, catls.PrivateKey)
+ if err != nil {
+ panic(err)
+ }
+ certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert_b})
+ keyPem := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
+
+ return certPem, keyPem
+}
+
+// generate CA in PEM
+// see: https://github.com/golang/go/blob/master/src/crypto/tls/generate_cert.go
+func generateCA() ([]byte, []byte) {
+ serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
+ serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
+ if err != nil {
+ panic(err)
+ }
+ template := &x509.Certificate{
+ SerialNumber: serialNumber,
+ Subject: pkix.Name{
+ Organization: []string{"ORGANIZATION_NAME"},
+ Country: []string{"COUNTRY_CODE"},
+ Province: []string{"PROVINCE"},
+ Locality: []string{"CITY"},
+ StreetAddress: []string{"ADDRESS"},
+ PostalCode: []string{"POSTAL_CODE"},
+ },
+ NotBefore: time.Now(),
+ NotAfter: time.Now().AddDate(10, 0, 0),
+ IsCA: true,
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
+ KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageKeyEncipherment,
+ BasicConstraintsValid: true,
+ }
+
+ priv, _ := rsa.GenerateKey(rand.Reader, 2048)
+ derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
+ if err != nil {
+ panic(err)
+ }
+
+ caPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
+ caKey := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
+
+ return caPem, caKey
+}
diff --git a/vendor/github.com/siddontang/go-mysql/server/stmt.go b/vendor/github.com/siddontang/go-mysql/server/stmt.go
index 7a325d7..9bef23e 100644
--- a/vendor/github.com/siddontang/go-mysql/server/stmt.go
+++ b/vendor/github.com/siddontang/go-mysql/server/stmt.go
@@ -144,7 +144,7 @@ func (c *Conn) handleStmtExecute(data []byte) (*Result, error) {
}
paramTypes = data[pos : pos+(paramNum<<1)]
- pos += (paramNum << 1)
+ pos += paramNum << 1
paramValues = data[pos:]
}
@@ -211,7 +211,7 @@ func (c *Conn) bindStmtArgs(s *Stmt, nullBitmap, paramTypes, paramValues []byte)
if isUnsigned {
args[i] = uint16(binary.LittleEndian.Uint16(paramValues[pos : pos+2]))
} else {
- args[i] = int16((binary.LittleEndian.Uint16(paramValues[pos : pos+2])))
+ args[i] = int16(binary.LittleEndian.Uint16(paramValues[pos : pos+2]))
}
pos += 2
continue
@@ -270,7 +270,7 @@ func (c *Conn) bindStmtArgs(s *Stmt, nullBitmap, paramTypes, paramValues []byte)
return ErrMalformPacket
}
- v, isNull, n, err = LengthEnodedString(paramValues[pos:])
+ v, isNull, n, err = LengthEncodedString(paramValues[pos:])
pos += n
if err != nil {
return errors.Trace(err)
@@ -290,7 +290,7 @@ func (c *Conn) bindStmtArgs(s *Stmt, nullBitmap, paramTypes, paramValues []byte)
return nil
}
-// stmt send long data command has no repsonse
+// stmt send long data command has no response
func (c *Conn) handleStmtSendLongData(data []byte) error {
if len(data) < 6 {
return nil
@@ -340,7 +340,7 @@ func (c *Conn) handleStmtReset(data []byte) (*Result, error) {
return &Result{}, nil
}
-// stmt close command has no repsonse
+// stmt close command has no response
func (c *Conn) handleStmtClose(data []byte) error {
if len(data) < 4 {
return nil
diff --git a/vendor/github.com/siddontang/go-mysql/test_util/test_keys/keys.go b/vendor/github.com/siddontang/go-mysql/test_util/test_keys/keys.go
new file mode 100644
index 0000000..c1049b6
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/test_util/test_keys/keys.go
@@ -0,0 +1,85 @@
+package test_keys
+
+// here we put the testing encryption keys here
+// NOTE THIS IS FOR TESTING ONLY, DO NOT USE THEM IN PRODUCTION!
+
+var PubPem = []byte(`-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsraCori69OXEkA07Ykp2
+Ju+aHz33PqgKj0qbSbPm6ePh2mer2GWOxC4q1wdRwzgddwTTqSdonhM4XuVyyNqq
+gM7uv9JoWCONcKo28cPRK7gH7up7nYFllNFXUAA0/XQ+95tqtdITNplQLIceFIXz
+5Bvi9fThcpf9M6qKdNUa2Wd24rM/n6qxoUG2ksDDVXQC30RAHkGCdNi10iya8Pj/
+ZaEG86NXFpvvnLHRHiih/gXe7nby1sR6BxaEG2bLZd0cjdL5MuWOPeQ450H6mCtV
+SX4poNq9YrdP4XW9M0N7nocRU0p5aUvLWxy6XrUTSP0iRkC7ppEPG0p2Xtsq7QGT
+MwIDAQAB
+-----END PUBLIC KEY-----`)
+
+var CertPem = []byte(`-----BEGIN CERTIFICATE-----
+MIIDBjCCAe4CCQDg06wCf7hcuDANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMB4XDTE4MDgxOTA4NDUyNVoXDTI4MDgxNjA4NDUyNVowRTELMAkG
+A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0
+IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ALK2gqK4uvTlxJANO2JKdibvmh899z6oCo9Km0mz5unj4dpnq9hljsQuKtcHUcM4
+HXcE06knaJ4TOF7lcsjaqoDO7r/SaFgjjXCqNvHD0Su4B+7qe52BZZTRV1AANP10
+PvebarXSEzaZUCyHHhSF8+Qb4vX04XKX/TOqinTVGtlnduKzP5+qsaFBtpLAw1V0
+At9EQB5BgnTYtdIsmvD4/2WhBvOjVxab75yx0R4oof4F3u528tbEegcWhBtmy2Xd
+HI3S+TLljj3kOOdB+pgrVUl+KaDavWK3T+F1vTNDe56HEVNKeWlLy1scul61E0j9
+IkZAu6aRDxtKdl7bKu0BkzMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAma3yFqR7
+xkeaZBg4/1I3jSlaNe5+2JB4iybAkMOu77fG5zytLomTbzdhewsuBwpTVMJdga8T
+IdPeIFCin1U+5SkbjSMlpKf+krE+5CyrNJ5jAgO9ATIqx66oCTYXfGlNapGRLfSE
+sa0iMqCe/dr4GPU+flW2DZFWiyJVDSF1JjReQnfrWY+SD2SpP/lmlgltnY8MJngd
+xBLG5nsZCpUXGB713Q8ZyIm2ThVAMiskcxBleIZDDghLuhGvY/9eFJhZpvOkjWa6
+XGEi4E1G/SA+zVKFl41nHKCdqXdmIOnpcLlFBUVloQok5a95Kqc1TYw3f+WbdFff
+99dAgk3gWwWZQA==
+-----END CERTIFICATE-----`)
+
+var KeyPem = []byte(`-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAsraCori69OXEkA07Ykp2Ju+aHz33PqgKj0qbSbPm6ePh2mer
+2GWOxC4q1wdRwzgddwTTqSdonhM4XuVyyNqqgM7uv9JoWCONcKo28cPRK7gH7up7
+nYFllNFXUAA0/XQ+95tqtdITNplQLIceFIXz5Bvi9fThcpf9M6qKdNUa2Wd24rM/
+n6qxoUG2ksDDVXQC30RAHkGCdNi10iya8Pj/ZaEG86NXFpvvnLHRHiih/gXe7nby
+1sR6BxaEG2bLZd0cjdL5MuWOPeQ450H6mCtVSX4poNq9YrdP4XW9M0N7nocRU0p5
+aUvLWxy6XrUTSP0iRkC7ppEPG0p2Xtsq7QGTMwIDAQABAoIBAGh1m8hHWCg7gXh9
+838RbRx3IswuKS27hWiaQEiFWmzOIb7KqDy1qAxtu+ayRY1paHegH6QY/+Kd824s
+ibpzbgQacJ04/HrAVTVMmQ8Z2VLHoAN7lcPL1bd14aZGaLLZVtDeTDJ413grhxxv
+4ho27gcgcbo4Z+rWgk7H2WRPCAGYqWYAycm3yF5vy9QaO6edU+T588YsEQOos5iy
+5pVFSGDGZkcUp1ukL3BJYR+jvygn6WPCobQ/LScUdi+ucitaI9i+UdlLokZARVRG
+M/msqcTM73thR8yVRcexU6NUDxRBfZ/f7moSAEbBmGDXuxDcIyH9KGMQ2rMtN1X3
+lK8UNwkCgYEA2STJq/IUQHjdqd3Dqh/Q7Zm8/pMWFqLJSkqpnFtFsXPyUOx9zDOy
+KqkIfGeyKwvsj9X9BcZ0FUKj9zoct1/WpPY+h7i7+z0MIujBh4AMjAcDrt4o76yK
+UHuVmG2xKTdJoAbqOdToQeX6E82Ioal5pbB2W7AbCQScNBPZ52jxgtcCgYEA0rE7
+2dFiRm0YmuszFYxft2+GP6NgP3R2TQNEooi1uCXG2xgwObie1YCHzpZ5CfSqJIxP
+XB7DXpIWi7PxJoeai2F83LnmdFz6F1BPRobwDoSFNdaSKLg4Yf856zpgYNKhL1fE
+OoOXj4VBWBZh1XDfZV44fgwlMIf7edOF1XOagwUCgYAw953O+7FbdKYwF0V3iOM5
+oZDAK/UwN5eC/GFRVDfcM5RycVJRCVtlSWcTfuLr2C2Jpiz/72fgH34QU3eEVsV1
+v94MBznFB1hESw7ReqvZq/9FoO3EVrl+OtBaZmosLD6bKtQJJJ0Xtz/01UW5hxla
+pveZ55XBK9v51nwuNjk4UwKBgHD8fJUllSchUCWb5cwzeAz98Kdl7LJ6uQo5q2/i
+EllLYOWThiEeIYdrIuklholRPIDXAaPsF2c6vn5yo+q+o6EFSZlw0+YpCjDAb5Lp
+wAh5BprFk6HkkM/0t9Guf4rMyYWC8odSlE9x7YXYkuSMYDCTI4Zs6vCoq7I8PbQn
+B4AlAoGAZ6Ee5m/ph5UVp/3+cR6jCY7aHBUU/M3pbJSkVjBW+ymEBVJ6sUdz8k3P
+x8BiPEQggNN7faWBqRWP7KXPnDYHh6shYUgPJwI5HX6NE/ZDnnXjeysHRyf0oCo5
+S6tHXwHNKB5HS1c/KDyyNGjP2oi/MF4o/MGWNWEcK6TJA3RGOYM=
+-----END RSA PRIVATE KEY-----`)
+
+var CaPem = []byte(`-----BEGIN CERTIFICATE-----
+MIIDtTCCAp2gAwIBAgIJANeS1FOzWXlZMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTgwODE2MTUxNDE5WhcNMjEwNjA1MTUxNDE5WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAsV6xlhFxMn14Pn7XBRGLt8/HXmhVVu20IKFgIOyX7gAZr0QLsuT1fGf5
+zH9HrlgOMkfdhV847U03KPfUnBsi9lS6/xOxnH/OzTYM0WW0eNMGF7eoxrS64GSb
+PVX4pLi5+uwrrZT5HmDgZi49ANmuX6UYmH/eRRvSIoYUTV6t0aYsLyKvlpEAtRAe
+4AlKB236j5ggmJ36QUhTFTbeNbeOOgloTEdPK8Y/kgpnhiqzMdPqqIc7IeXUc456
+yX8MJUgniTM2qCNTFdEw+C2Ok0RbU6TI2SuEgVF4jtCcVEKxZ8kYbioONaePQKFR
+/EhdXO+/ag1IEdXElH9knLOfB+zCgwIDAQABo4GnMIGkMB0GA1UdDgQWBBQgHiwD
+00upIbCOunlK4HRw89DhjjB1BgNVHSMEbjBsgBQgHiwD00upIbCOunlK4HRw89Dh
+jqFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV
+BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJANeS1FOzWXlZMAwGA1UdEwQF
+MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAFMZFQTFKU5tWIpWh8BbVZeVZcng0Kiq
+qwbhVwaTkqtfmbqw8/w+faOWylmLncQEMmgvnUltGMQlQKBwQM2byzPkz9phal3g
+uI0JWJYqtcMyIQUB9QbbhrDNC9kdt/ji/x6rrIqzaMRuiBXqH5LQ9h856yXzArqd
+cAQGzzYpbUCIv7ciSB93cKkU73fQLZVy5ZBy1+oAa1V9U4cb4G/20/PDmT+G3Gxz
+pEjeDKtz8XINoWgA2cSdfAhNZt5vqJaCIZ8qN0z6C7SUKwUBderERUMLUXdhUldC
+KTVHyEPvd0aULd5S5vEpKCnHcQmFcLdoN8t9k9pR9ZgwqXbyJHlxWFo=
+-----END CERTIFICATE-----`)
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/COPYING b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/COPYING
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/COPYING
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/COPYING
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING
new file mode 100644
index 0000000..5a8e332
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING
@@ -0,0 +1,14 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2004 Sam Hocevar
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
+
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING
new file mode 100644
index 0000000..5a8e332
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING
@@ -0,0 +1,14 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2004 Sam Hocevar
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
+
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING
new file mode 100644
index 0000000..5a8e332
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING
@@ -0,0 +1,14 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2004 Sam Hocevar
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
+
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/decode.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/decode.go
similarity index 90%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/decode.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/decode.go
index 6c7d398..b0fd51d 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/decode.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/decode.go
@@ -10,7 +10,9 @@ import (
"time"
)
-var e = fmt.Errorf
+func e(format string, args ...interface{}) error {
+ return fmt.Errorf("toml: "+format, args...)
+}
// Unmarshaler is the interface implemented by objects that can unmarshal a
// TOML description of themselves.
@@ -103,6 +105,13 @@ func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
// This decoder will not handle cyclic types. If a cyclic type is passed,
// `Decode` will not terminate.
func Decode(data string, v interface{}) (MetaData, error) {
+ rv := reflect.ValueOf(v)
+ if rv.Kind() != reflect.Ptr {
+ return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v))
+ }
+ if rv.IsNil() {
+ return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v))
+ }
p, err := parse(data)
if err != nil {
return MetaData{}, err
@@ -111,7 +120,7 @@ func Decode(data string, v interface{}) (MetaData, error) {
p.mapping, p.types, p.ordered,
make(map[string]bool, len(p.ordered)), nil,
}
- return md, md.unify(p.mapping, rvalue(v))
+ return md, md.unify(p.mapping, indirect(rv))
}
// DecodeFile is just like Decode, except it will automatically read the
@@ -211,7 +220,7 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
case reflect.Interface:
// we only support empty interfaces.
if rv.NumMethod() > 0 {
- return e("Unsupported type '%s'.", rv.Kind())
+ return e("unsupported type %s", rv.Type())
}
return md.unifyAnything(data, rv)
case reflect.Float32:
@@ -219,13 +228,17 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
case reflect.Float64:
return md.unifyFloat64(data, rv)
}
- return e("Unsupported type '%s'.", rv.Kind())
+ return e("unsupported type %s", rv.Kind())
}
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
tmap, ok := mapping.(map[string]interface{})
if !ok {
- return mismatch(rv, "map", mapping)
+ if mapping == nil {
+ return nil
+ }
+ return e("type mismatch for %s: expected table but found %T",
+ rv.Type().String(), mapping)
}
for key, datum := range tmap {
@@ -250,14 +263,13 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
md.decoded[md.context.add(key).String()] = true
md.context = append(md.context, key)
if err := md.unify(datum, subv); err != nil {
- return e("Type mismatch for '%s.%s': %s",
- rv.Type().String(), f.name, err)
+ return err
}
md.context = md.context[0 : len(md.context)-1]
} else if f.name != "" {
// Bad user! No soup for you!
- return e("Field '%s.%s' is unexported, and therefore cannot "+
- "be loaded with reflection.", rv.Type().String(), f.name)
+ return e("cannot write unexported field %s.%s",
+ rv.Type().String(), f.name)
}
}
}
@@ -267,6 +279,9 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
tmap, ok := mapping.(map[string]interface{})
if !ok {
+ if tmap == nil {
+ return nil
+ }
return badtype("map", mapping)
}
if rv.IsNil() {
@@ -292,6 +307,9 @@ func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
datav := reflect.ValueOf(data)
if datav.Kind() != reflect.Slice {
+ if !datav.IsValid() {
+ return nil
+ }
return badtype("slice", data)
}
sliceLen := datav.Len()
@@ -305,12 +323,16 @@ func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
datav := reflect.ValueOf(data)
if datav.Kind() != reflect.Slice {
+ if !datav.IsValid() {
+ return nil
+ }
return badtype("slice", data)
}
- sliceLen := datav.Len()
- if rv.IsNil() {
- rv.Set(reflect.MakeSlice(rv.Type(), sliceLen, sliceLen))
+ n := datav.Len()
+ if rv.IsNil() || rv.Cap() < n {
+ rv.Set(reflect.MakeSlice(rv.Type(), n, n))
}
+ rv.SetLen(n)
return md.unifySliceArray(datav, rv)
}
@@ -365,15 +387,15 @@ func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
// No bounds checking necessary.
case reflect.Int8:
if num < math.MinInt8 || num > math.MaxInt8 {
- return e("Value '%d' is out of range for int8.", num)
+ return e("value %d is out of range for int8", num)
}
case reflect.Int16:
if num < math.MinInt16 || num > math.MaxInt16 {
- return e("Value '%d' is out of range for int16.", num)
+ return e("value %d is out of range for int16", num)
}
case reflect.Int32:
if num < math.MinInt32 || num > math.MaxInt32 {
- return e("Value '%d' is out of range for int32.", num)
+ return e("value %d is out of range for int32", num)
}
}
rv.SetInt(num)
@@ -384,15 +406,15 @@ func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
// No bounds checking necessary.
case reflect.Uint8:
if num < 0 || unum > math.MaxUint8 {
- return e("Value '%d' is out of range for uint8.", num)
+ return e("value %d is out of range for uint8", num)
}
case reflect.Uint16:
if num < 0 || unum > math.MaxUint16 {
- return e("Value '%d' is out of range for uint16.", num)
+ return e("value %d is out of range for uint16", num)
}
case reflect.Uint32:
if num < 0 || unum > math.MaxUint32 {
- return e("Value '%d' is out of range for uint32.", num)
+ return e("value %d is out of range for uint32", num)
}
}
rv.SetUint(unum)
@@ -458,7 +480,7 @@ func rvalue(v interface{}) reflect.Value {
// interest to us (like encoding.TextUnmarshaler).
func indirect(v reflect.Value) reflect.Value {
if v.Kind() != reflect.Ptr {
- if v.CanAddr() {
+ if v.CanSet() {
pv := v.Addr()
if _, ok := pv.Interface().(TextUnmarshaler); ok {
return pv
@@ -483,10 +505,5 @@ func isUnifiable(rv reflect.Value) bool {
}
func badtype(expected string, data interface{}) error {
- return e("Expected %s but found '%T'.", expected, data)
-}
-
-func mismatch(user reflect.Value, expected string, data interface{}) error {
- return e("Type mismatch for %s. Expected %s but found '%T'.",
- user.Type().String(), expected, data)
+ return e("cannot load TOML value of type %T into a Go %s", data, expected)
}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/decode_meta.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/decode_meta.go
similarity index 99%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/decode_meta.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/decode_meta.go
index ef6f545..b9914a6 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/decode_meta.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/decode_meta.go
@@ -77,9 +77,8 @@ func (k Key) maybeQuoted(i int) string {
}
if quote {
return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\""
- } else {
- return k[i]
}
+ return k[i]
}
func (k Key) add(piece string) Key {
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/doc.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/doc.go
similarity index 94%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/doc.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/doc.go
index fe26800..b371f39 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/doc.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/doc.go
@@ -4,7 +4,7 @@ files via reflection. There is also support for delaying decoding with
the Primitive type, and querying the set of keys in a TOML document with the
MetaData type.
-The specification implemented: https://github.com/mojombo/toml
+The specification implemented: https://github.com/toml-lang/toml
The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify
whether a file is a valid TOML document. It can also be used to print the
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/encode.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/encode.go
similarity index 85%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/encode.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/encode.go
index c7e227c..d905c21 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/encode.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/encode.go
@@ -16,17 +16,17 @@ type tomlEncodeError struct{ error }
var (
errArrayMixedElementTypes = errors.New(
- "can't encode array with mixed element types")
+ "toml: cannot encode array with mixed element types")
errArrayNilElement = errors.New(
- "can't encode array with nil element")
+ "toml: cannot encode array with nil element")
errNonString = errors.New(
- "can't encode a map with non-string key type")
+ "toml: cannot encode a map with non-string key type")
errAnonNonStruct = errors.New(
- "can't encode an anonymous field that is not a struct")
+ "toml: cannot encode an anonymous field that is not a struct")
errArrayNoTable = errors.New(
- "TOML array element can't contain a table")
+ "toml: TOML array element cannot contain a table")
errNoKey = errors.New(
- "top-level values must be a Go map or struct")
+ "toml: top-level values must be Go maps or structs")
errAnything = errors.New("") // used in testing
)
@@ -148,7 +148,7 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) {
case reflect.Struct:
enc.eTable(key, rv)
default:
- panic(e("Unsupported type for key '%s': %s", key, k))
+ panic(e("unsupported type for key '%s': %s", key, k))
}
}
@@ -160,7 +160,7 @@ func (enc *Encoder) eElement(rv reflect.Value) {
// Special case time.Time as a primitive. Has to come before
// TextMarshaler below because time.Time implements
// encoding.TextMarshaler, but we need to always use UTC.
- enc.wf(v.In(time.FixedZone("UTC", 0)).Format("2006-01-02T15:04:05Z"))
+ enc.wf(v.UTC().Format("2006-01-02T15:04:05Z"))
return
case TextMarshaler:
// Special case. Use text marshaler if it's available for this value.
@@ -191,7 +191,7 @@ func (enc *Encoder) eElement(rv reflect.Value) {
case reflect.String:
enc.writeQuoted(rv.String())
default:
- panic(e("Unexpected primitive type: %s", rv.Kind()))
+ panic(e("unexpected primitive type: %s", rv.Kind()))
}
}
@@ -241,7 +241,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
func (enc *Encoder) eTable(key Key, rv reflect.Value) {
panicIfInvalidKey(key)
if len(key) == 1 {
- // Output an extra new line between top-level tables.
+ // Output an extra newline between top-level tables.
// (The newline isn't written if nothing else has been written though.)
enc.newline()
}
@@ -306,19 +306,36 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
for i := 0; i < rt.NumField(); i++ {
f := rt.Field(i)
- // skip unexporded fields
- if f.PkgPath != "" {
+ // skip unexported fields
+ if f.PkgPath != "" && !f.Anonymous {
continue
}
frv := rv.Field(i)
if f.Anonymous {
- frv := eindirect(frv)
- t := frv.Type()
- if t.Kind() != reflect.Struct {
- encPanic(errAnonNonStruct)
+ t := f.Type
+ switch t.Kind() {
+ case reflect.Struct:
+ // Treat anonymous struct fields with
+ // tag names as though they are not
+ // anonymous, like encoding/json does.
+ if getOptions(f.Tag).name == "" {
+ addFields(t, frv, f.Index)
+ continue
+ }
+ case reflect.Ptr:
+ if t.Elem().Kind() == reflect.Struct &&
+ getOptions(f.Tag).name == "" {
+ if !frv.IsNil() {
+ addFields(t.Elem(), frv.Elem(), f.Index)
+ }
+ continue
+ }
+ // Fall through to the normal field encoding logic below
+ // for non-struct anonymous fields.
}
- addFields(t, frv, f.Index)
- } else if typeIsHash(tomlTypeOfGo(frv)) {
+ }
+
+ if typeIsHash(tomlTypeOfGo(frv)) {
fieldsSub = append(fieldsSub, append(start, f.Index...))
} else {
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
@@ -336,18 +353,18 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
continue
}
- keyName := sft.Tag.Get("toml")
- if keyName == "-" {
+ opts := getOptions(sft.Tag)
+ if opts.skip {
continue
}
- if keyName == "" {
- keyName = sft.Name
+ keyName := sft.Name
+ if opts.name != "" {
+ keyName = opts.name
}
-
- keyName, opts := getOptions(keyName)
- if _, ok := opts["omitempty"]; ok && isEmpty(sf) {
+ if opts.omitempty && isEmpty(sf) {
continue
- } else if _, ok := opts["omitzero"]; ok && isZero(sf) {
+ }
+ if opts.omitzero && isZero(sf) {
continue
}
@@ -382,9 +399,8 @@ func tomlTypeOfGo(rv reflect.Value) tomlType {
case reflect.Array, reflect.Slice:
if typeEqual(tomlHash, tomlArrayType(rv)) {
return tomlArrayHash
- } else {
- return tomlArray
}
+ return tomlArray
case reflect.Ptr, reflect.Interface:
return tomlTypeOfGo(rv.Elem())
case reflect.String:
@@ -441,50 +457,51 @@ func tomlArrayType(rv reflect.Value) tomlType {
return firstType
}
-func getOptions(keyName string) (string, map[string]struct{}) {
- opts := make(map[string]struct{})
- ss := strings.Split(keyName, ",")
- name := ss[0]
- if len(ss) > 1 {
- for _, opt := range ss {
- opts[opt] = struct{}{}
+type tagOptions struct {
+ skip bool // "-"
+ name string
+ omitempty bool
+ omitzero bool
+}
+
+func getOptions(tag reflect.StructTag) tagOptions {
+ t := tag.Get("toml")
+ if t == "-" {
+ return tagOptions{skip: true}
+ }
+ var opts tagOptions
+ parts := strings.Split(t, ",")
+ opts.name = parts[0]
+ for _, s := range parts[1:] {
+ switch s {
+ case "omitempty":
+ opts.omitempty = true
+ case "omitzero":
+ opts.omitzero = true
}
}
-
- return name, opts
+ return opts
}
func isZero(rv reflect.Value) bool {
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- if rv.Int() == 0 {
- return true
- }
+ return rv.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- if rv.Uint() == 0 {
- return true
- }
+ return rv.Uint() == 0
case reflect.Float32, reflect.Float64:
- if rv.Float() == 0.0 {
- return true
- }
+ return rv.Float() == 0.0
}
-
return false
}
func isEmpty(rv reflect.Value) bool {
switch rv.Kind() {
- case reflect.String:
- if len(strings.TrimSpace(rv.String())) == 0 {
- return true
- }
- case reflect.Array, reflect.Slice, reflect.Map:
- if rv.Len() == 0 {
- return true
- }
+ case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
+ return rv.Len() == 0
+ case reflect.Bool:
+ return !rv.Bool()
}
-
return false
}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/encoding_types.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/encoding_types.go
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/encoding_types.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/encoding_types.go
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/encoding_types_1.1.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/lex.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/lex.go
similarity index 62%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/lex.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/lex.go
index 2191228..6dee7fc 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/lex.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/lex.go
@@ -3,6 +3,7 @@ package toml
import (
"fmt"
"strings"
+ "unicode"
"unicode/utf8"
)
@@ -29,24 +30,28 @@ const (
itemArrayTableEnd
itemKeyStart
itemCommentStart
+ itemInlineTableStart
+ itemInlineTableEnd
)
const (
- eof = 0
- tableStart = '['
- tableEnd = ']'
- arrayTableStart = '['
- arrayTableEnd = ']'
- tableSep = '.'
- keySep = '='
- arrayStart = '['
- arrayEnd = ']'
- arrayValTerm = ','
- commentStart = '#'
- stringStart = '"'
- stringEnd = '"'
- rawStringStart = '\''
- rawStringEnd = '\''
+ eof = 0
+ comma = ','
+ tableStart = '['
+ tableEnd = ']'
+ arrayTableStart = '['
+ arrayTableEnd = ']'
+ tableSep = '.'
+ keySep = '='
+ arrayStart = '['
+ arrayEnd = ']'
+ commentStart = '#'
+ stringStart = '"'
+ stringEnd = '"'
+ rawStringStart = '\''
+ rawStringEnd = '\''
+ inlineTableStart = '{'
+ inlineTableEnd = '}'
)
type stateFn func(lx *lexer) stateFn
@@ -55,11 +60,18 @@ type lexer struct {
input string
start int
pos int
- width int
line int
state stateFn
items chan item
+ // Allow for backing up up to three runes.
+ // This is necessary because TOML contains 3-rune tokens (""" and ''').
+ prevWidths [3]int
+ nprev int // how many of prevWidths are in use
+ // If we emit an eof, we can still back up, but it is not OK to call
+ // next again.
+ atEOF bool
+
// A stack of state functions used to maintain context.
// The idea is to reuse parts of the state machine in various places.
// For example, values can appear at the top level or within arbitrarily
@@ -87,7 +99,7 @@ func (lx *lexer) nextItem() item {
func lex(input string) *lexer {
lx := &lexer{
- input: input + "\n",
+ input: input,
state: lexTop,
line: 1,
items: make(chan item, 10),
@@ -102,7 +114,7 @@ func (lx *lexer) push(state stateFn) {
func (lx *lexer) pop() stateFn {
if len(lx.stack) == 0 {
- return lx.errorf("BUG in lexer: no states to pop.")
+ return lx.errorf("BUG in lexer: no states to pop")
}
last := lx.stack[len(lx.stack)-1]
lx.stack = lx.stack[0 : len(lx.stack)-1]
@@ -124,16 +136,25 @@ func (lx *lexer) emitTrim(typ itemType) {
}
func (lx *lexer) next() (r rune) {
+ if lx.atEOF {
+ panic("next called after EOF")
+ }
if lx.pos >= len(lx.input) {
- lx.width = 0
+ lx.atEOF = true
return eof
}
if lx.input[lx.pos] == '\n' {
lx.line++
}
- r, lx.width = utf8.DecodeRuneInString(lx.input[lx.pos:])
- lx.pos += lx.width
+ lx.prevWidths[2] = lx.prevWidths[1]
+ lx.prevWidths[1] = lx.prevWidths[0]
+ if lx.nprev < 3 {
+ lx.nprev++
+ }
+ r, w := utf8.DecodeRuneInString(lx.input[lx.pos:])
+ lx.prevWidths[0] = w
+ lx.pos += w
return r
}
@@ -142,9 +163,20 @@ func (lx *lexer) ignore() {
lx.start = lx.pos
}
-// backup steps back one rune. Can be called only once per call of next.
+// backup steps back one rune. Can be called only twice between calls to next.
func (lx *lexer) backup() {
- lx.pos -= lx.width
+ if lx.atEOF {
+ lx.atEOF = false
+ return
+ }
+ if lx.nprev < 1 {
+ panic("backed up too far")
+ }
+ w := lx.prevWidths[0]
+ lx.prevWidths[0] = lx.prevWidths[1]
+ lx.prevWidths[1] = lx.prevWidths[2]
+ lx.nprev--
+ lx.pos -= w
if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' {
lx.line--
}
@@ -166,9 +198,22 @@ func (lx *lexer) peek() rune {
return r
}
+// skip ignores all input that matches the given predicate.
+func (lx *lexer) skip(pred func(rune) bool) {
+ for {
+ r := lx.next()
+ if pred(r) {
+ continue
+ }
+ lx.backup()
+ lx.ignore()
+ return
+ }
+}
+
// errorf stops all lexing by emitting an error and returning `nil`.
// Note that any value that is a character is escaped if it's a special
-// character (new lines, tabs, etc.).
+// character (newlines, tabs, etc.).
func (lx *lexer) errorf(format string, values ...interface{}) stateFn {
lx.items <- item{
itemError,
@@ -184,7 +229,6 @@ func lexTop(lx *lexer) stateFn {
if isWhitespace(r) || isNL(r) {
return lexSkip(lx, lexTop)
}
-
switch r {
case commentStart:
lx.push(lexTop)
@@ -193,7 +237,7 @@ func lexTop(lx *lexer) stateFn {
return lexTableStart
case eof:
if lx.pos > lx.start {
- return lx.errorf("Unexpected EOF.")
+ return lx.errorf("unexpected EOF")
}
lx.emit(itemEOF)
return nil
@@ -208,12 +252,12 @@ func lexTop(lx *lexer) stateFn {
// lexTopEnd is entered whenever a top-level item has been consumed. (A value
// or a table.) It must see only whitespace, and will turn back to lexTop
-// upon a new line. If it sees EOF, it will quit the lexer successfully.
+// upon a newline. If it sees EOF, it will quit the lexer successfully.
func lexTopEnd(lx *lexer) stateFn {
r := lx.next()
switch {
case r == commentStart:
- // a comment will read to a new line for us.
+ // a comment will read to a newline for us.
lx.push(lexTop)
return lexCommentStart
case isWhitespace(r):
@@ -222,11 +266,11 @@ func lexTopEnd(lx *lexer) stateFn {
lx.ignore()
return lexTop
case r == eof:
- lx.ignore()
- return lexTop
+ lx.emit(itemEOF)
+ return nil
}
- return lx.errorf("Expected a top-level item to end with a new line, "+
- "comment or EOF, but got %q instead.", r)
+ return lx.errorf("expected a top-level item to end with a newline, "+
+ "comment, or EOF, but got %q instead", r)
}
// lexTable lexes the beginning of a table. Namely, it makes sure that
@@ -253,50 +297,47 @@ func lexTableEnd(lx *lexer) stateFn {
func lexArrayTableEnd(lx *lexer) stateFn {
if r := lx.next(); r != arrayTableEnd {
- return lx.errorf("Expected end of table array name delimiter %q, "+
- "but got %q instead.", arrayTableEnd, r)
+ return lx.errorf("expected end of table array name delimiter %q, "+
+ "but got %q instead", arrayTableEnd, r)
}
lx.emit(itemArrayTableEnd)
return lexTopEnd
}
func lexTableNameStart(lx *lexer) stateFn {
+ lx.skip(isWhitespace)
switch r := lx.peek(); {
case r == tableEnd || r == eof:
- return lx.errorf("Unexpected end of table name. (Table names cannot " +
- "be empty.)")
+ return lx.errorf("unexpected end of table name " +
+ "(table names cannot be empty)")
case r == tableSep:
- return lx.errorf("Unexpected table separator. (Table names cannot " +
- "be empty.)")
+ return lx.errorf("unexpected table separator " +
+ "(table names cannot be empty)")
case r == stringStart || r == rawStringStart:
lx.ignore()
lx.push(lexTableNameEnd)
return lexValue // reuse string lexing
- case isWhitespace(r):
- return lexTableNameStart
default:
return lexBareTableName
}
}
-// lexTableName lexes the name of a table. It assumes that at least one
+// lexBareTableName lexes the name of a table. It assumes that at least one
// valid character for the table has already been read.
func lexBareTableName(lx *lexer) stateFn {
- switch r := lx.next(); {
- case isBareKeyChar(r):
+ r := lx.next()
+ if isBareKeyChar(r) {
return lexBareTableName
- case r == tableSep || r == tableEnd:
- lx.backup()
- lx.emitTrim(itemText)
- return lexTableNameEnd
- default:
- return lx.errorf("Bare keys cannot contain %q.", r)
}
+ lx.backup()
+ lx.emit(itemText)
+ return lexTableNameEnd
}
// lexTableNameEnd reads the end of a piece of a table name, optionally
// consuming whitespace.
func lexTableNameEnd(lx *lexer) stateFn {
+ lx.skip(isWhitespace)
switch r := lx.next(); {
case isWhitespace(r):
return lexTableNameEnd
@@ -306,8 +347,8 @@ func lexTableNameEnd(lx *lexer) stateFn {
case r == tableEnd:
return lx.pop()
default:
- return lx.errorf("Expected '.' or ']' to end table name, but got %q "+
- "instead.", r)
+ return lx.errorf("expected '.' or ']' to end table name, "+
+ "but got %q instead", r)
}
}
@@ -317,7 +358,7 @@ func lexKeyStart(lx *lexer) stateFn {
r := lx.peek()
switch {
case r == keySep:
- return lx.errorf("Unexpected key separator %q.", keySep)
+ return lx.errorf("unexpected key separator %q", keySep)
case isWhitespace(r) || isNL(r):
lx.next()
return lexSkip(lx, lexKeyStart)
@@ -340,14 +381,15 @@ func lexBareKey(lx *lexer) stateFn {
case isBareKeyChar(r):
return lexBareKey
case isWhitespace(r):
- lx.emitTrim(itemText)
+ lx.backup()
+ lx.emit(itemText)
return lexKeyEnd
case r == keySep:
lx.backup()
- lx.emitTrim(itemText)
+ lx.emit(itemText)
return lexKeyEnd
default:
- return lx.errorf("Bare keys cannot contain %q.", r)
+ return lx.errorf("bare keys cannot contain %q", r)
}
}
@@ -360,7 +402,7 @@ func lexKeyEnd(lx *lexer) stateFn {
case isWhitespace(r):
return lexSkip(lx, lexKeyEnd)
default:
- return lx.errorf("Expected key separator %q, but got %q instead.",
+ return lx.errorf("expected key separator %q, but got %q instead",
keySep, r)
}
}
@@ -369,20 +411,26 @@ func lexKeyEnd(lx *lexer) stateFn {
// lexValue will ignore whitespace.
// After a value is lexed, the last state on the next is popped and returned.
func lexValue(lx *lexer) stateFn {
- // We allow whitespace to precede a value, but NOT new lines.
- // In array syntax, the array states are responsible for ignoring new
- // lines.
+ // We allow whitespace to precede a value, but NOT newlines.
+ // In array syntax, the array states are responsible for ignoring newlines.
r := lx.next()
- if isWhitespace(r) {
- return lexSkip(lx, lexValue)
- }
-
switch {
- case r == arrayStart:
+ case isWhitespace(r):
+ return lexSkip(lx, lexValue)
+ case isDigit(r):
+ lx.backup() // avoid an extra state and use the same as above
+ return lexNumberOrDateStart
+ }
+ switch r {
+ case arrayStart:
lx.ignore()
lx.emit(itemArray)
return lexArrayValue
- case r == stringStart:
+ case inlineTableStart:
+ lx.ignore()
+ lx.emit(itemInlineTableStart)
+ return lexInlineTableValue
+ case stringStart:
if lx.accept(stringStart) {
if lx.accept(stringStart) {
lx.ignore() // Ignore """
@@ -392,7 +440,7 @@ func lexValue(lx *lexer) stateFn {
}
lx.ignore() // ignore the '"'
return lexString
- case r == rawStringStart:
+ case rawStringStart:
if lx.accept(rawStringStart) {
if lx.accept(rawStringStart) {
lx.ignore() // Ignore """
@@ -402,23 +450,24 @@ func lexValue(lx *lexer) stateFn {
}
lx.ignore() // ignore the "'"
return lexRawString
- case r == 't':
- return lexTrue
- case r == 'f':
- return lexFalse
- case r == '-':
+ case '+', '-':
return lexNumberStart
- case isDigit(r):
- lx.backup() // avoid an extra state and use the same as above
- return lexNumberOrDateStart
- case r == '.': // special error case, be kind to users
- return lx.errorf("Floats must start with a digit, not '.'.")
+ case '.': // special error case, be kind to users
+ return lx.errorf("floats must start with a digit, not '.'")
}
- return lx.errorf("Expected value but found %q instead.", r)
+ if unicode.IsLetter(r) {
+ // Be permissive here; lexBool will give a nice error if the
+ // user wrote something like
+ // x = foo
+ // (i.e. not 'true' or 'false' but is something else word-like.)
+ lx.backup()
+ return lexBool
+ }
+ return lx.errorf("expected value but found %q instead", r)
}
// lexArrayValue consumes one value in an array. It assumes that '[' or ','
-// have already been consumed. All whitespace and new lines are ignored.
+// have already been consumed. All whitespace and newlines are ignored.
func lexArrayValue(lx *lexer) stateFn {
r := lx.next()
switch {
@@ -427,10 +476,11 @@ func lexArrayValue(lx *lexer) stateFn {
case r == commentStart:
lx.push(lexArrayValue)
return lexCommentStart
- case r == arrayValTerm:
- return lx.errorf("Unexpected array value terminator %q.",
- arrayValTerm)
+ case r == comma:
+ return lx.errorf("unexpected comma")
case r == arrayEnd:
+ // NOTE(caleb): The spec isn't clear about whether you can have
+ // a trailing comma or not, so we'll allow it.
return lexArrayEnd
}
@@ -439,8 +489,9 @@ func lexArrayValue(lx *lexer) stateFn {
return lexValue
}
-// lexArrayValueEnd consumes the cruft between values of an array. Namely,
-// it ignores whitespace and expects either a ',' or a ']'.
+// lexArrayValueEnd consumes everything between the end of an array value and
+// the next value (or the end of the array): it ignores whitespace and newlines
+// and expects either a ',' or a ']'.
func lexArrayValueEnd(lx *lexer) stateFn {
r := lx.next()
switch {
@@ -449,31 +500,88 @@ func lexArrayValueEnd(lx *lexer) stateFn {
case r == commentStart:
lx.push(lexArrayValueEnd)
return lexCommentStart
- case r == arrayValTerm:
+ case r == comma:
lx.ignore()
return lexArrayValue // move on to the next value
case r == arrayEnd:
return lexArrayEnd
}
- return lx.errorf("Expected an array value terminator %q or an array "+
- "terminator %q, but got %q instead.", arrayValTerm, arrayEnd, r)
+ return lx.errorf(
+ "expected a comma or array terminator %q, but got %q instead",
+ arrayEnd, r,
+ )
}
-// lexArrayEnd finishes the lexing of an array. It assumes that a ']' has
-// just been consumed.
+// lexArrayEnd finishes the lexing of an array.
+// It assumes that a ']' has just been consumed.
func lexArrayEnd(lx *lexer) stateFn {
lx.ignore()
lx.emit(itemArrayEnd)
return lx.pop()
}
+// lexInlineTableValue consumes one key/value pair in an inline table.
+// It assumes that '{' or ',' have already been consumed. Whitespace is ignored.
+func lexInlineTableValue(lx *lexer) stateFn {
+ r := lx.next()
+ switch {
+ case isWhitespace(r):
+ return lexSkip(lx, lexInlineTableValue)
+ case isNL(r):
+ return lx.errorf("newlines not allowed within inline tables")
+ case r == commentStart:
+ lx.push(lexInlineTableValue)
+ return lexCommentStart
+ case r == comma:
+ return lx.errorf("unexpected comma")
+ case r == inlineTableEnd:
+ return lexInlineTableEnd
+ }
+ lx.backup()
+ lx.push(lexInlineTableValueEnd)
+ return lexKeyStart
+}
+
+// lexInlineTableValueEnd consumes everything between the end of an inline table
+// key/value pair and the next pair (or the end of the table):
+// it ignores whitespace and expects either a ',' or a '}'.
+func lexInlineTableValueEnd(lx *lexer) stateFn {
+ r := lx.next()
+ switch {
+ case isWhitespace(r):
+ return lexSkip(lx, lexInlineTableValueEnd)
+ case isNL(r):
+ return lx.errorf("newlines not allowed within inline tables")
+ case r == commentStart:
+ lx.push(lexInlineTableValueEnd)
+ return lexCommentStart
+ case r == comma:
+ lx.ignore()
+ return lexInlineTableValue
+ case r == inlineTableEnd:
+ return lexInlineTableEnd
+ }
+ return lx.errorf("expected a comma or an inline table terminator %q, "+
+ "but got %q instead", inlineTableEnd, r)
+}
+
+// lexInlineTableEnd finishes the lexing of an inline table.
+// It assumes that a '}' has just been consumed.
+func lexInlineTableEnd(lx *lexer) stateFn {
+ lx.ignore()
+ lx.emit(itemInlineTableEnd)
+ return lx.pop()
+}
+
// lexString consumes the inner contents of a string. It assumes that the
// beginning '"' has already been consumed and ignored.
func lexString(lx *lexer) stateFn {
r := lx.next()
switch {
+ case r == eof:
+ return lx.errorf("unexpected EOF")
case isNL(r):
- return lx.errorf("Strings cannot contain new lines.")
+ return lx.errorf("strings cannot contain newlines")
case r == '\\':
lx.push(lexString)
return lexStringEscape
@@ -490,11 +598,12 @@ func lexString(lx *lexer) stateFn {
// lexMultilineString consumes the inner contents of a string. It assumes that
// the beginning '"""' has already been consumed and ignored.
func lexMultilineString(lx *lexer) stateFn {
- r := lx.next()
- switch {
- case r == '\\':
+ switch lx.next() {
+ case eof:
+ return lx.errorf("unexpected EOF")
+ case '\\':
return lexMultilineStringEscape
- case r == stringEnd:
+ case stringEnd:
if lx.accept(stringEnd) {
if lx.accept(stringEnd) {
lx.backup()
@@ -518,8 +627,10 @@ func lexMultilineString(lx *lexer) stateFn {
func lexRawString(lx *lexer) stateFn {
r := lx.next()
switch {
+ case r == eof:
+ return lx.errorf("unexpected EOF")
case isNL(r):
- return lx.errorf("Strings cannot contain new lines.")
+ return lx.errorf("strings cannot contain newlines")
case r == rawStringEnd:
lx.backup()
lx.emit(itemRawString)
@@ -531,12 +642,13 @@ func lexRawString(lx *lexer) stateFn {
}
// lexMultilineRawString consumes a raw string. Nothing can be escaped in such
-// a string. It assumes that the beginning "'" has already been consumed and
+// a string. It assumes that the beginning "'''" has already been consumed and
// ignored.
func lexMultilineRawString(lx *lexer) stateFn {
- r := lx.next()
- switch {
- case r == rawStringEnd:
+ switch lx.next() {
+ case eof:
+ return lx.errorf("unexpected EOF")
+ case rawStringEnd:
if lx.accept(rawStringEnd) {
if lx.accept(rawStringEnd) {
lx.backup()
@@ -560,13 +672,11 @@ func lexMultilineRawString(lx *lexer) stateFn {
func lexMultilineStringEscape(lx *lexer) stateFn {
// Handle the special case first:
if isNL(lx.next()) {
- lx.next()
return lexMultilineString
- } else {
- lx.backup()
- lx.push(lexMultilineString)
- return lexStringEscape(lx)
}
+ lx.backup()
+ lx.push(lexMultilineString)
+ return lexStringEscape(lx)
}
func lexStringEscape(lx *lexer) stateFn {
@@ -591,10 +701,9 @@ func lexStringEscape(lx *lexer) stateFn {
case 'U':
return lexLongUnicodeEscape
}
- return lx.errorf("Invalid escape character %q. Only the following "+
+ return lx.errorf("invalid escape character %q; only the following "+
"escape characters are allowed: "+
- "\\b, \\t, \\n, \\f, \\r, \\\", \\/, \\\\, "+
- "\\uXXXX and \\UXXXXXXXX.", r)
+ `\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r)
}
func lexShortUnicodeEscape(lx *lexer) stateFn {
@@ -602,8 +711,8 @@ func lexShortUnicodeEscape(lx *lexer) stateFn {
for i := 0; i < 4; i++ {
r = lx.next()
if !isHexadecimal(r) {
- return lx.errorf("Expected four hexadecimal digits after '\\u', "+
- "but got '%s' instead.", lx.current())
+ return lx.errorf(`expected four hexadecimal digits after '\u', `+
+ "but got %q instead", lx.current())
}
}
return lx.pop()
@@ -614,40 +723,43 @@ func lexLongUnicodeEscape(lx *lexer) stateFn {
for i := 0; i < 8; i++ {
r = lx.next()
if !isHexadecimal(r) {
- return lx.errorf("Expected eight hexadecimal digits after '\\U', "+
- "but got '%s' instead.", lx.current())
+ return lx.errorf(`expected eight hexadecimal digits after '\U', `+
+ "but got %q instead", lx.current())
}
}
return lx.pop()
}
-// lexNumberOrDateStart consumes either a (positive) integer, float or
-// datetime. It assumes that NO negative sign has been consumed.
+// lexNumberOrDateStart consumes either an integer, a float, or datetime.
func lexNumberOrDateStart(lx *lexer) stateFn {
r := lx.next()
- if !isDigit(r) {
- if r == '.' {
- return lx.errorf("Floats must start with a digit, not '.'.")
- } else {
- return lx.errorf("Expected a digit but got %q.", r)
- }
+ if isDigit(r) {
+ return lexNumberOrDate
}
- return lexNumberOrDate
+ switch r {
+ case '_':
+ return lexNumber
+ case 'e', 'E':
+ return lexFloat
+ case '.':
+ return lx.errorf("floats must start with a digit, not '.'")
+ }
+ return lx.errorf("expected a digit but got %q", r)
}
-// lexNumberOrDate consumes either a (positive) integer, float or datetime.
+// lexNumberOrDate consumes either an integer, float or datetime.
func lexNumberOrDate(lx *lexer) stateFn {
r := lx.next()
- switch {
- case r == '-':
- if lx.pos-lx.start != 5 {
- return lx.errorf("All ISO8601 dates must be in full Zulu form.")
- }
- return lexDateAfterYear
- case isDigit(r):
+ if isDigit(r) {
return lexNumberOrDate
- case r == '.':
- return lexFloatStart
+ }
+ switch r {
+ case '-':
+ return lexDatetime
+ case '_':
+ return lexNumber
+ case '.', 'e', 'E':
+ return lexFloat
}
lx.backup()
@@ -655,46 +767,34 @@ func lexNumberOrDate(lx *lexer) stateFn {
return lx.pop()
}
-// lexDateAfterYear consumes a full Zulu Datetime in ISO8601 format.
-// It assumes that "YYYY-" has already been consumed.
-func lexDateAfterYear(lx *lexer) stateFn {
- formats := []rune{
- // digits are '0'.
- // everything else is direct equality.
- '0', '0', '-', '0', '0',
- 'T',
- '0', '0', ':', '0', '0', ':', '0', '0',
- 'Z',
+// lexDatetime consumes a Datetime, to a first approximation.
+// The parser validates that it matches one of the accepted formats.
+func lexDatetime(lx *lexer) stateFn {
+ r := lx.next()
+ if isDigit(r) {
+ return lexDatetime
}
- for _, f := range formats {
- r := lx.next()
- if f == '0' {
- if !isDigit(r) {
- return lx.errorf("Expected digit in ISO8601 datetime, "+
- "but found %q instead.", r)
- }
- } else if f != r {
- return lx.errorf("Expected %q in ISO8601 datetime, "+
- "but found %q instead.", f, r)
- }
+ switch r {
+ case '-', 'T', ':', '.', 'Z':
+ return lexDatetime
}
+
+ lx.backup()
lx.emit(itemDatetime)
return lx.pop()
}
-// lexNumberStart consumes either an integer or a float. It assumes that
-// a negative sign has already been read, but that *no* digits have been
-// consumed. lexNumberStart will move to the appropriate integer or float
-// states.
+// lexNumberStart consumes either an integer or a float. It assumes that a sign
+// has already been read, but that *no* digits have been consumed.
+// lexNumberStart will move to the appropriate integer or float states.
func lexNumberStart(lx *lexer) stateFn {
- // we MUST see a digit. Even floats have to start with a digit.
+ // We MUST see a digit. Even floats have to start with a digit.
r := lx.next()
if !isDigit(r) {
if r == '.' {
- return lx.errorf("Floats must start with a digit, not '.'.")
- } else {
- return lx.errorf("Expected a digit but got %q.", r)
+ return lx.errorf("floats must start with a digit, not '.'")
}
+ return lx.errorf("expected a digit but got %q", r)
}
return lexNumber
}
@@ -702,11 +802,14 @@ func lexNumberStart(lx *lexer) stateFn {
// lexNumber consumes an integer or a float after seeing the first digit.
func lexNumber(lx *lexer) stateFn {
r := lx.next()
- switch {
- case isDigit(r):
+ if isDigit(r) {
return lexNumber
- case r == '.':
- return lexFloatStart
+ }
+ switch r {
+ case '_':
+ return lexNumber
+ case '.', 'e', 'E':
+ return lexFloat
}
lx.backup()
@@ -714,60 +817,42 @@ func lexNumber(lx *lexer) stateFn {
return lx.pop()
}
-// lexFloatStart starts the consumption of digits of a float after a '.'.
-// Namely, at least one digit is required.
-func lexFloatStart(lx *lexer) stateFn {
- r := lx.next()
- if !isDigit(r) {
- return lx.errorf("Floats must have a digit after the '.', but got "+
- "%q instead.", r)
- }
- return lexFloat
-}
-
-// lexFloat consumes the digits of a float after a '.'.
-// Assumes that one digit has been consumed after a '.' already.
+// lexFloat consumes the elements of a float. It allows any sequence of
+// float-like characters, so floats emitted by the lexer are only a first
+// approximation and must be validated by the parser.
func lexFloat(lx *lexer) stateFn {
r := lx.next()
if isDigit(r) {
return lexFloat
}
+ switch r {
+ case '_', '.', '-', '+', 'e', 'E':
+ return lexFloat
+ }
lx.backup()
lx.emit(itemFloat)
return lx.pop()
}
-// lexConst consumes the s[1:] in s. It assumes that s[0] has already been
-// consumed.
-func lexConst(lx *lexer, s string) stateFn {
- for i := range s[1:] {
- if r := lx.next(); r != rune(s[i+1]) {
- return lx.errorf("Expected %q, but found %q instead.", s[:i+1],
- s[:i]+string(r))
+// lexBool consumes a bool string: 'true' or 'false.
+func lexBool(lx *lexer) stateFn {
+ var rs []rune
+ for {
+ r := lx.next()
+ if !unicode.IsLetter(r) {
+ lx.backup()
+ break
}
+ rs = append(rs, r)
}
- return nil
-}
-
-// lexTrue consumes the "rue" in "true". It assumes that 't' has already
-// been consumed.
-func lexTrue(lx *lexer) stateFn {
- if fn := lexConst(lx, "true"); fn != nil {
- return fn
+ s := string(rs)
+ switch s {
+ case "true", "false":
+ lx.emit(itemBool)
+ return lx.pop()
}
- lx.emit(itemBool)
- return lx.pop()
-}
-
-// lexFalse consumes the "alse" in "false". It assumes that 'f' has already
-// been consumed.
-func lexFalse(lx *lexer) stateFn {
- if fn := lexConst(lx, "false"); fn != nil {
- return fn
- }
- lx.emit(itemBool)
- return lx.pop()
+ return lx.errorf("expected value but found %q instead", s)
}
// lexCommentStart begins the lexing of a comment. It will emit
@@ -779,7 +864,7 @@ func lexCommentStart(lx *lexer) stateFn {
}
// lexComment lexes an entire comment. It assumes that '#' has been consumed.
-// It will consume *up to* the first new line character, and pass control
+// It will consume *up to* the first newline character, and pass control
// back to the last state on the stack.
func lexComment(lx *lexer) stateFn {
r := lx.peek()
@@ -837,13 +922,7 @@ func (itype itemType) String() string {
return "EOF"
case itemText:
return "Text"
- case itemString:
- return "String"
- case itemRawString:
- return "String"
- case itemMultilineString:
- return "String"
- case itemRawMultilineString:
+ case itemString, itemRawString, itemMultilineString, itemRawMultilineString:
return "String"
case itemBool:
return "Bool"
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/parse.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/parse.go
similarity index 80%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/parse.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/parse.go
index c6069be..50869ef 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/parse.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/parse.go
@@ -2,7 +2,6 @@ package toml
import (
"fmt"
- "log"
"strconv"
"strings"
"time"
@@ -81,7 +80,7 @@ func (p *parser) next() item {
}
func (p *parser) bug(format string, v ...interface{}) {
- log.Fatalf("BUG: %s\n\n", fmt.Sprintf(format, v...))
+ panic(fmt.Sprintf("BUG: "+format+"\n\n", v...))
}
func (p *parser) expect(typ itemType) item {
@@ -179,10 +178,18 @@ func (p *parser) value(it item) (interface{}, tomlType) {
}
p.bug("Expected boolean value, but got '%s'.", it.val)
case itemInteger:
- num, err := strconv.ParseInt(it.val, 10, 64)
+ if !numUnderscoresOK(it.val) {
+ p.panicf("Invalid integer %q: underscores must be surrounded by digits",
+ it.val)
+ }
+ val := strings.Replace(it.val, "_", "", -1)
+ num, err := strconv.ParseInt(val, 10, 64)
if err != nil {
- // See comment below for floats describing why we make a
- // distinction between a bug and a user error.
+ // Distinguish integer values. Normally, it'd be a bug if the lexer
+ // provides an invalid integer, but it's possible that the number is
+ // out of range of valid values (which the lexer cannot determine).
+ // So mark the former as a bug but the latter as a legitimate user
+ // error.
if e, ok := err.(*strconv.NumError); ok &&
e.Err == strconv.ErrRange {
@@ -194,29 +201,57 @@ func (p *parser) value(it item) (interface{}, tomlType) {
}
return num, p.typeOfPrimitive(it)
case itemFloat:
- num, err := strconv.ParseFloat(it.val, 64)
+ parts := strings.FieldsFunc(it.val, func(r rune) bool {
+ switch r {
+ case '.', 'e', 'E':
+ return true
+ }
+ return false
+ })
+ for _, part := range parts {
+ if !numUnderscoresOK(part) {
+ p.panicf("Invalid float %q: underscores must be "+
+ "surrounded by digits", it.val)
+ }
+ }
+ if !numPeriodsOK(it.val) {
+ // As a special case, numbers like '123.' or '1.e2',
+ // which are valid as far as Go/strconv are concerned,
+ // must be rejected because TOML says that a fractional
+ // part consists of '.' followed by 1+ digits.
+ p.panicf("Invalid float %q: '.' must be followed "+
+ "by one or more digits", it.val)
+ }
+ val := strings.Replace(it.val, "_", "", -1)
+ num, err := strconv.ParseFloat(val, 64)
if err != nil {
- // Distinguish float values. Normally, it'd be a bug if the lexer
- // provides an invalid float, but it's possible that the float is
- // out of range of valid values (which the lexer cannot determine).
- // So mark the former as a bug but the latter as a legitimate user
- // error.
- //
- // This is also true for integers.
if e, ok := err.(*strconv.NumError); ok &&
e.Err == strconv.ErrRange {
p.panicf("Float '%s' is out of the range of 64-bit "+
"IEEE-754 floating-point numbers.", it.val)
} else {
- p.bug("Expected float value, but got '%s'.", it.val)
+ p.panicf("Invalid float value: %q", it.val)
}
}
return num, p.typeOfPrimitive(it)
case itemDatetime:
- t, err := time.Parse("2006-01-02T15:04:05Z", it.val)
- if err != nil {
- p.bug("Expected Zulu formatted DateTime, but got '%s'.", it.val)
+ var t time.Time
+ var ok bool
+ var err error
+ for _, format := range []string{
+ "2006-01-02T15:04:05Z07:00",
+ "2006-01-02T15:04:05",
+ "2006-01-02",
+ } {
+ t, err = time.ParseInLocation(format, it.val, time.Local)
+ if err == nil {
+ ok = true
+ break
+ }
+ }
+ if !ok {
+ p.panicf("Invalid TOML Datetime: %q.", it.val)
}
return t, p.typeOfPrimitive(it)
case itemArray:
@@ -234,11 +269,75 @@ func (p *parser) value(it item) (interface{}, tomlType) {
types = append(types, typ)
}
return array, p.typeOfArray(types)
+ case itemInlineTableStart:
+ var (
+ hash = make(map[string]interface{})
+ outerContext = p.context
+ outerKey = p.currentKey
+ )
+
+ p.context = append(p.context, p.currentKey)
+ p.currentKey = ""
+ for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
+ if it.typ != itemKeyStart {
+ p.bug("Expected key start but instead found %q, around line %d",
+ it.val, p.approxLine)
+ }
+ if it.typ == itemCommentStart {
+ p.expect(itemText)
+ continue
+ }
+
+ // retrieve key
+ k := p.next()
+ p.approxLine = k.line
+ kname := p.keyString(k)
+
+ // retrieve value
+ p.currentKey = kname
+ val, typ := p.value(p.next())
+ // make sure we keep metadata up to date
+ p.setType(kname, typ)
+ p.ordered = append(p.ordered, p.context.add(p.currentKey))
+ hash[kname] = val
+ }
+ p.context = outerContext
+ p.currentKey = outerKey
+ return hash, tomlHash
}
p.bug("Unexpected value type: %s", it.typ)
panic("unreachable")
}
+// numUnderscoresOK checks whether each underscore in s is surrounded by
+// characters that are not underscores.
+func numUnderscoresOK(s string) bool {
+ accept := false
+ for _, r := range s {
+ if r == '_' {
+ if !accept {
+ return false
+ }
+ accept = false
+ continue
+ }
+ accept = true
+ }
+ return accept
+}
+
+// numPeriodsOK checks whether every period in s is followed by a digit.
+func numPeriodsOK(s string) bool {
+ period := false
+ for _, r := range s {
+ if period && !isDigit(r) {
+ return false
+ }
+ period = r == '.'
+ }
+ return !period
+}
+
// establishContext sets the current context of the parser,
// where the context is either a hash or an array of hashes. Which one is
// set depends on the value of the `array` parameter.
@@ -401,7 +500,7 @@ func stripFirstNewline(s string) string {
if len(s) == 0 || s[0] != '\n' {
return s
}
- return s[1:len(s)]
+ return s[1:]
}
func stripEscapedWhitespace(s string) string {
@@ -481,12 +580,7 @@ func (p *parser) asciiEscapeToUnicode(bs []byte) rune {
p.bug("Could not parse '%s' as a hexadecimal number, but the "+
"lexer claims it's OK: %s", s, err)
}
-
- // BUG(burntsushi)
- // I honestly don't understand how this works. I can't seem
- // to find a way to make this fail. I figured this would fail on invalid
- // UTF-8 characters like U+DCFF, but it doesn't.
- if !utf8.ValidString(string(rune(hex))) {
+ if !utf8.ValidRune(rune(hex)) {
p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s)
}
return rune(hex)
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/type_check.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/type_check.go
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/type_check.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/type_check.go
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/type_fields.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/type_fields.go
similarity index 96%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/type_fields.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/type_fields.go
index 7592f87..608997c 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/BurntSushi/toml/type_fields.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/BurntSushi/toml/type_fields.go
@@ -92,11 +92,11 @@ func typeFields(t reflect.Type) []field {
// Scan f.typ for fields to include.
for i := 0; i < f.typ.NumField(); i++ {
sf := f.typ.Field(i)
- if sf.PkgPath != "" { // unexported
+ if sf.PkgPath != "" && !sf.Anonymous { // unexported
continue
}
- name := sf.Tag.Get("toml")
- if name == "-" {
+ opts := getOptions(sf.Tag)
+ if opts.skip {
continue
}
index := make([]int, len(f.index)+1)
@@ -110,8 +110,9 @@ func typeFields(t reflect.Type) []field {
}
// Record found field and index sequence.
- if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
- tagged := name != ""
+ if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
+ tagged := opts.name != ""
+ name := opts.name
if name == "" {
name = sf.Name
}
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/AUTHORS b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/AUTHORS
new file mode 100644
index 0000000..fbe4ec4
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/AUTHORS
@@ -0,0 +1,90 @@
+# This is the official list of Go-MySQL-Driver authors for copyright purposes.
+
+# If you are submitting a patch, please add your name or the name of the
+# organization which holds the copyright to this list in alphabetical order.
+
+# Names should be added to this file as
+# Name
+# The email address is not required for organizations.
+# Please keep the list sorted.
+
+
+# Individual Persons
+
+Aaron Hopkins
+Achille Roussel
+Alexey Palazhchenko
+Andrew Reid
+Arne Hormann
+Asta Xie
+Bulat Gaifullin
+Carlos Nieto
+Chris Moos
+Craig Wilson
+Daniel Montoya
+Daniel Nichter
+Daniël van Eeden
+Dave Protasowski
+DisposaBoy
+Egor Smolyakov
+Evan Shaw
+Frederick Mayle
+Gustavo Kristic
+Hajime Nakagami
+Hanno Braun
+Henri Yandell
+Hirotaka Yamamoto
+ICHINOSE Shogo
+INADA Naoki
+Jacek Szwec
+James Harr
+Jeff Hodges
+Jeffrey Charles
+Jian Zhen
+Joshua Prunier
+Julien Lefevre
+Julien Schmidt
+Justin Li
+Justin Nuß
+Kamil Dziedzic
+Kevin Malachowski
+Kieron Woodhouse
+Lennart Rudolph
+Leonardo YongUk Kim
+Linh Tran Tuan
+Lion Yang
+Luca Looz
+Lucas Liu
+Luke Scott
+Maciej Zimnoch
+Michael Woolnough
+Nicola Peduzzi
+Olivier Mengué
+oscarzhao
+Paul Bonser
+Peter Schultz
+Rebecca Chin
+Reed Allman
+Richard Wilkes
+Robert Russell
+Runrioter Wung
+Shuode Li
+Soroush Pour
+Stan Putrya
+Stanley Gunawan
+Thomas Wodarek
+Xiangyu Hu
+Xiaobing Jiang
+Xiuming Chen
+Zhenye Xie
+
+# Organizations
+
+Barracuda Networks, Inc.
+Counting Ltd.
+Google Inc.
+InfoSum Ltd.
+Keybase Inc.
+Percona LLC
+Pivotal Inc.
+Stripe Inc.
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/LICENSE b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/LICENSE
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/LICENSE
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/LICENSE
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/appengine.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/appengine.go
similarity index 91%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/appengine.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/appengine.go
index 565614e..be41f2e 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/appengine.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/appengine.go
@@ -11,7 +11,7 @@
package mysql
import (
- "appengine/cloudsql"
+ "google.golang.org/appengine/cloudsql"
)
func init() {
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/auth.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/auth.go
new file mode 100644
index 0000000..2f61ecd
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/auth.go
@@ -0,0 +1,420 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2018 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+import (
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha1"
+ "crypto/sha256"
+ "crypto/x509"
+ "encoding/pem"
+ "sync"
+)
+
+// server pub keys registry
+var (
+ serverPubKeyLock sync.RWMutex
+ serverPubKeyRegistry map[string]*rsa.PublicKey
+)
+
+// RegisterServerPubKey registers a server RSA public key which can be used to
+// send data in a secure manner to the server without receiving the public key
+// in a potentially insecure way from the server first.
+// Registered keys can afterwards be used adding serverPubKey= to the DSN.
+//
+// Note: The provided rsa.PublicKey instance is exclusively owned by the driver
+// after registering it and may not be modified.
+//
+// data, err := ioutil.ReadFile("mykey.pem")
+// if err != nil {
+// log.Fatal(err)
+// }
+//
+// block, _ := pem.Decode(data)
+// if block == nil || block.Type != "PUBLIC KEY" {
+// log.Fatal("failed to decode PEM block containing public key")
+// }
+//
+// pub, err := x509.ParsePKIXPublicKey(block.Bytes)
+// if err != nil {
+// log.Fatal(err)
+// }
+//
+// if rsaPubKey, ok := pub.(*rsa.PublicKey); ok {
+// mysql.RegisterServerPubKey("mykey", rsaPubKey)
+// } else {
+// log.Fatal("not a RSA public key")
+// }
+//
+func RegisterServerPubKey(name string, pubKey *rsa.PublicKey) {
+ serverPubKeyLock.Lock()
+ if serverPubKeyRegistry == nil {
+ serverPubKeyRegistry = make(map[string]*rsa.PublicKey)
+ }
+
+ serverPubKeyRegistry[name] = pubKey
+ serverPubKeyLock.Unlock()
+}
+
+// DeregisterServerPubKey removes the public key registered with the given name.
+func DeregisterServerPubKey(name string) {
+ serverPubKeyLock.Lock()
+ if serverPubKeyRegistry != nil {
+ delete(serverPubKeyRegistry, name)
+ }
+ serverPubKeyLock.Unlock()
+}
+
+func getServerPubKey(name string) (pubKey *rsa.PublicKey) {
+ serverPubKeyLock.RLock()
+ if v, ok := serverPubKeyRegistry[name]; ok {
+ pubKey = v
+ }
+ serverPubKeyLock.RUnlock()
+ return
+}
+
+// Hash password using pre 4.1 (old password) method
+// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
+type myRnd struct {
+ seed1, seed2 uint32
+}
+
+const myRndMaxVal = 0x3FFFFFFF
+
+// Pseudo random number generator
+func newMyRnd(seed1, seed2 uint32) *myRnd {
+ return &myRnd{
+ seed1: seed1 % myRndMaxVal,
+ seed2: seed2 % myRndMaxVal,
+ }
+}
+
+// Tested to be equivalent to MariaDB's floating point variant
+// http://play.golang.org/p/QHvhd4qved
+// http://play.golang.org/p/RG0q4ElWDx
+func (r *myRnd) NextByte() byte {
+ r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
+ r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
+
+ return byte(uint64(r.seed1) * 31 / myRndMaxVal)
+}
+
+// Generate binary hash from byte string using insecure pre 4.1 method
+func pwHash(password []byte) (result [2]uint32) {
+ var add uint32 = 7
+ var tmp uint32
+
+ result[0] = 1345345333
+ result[1] = 0x12345671
+
+ for _, c := range password {
+ // skip spaces and tabs in password
+ if c == ' ' || c == '\t' {
+ continue
+ }
+
+ tmp = uint32(c)
+ result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
+ result[1] += (result[1] << 8) ^ result[0]
+ add += tmp
+ }
+
+ // Remove sign bit (1<<31)-1)
+ result[0] &= 0x7FFFFFFF
+ result[1] &= 0x7FFFFFFF
+
+ return
+}
+
+// Hash password using insecure pre 4.1 method
+func scrambleOldPassword(scramble []byte, password string) []byte {
+ if len(password) == 0 {
+ return nil
+ }
+
+ scramble = scramble[:8]
+
+ hashPw := pwHash([]byte(password))
+ hashSc := pwHash(scramble)
+
+ r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
+
+ var out [8]byte
+ for i := range out {
+ out[i] = r.NextByte() + 64
+ }
+
+ mask := r.NextByte()
+ for i := range out {
+ out[i] ^= mask
+ }
+
+ return out[:]
+}
+
+// Hash password using 4.1+ method (SHA1)
+func scramblePassword(scramble []byte, password string) []byte {
+ if len(password) == 0 {
+ return nil
+ }
+
+ // stage1Hash = SHA1(password)
+ crypt := sha1.New()
+ crypt.Write([]byte(password))
+ stage1 := crypt.Sum(nil)
+
+ // scrambleHash = SHA1(scramble + SHA1(stage1Hash))
+ // inner Hash
+ crypt.Reset()
+ crypt.Write(stage1)
+ hash := crypt.Sum(nil)
+
+ // outer Hash
+ crypt.Reset()
+ crypt.Write(scramble)
+ crypt.Write(hash)
+ scramble = crypt.Sum(nil)
+
+ // token = scrambleHash XOR stage1Hash
+ for i := range scramble {
+ scramble[i] ^= stage1[i]
+ }
+ return scramble
+}
+
+// Hash password using MySQL 8+ method (SHA256)
+func scrambleSHA256Password(scramble []byte, password string) []byte {
+ if len(password) == 0 {
+ return nil
+ }
+
+ // XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble))
+
+ crypt := sha256.New()
+ crypt.Write([]byte(password))
+ message1 := crypt.Sum(nil)
+
+ crypt.Reset()
+ crypt.Write(message1)
+ message1Hash := crypt.Sum(nil)
+
+ crypt.Reset()
+ crypt.Write(message1Hash)
+ crypt.Write(scramble)
+ message2 := crypt.Sum(nil)
+
+ for i := range message1 {
+ message1[i] ^= message2[i]
+ }
+
+ return message1
+}
+
+func encryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte, error) {
+ plain := make([]byte, len(password)+1)
+ copy(plain, password)
+ for i := range plain {
+ j := i % len(seed)
+ plain[i] ^= seed[j]
+ }
+ sha1 := sha1.New()
+ return rsa.EncryptOAEP(sha1, rand.Reader, pub, plain, nil)
+}
+
+func (mc *mysqlConn) sendEncryptedPassword(seed []byte, pub *rsa.PublicKey) error {
+ enc, err := encryptPassword(mc.cfg.Passwd, seed, pub)
+ if err != nil {
+ return err
+ }
+ return mc.writeAuthSwitchPacket(enc, false)
+}
+
+func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, bool, error) {
+ switch plugin {
+ case "caching_sha2_password":
+ authResp := scrambleSHA256Password(authData, mc.cfg.Passwd)
+ return authResp, false, nil
+
+ case "mysql_old_password":
+ if !mc.cfg.AllowOldPasswords {
+ return nil, false, ErrOldPassword
+ }
+ // Note: there are edge cases where this should work but doesn't;
+ // this is currently "wontfix":
+ // https://github.com/go-sql-driver/mysql/issues/184
+ authResp := scrambleOldPassword(authData[:8], mc.cfg.Passwd)
+ return authResp, true, nil
+
+ case "mysql_clear_password":
+ if !mc.cfg.AllowCleartextPasswords {
+ return nil, false, ErrCleartextPassword
+ }
+ // http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
+ // http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
+ return []byte(mc.cfg.Passwd), true, nil
+
+ case "mysql_native_password":
+ if !mc.cfg.AllowNativePasswords {
+ return nil, false, ErrNativePassword
+ }
+ // https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
+ // Native password authentication only need and will need 20-byte challenge.
+ authResp := scramblePassword(authData[:20], mc.cfg.Passwd)
+ return authResp, false, nil
+
+ case "sha256_password":
+ if len(mc.cfg.Passwd) == 0 {
+ return nil, true, nil
+ }
+ if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
+ // write cleartext auth packet
+ return []byte(mc.cfg.Passwd), true, nil
+ }
+
+ pubKey := mc.cfg.pubKey
+ if pubKey == nil {
+ // request public key from server
+ return []byte{1}, false, nil
+ }
+
+ // encrypted password
+ enc, err := encryptPassword(mc.cfg.Passwd, authData, pubKey)
+ return enc, false, err
+
+ default:
+ errLog.Print("unknown auth plugin:", plugin)
+ return nil, false, ErrUnknownPlugin
+ }
+}
+
+func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
+ // Read Result Packet
+ authData, newPlugin, err := mc.readAuthResult()
+ if err != nil {
+ return err
+ }
+
+ // handle auth plugin switch, if requested
+ if newPlugin != "" {
+ // If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
+ // sent and we have to keep using the cipher sent in the init packet.
+ if authData == nil {
+ authData = oldAuthData
+ } else {
+ // copy data from read buffer to owned slice
+ copy(oldAuthData, authData)
+ }
+
+ plugin = newPlugin
+
+ authResp, addNUL, err := mc.auth(authData, plugin)
+ if err != nil {
+ return err
+ }
+ if err = mc.writeAuthSwitchPacket(authResp, addNUL); err != nil {
+ return err
+ }
+
+ // Read Result Packet
+ authData, newPlugin, err = mc.readAuthResult()
+ if err != nil {
+ return err
+ }
+
+ // Do not allow to change the auth plugin more than once
+ if newPlugin != "" {
+ return ErrMalformPkt
+ }
+ }
+
+ switch plugin {
+
+ // https://insidemysql.com/preparing-your-community-connector-for-mysql-8-part-2-sha256/
+ case "caching_sha2_password":
+ switch len(authData) {
+ case 0:
+ return nil // auth successful
+ case 1:
+ switch authData[0] {
+ case cachingSha2PasswordFastAuthSuccess:
+ if err = mc.readResultOK(); err == nil {
+ return nil // auth successful
+ }
+
+ case cachingSha2PasswordPerformFullAuthentication:
+ if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
+ // write cleartext auth packet
+ err = mc.writeAuthSwitchPacket([]byte(mc.cfg.Passwd), true)
+ if err != nil {
+ return err
+ }
+ } else {
+ pubKey := mc.cfg.pubKey
+ if pubKey == nil {
+ // request public key from server
+ data := mc.buf.takeSmallBuffer(4 + 1)
+ data[4] = cachingSha2PasswordRequestPublicKey
+ mc.writePacket(data)
+
+ // parse public key
+ data, err := mc.readPacket()
+ if err != nil {
+ return err
+ }
+
+ block, _ := pem.Decode(data[1:])
+ pkix, err := x509.ParsePKIXPublicKey(block.Bytes)
+ if err != nil {
+ return err
+ }
+ pubKey = pkix.(*rsa.PublicKey)
+ }
+
+ // send encrypted password
+ err = mc.sendEncryptedPassword(oldAuthData, pubKey)
+ if err != nil {
+ return err
+ }
+ }
+ return mc.readResultOK()
+
+ default:
+ return ErrMalformPkt
+ }
+ default:
+ return ErrMalformPkt
+ }
+
+ case "sha256_password":
+ switch len(authData) {
+ case 0:
+ return nil // auth successful
+ default:
+ block, _ := pem.Decode(authData)
+ pub, err := x509.ParsePKIXPublicKey(block.Bytes)
+ if err != nil {
+ return err
+ }
+
+ // send encrypted password
+ err = mc.sendEncryptedPassword(oldAuthData, pub.(*rsa.PublicKey))
+ if err != nil {
+ return err
+ }
+ return mc.readResultOK()
+ }
+
+ default:
+ return nil // auth successful
+ }
+
+ return err
+}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/buffer.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/buffer.go
similarity index 96%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/buffer.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/buffer.go
index 2001fea..eb4748b 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/buffer.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/buffer.go
@@ -130,18 +130,18 @@ func (b *buffer) takeBuffer(length int) []byte {
// smaller than defaultBufSize
// Only one buffer (total) can be used at a time.
func (b *buffer) takeSmallBuffer(length int) []byte {
- if b.length == 0 {
- return b.buf[:length]
+ if b.length > 0 {
+ return nil
}
- return nil
+ return b.buf[:length]
}
// takeCompleteBuffer returns the complete existing buffer.
// This can be used if the necessary buffer size is unknown.
// Only one buffer (total) can be used at a time.
func (b *buffer) takeCompleteBuffer() []byte {
- if b.length == 0 {
- return b.buf
+ if b.length > 0 {
+ return nil
}
- return nil
+ return b.buf
}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/collations.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/collations.go
similarity index 99%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/collations.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/collations.go
index 82079cf..136c9e4 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/collations.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/collations.go
@@ -9,6 +9,7 @@
package mysql
const defaultCollation = "utf8_general_ci"
+const binaryCollation = "binary"
// A list of available collations mapped to the internal ID.
// To update this map use the following MySQL query:
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/connection.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/connection.go
similarity index 53%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/connection.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/connection.go
index c3899de..911be20 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/connection.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/connection.go
@@ -9,27 +9,47 @@
package mysql
import (
+ "context"
+ "database/sql"
"database/sql/driver"
+ "io"
"net"
"strconv"
"strings"
"time"
)
+// a copy of context.Context for Go 1.7 and earlier
+type mysqlContext interface {
+ Done() <-chan struct{}
+ Err() error
+
+ // defined in context.Context, but not used in this driver:
+ // Deadline() (deadline time.Time, ok bool)
+ // Value(key interface{}) interface{}
+}
+
type mysqlConn struct {
buf buffer
netConn net.Conn
affectedRows uint64
insertId uint64
cfg *Config
- maxPacketAllowed int
+ maxAllowedPacket int
maxWriteSize int
writeTimeout time.Duration
flags clientFlag
status statusFlag
sequence uint8
parseTime bool
- strict bool
+
+ // for context support (Go 1.8+)
+ watching bool
+ watcher chan<- mysqlContext
+ closech chan struct{}
+ finished chan<- struct{}
+ canceled atomicError // set non-nil if conn is canceled
+ closed atomicBool // set when conn is closed, before closech is closed
}
// Handles parameters set in DSN after the connection is established
@@ -62,22 +82,41 @@ func (mc *mysqlConn) handleParams() (err error) {
return
}
+func (mc *mysqlConn) markBadConn(err error) error {
+ if mc == nil {
+ return err
+ }
+ if err != errBadConnNoWrite {
+ return err
+ }
+ return driver.ErrBadConn
+}
+
func (mc *mysqlConn) Begin() (driver.Tx, error) {
- if mc.netConn == nil {
+ return mc.begin(false)
+}
+
+func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
+ if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
- err := mc.exec("START TRANSACTION")
+ var q string
+ if readOnly {
+ q = "START TRANSACTION READ ONLY"
+ } else {
+ q = "START TRANSACTION"
+ }
+ err := mc.exec(q)
if err == nil {
return &mysqlTx{mc}, err
}
-
- return nil, err
+ return nil, mc.markBadConn(err)
}
func (mc *mysqlConn) Close() (err error) {
// Makes Close idempotent
- if mc.netConn != nil {
+ if !mc.closed.IsSet() {
err = mc.writeCommandPacket(comQuit)
}
@@ -91,26 +130,39 @@ func (mc *mysqlConn) Close() (err error) {
// is called before auth or on auth failure because MySQL will have already
// closed the network connection.
func (mc *mysqlConn) cleanup() {
- // Makes cleanup idempotent
- if mc.netConn != nil {
- if err := mc.netConn.Close(); err != nil {
- errLog.Print(err)
- }
- mc.netConn = nil
+ if !mc.closed.TrySet(true) {
+ return
}
- mc.cfg = nil
- mc.buf.nc = nil
+
+ // Makes cleanup idempotent
+ close(mc.closech)
+ if mc.netConn == nil {
+ return
+ }
+ if err := mc.netConn.Close(); err != nil {
+ errLog.Print(err)
+ }
+}
+
+func (mc *mysqlConn) error() error {
+ if mc.closed.IsSet() {
+ if err := mc.canceled.Value(); err != nil {
+ return err
+ }
+ return ErrInvalidConn
+ }
+ return nil
}
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
- if mc.netConn == nil {
+ if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
// Send command
err := mc.writeCommandPacketStr(comStmtPrepare, query)
if err != nil {
- return nil, err
+ return nil, mc.markBadConn(err)
}
stmt := &mysqlStmt{
@@ -135,11 +187,16 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
}
func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) {
+ // Number of ? should be same to len(args)
+ if strings.Count(query, "?") != len(args) {
+ return "", driver.ErrSkip
+ }
+
buf := mc.buf.takeCompleteBuffer()
if buf == nil {
// can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer)
- return "", driver.ErrBadConn
+ return "", ErrInvalidConn
}
buf = buf[:0]
argPos := 0
@@ -241,7 +298,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
return "", driver.ErrSkip
}
- if len(buf)+4 > mc.maxPacketAllowed {
+ if len(buf)+4 > mc.maxAllowedPacket {
return "", driver.ErrSkip
}
}
@@ -252,7 +309,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
}
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
- if mc.netConn == nil {
+ if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
@@ -266,7 +323,6 @@ func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, err
return nil, err
}
query = prepared
- args = nil
}
mc.affectedRows = 0
mc.insertId = 0
@@ -278,32 +334,43 @@ func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, err
insertId: int64(mc.insertId),
}, err
}
- return nil, err
+ return nil, mc.markBadConn(err)
}
// Internal function to execute commands
func (mc *mysqlConn) exec(query string) error {
// Send command
- err := mc.writeCommandPacketStr(comQuery, query)
- if err != nil {
- return err
+ if err := mc.writeCommandPacketStr(comQuery, query); err != nil {
+ return mc.markBadConn(err)
}
// Read Result
resLen, err := mc.readResultSetHeaderPacket()
- if err == nil && resLen > 0 {
- if err = mc.readUntilEOF(); err != nil {
+ if err != nil {
+ return err
+ }
+
+ if resLen > 0 {
+ // columns
+ if err := mc.readUntilEOF(); err != nil {
return err
}
- err = mc.readUntilEOF()
+ // rows
+ if err := mc.readUntilEOF(); err != nil {
+ return err
+ }
}
- return err
+ return mc.discardResults()
}
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
- if mc.netConn == nil {
+ return mc.query(query, args)
+}
+
+func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
+ if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
@@ -317,7 +384,6 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
return nil, err
}
query = prepared
- args = nil
}
// Send command
err := mc.writeCommandPacketStr(comQuery, query)
@@ -330,15 +396,22 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
rows.mc = mc
if resLen == 0 {
- // no columns, no more data
- return emptyRows{}, nil
+ rows.rs.done = true
+
+ switch err := rows.NextResultSet(); err {
+ case nil, io.EOF:
+ return rows, nil
+ default:
+ return nil, err
+ }
}
+
// Columns
- rows.columns, err = mc.readColumns(resLen)
+ rows.rs.columns, err = mc.readColumns(resLen)
return rows, err
}
}
- return nil, err
+ return nil, mc.markBadConn(err)
}
// Gets the value of the given MySQL System Variable
@@ -354,7 +427,7 @@ func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
if err == nil {
rows := new(textRows)
rows.mc = mc
- rows.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
+ rows.rs.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
if resLen > 0 {
// Columns
@@ -370,3 +443,212 @@ func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
}
return nil, err
}
+
+// finish is called when the query has canceled.
+func (mc *mysqlConn) cancel(err error) {
+ mc.canceled.Set(err)
+ mc.cleanup()
+}
+
+// finish is called when the query has succeeded.
+func (mc *mysqlConn) finish() {
+ if !mc.watching || mc.finished == nil {
+ return
+ }
+ select {
+ case mc.finished <- struct{}{}:
+ mc.watching = false
+ case <-mc.closech:
+ }
+}
+
+// Ping implements driver.Pinger interface
+func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
+ if mc.closed.IsSet() {
+ errLog.Print(ErrInvalidConn)
+ return driver.ErrBadConn
+ }
+
+ if err = mc.watchCancel(ctx); err != nil {
+ return
+ }
+ defer mc.finish()
+
+ if err = mc.writeCommandPacket(comPing); err != nil {
+ return
+ }
+
+ return mc.readResultOK()
+}
+
+// BeginTx implements driver.ConnBeginTx interface
+func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
+ if err := mc.watchCancel(ctx); err != nil {
+ return nil, err
+ }
+ defer mc.finish()
+
+ if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
+ level, err := mapIsolationLevel(opts.Isolation)
+ if err != nil {
+ return nil, err
+ }
+ err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return mc.begin(opts.ReadOnly)
+}
+
+func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
+ dargs, err := namedValueToValue(args)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := mc.watchCancel(ctx); err != nil {
+ return nil, err
+ }
+
+ rows, err := mc.query(query, dargs)
+ if err != nil {
+ mc.finish()
+ return nil, err
+ }
+ rows.finish = mc.finish
+ return rows, err
+}
+
+func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
+ dargs, err := namedValueToValue(args)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := mc.watchCancel(ctx); err != nil {
+ return nil, err
+ }
+ defer mc.finish()
+
+ return mc.Exec(query, dargs)
+}
+
+func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
+ if err := mc.watchCancel(ctx); err != nil {
+ return nil, err
+ }
+
+ stmt, err := mc.Prepare(query)
+ mc.finish()
+ if err != nil {
+ return nil, err
+ }
+
+ select {
+ default:
+ case <-ctx.Done():
+ stmt.Close()
+ return nil, ctx.Err()
+ }
+ return stmt, nil
+}
+
+func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
+ dargs, err := namedValueToValue(args)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := stmt.mc.watchCancel(ctx); err != nil {
+ return nil, err
+ }
+
+ rows, err := stmt.query(dargs)
+ if err != nil {
+ stmt.mc.finish()
+ return nil, err
+ }
+ rows.finish = stmt.mc.finish
+ return rows, err
+}
+
+func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
+ dargs, err := namedValueToValue(args)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := stmt.mc.watchCancel(ctx); err != nil {
+ return nil, err
+ }
+ defer stmt.mc.finish()
+
+ return stmt.Exec(dargs)
+}
+
+func (mc *mysqlConn) watchCancel(ctx context.Context) error {
+ if mc.watching {
+ // Reach here if canceled,
+ // so the connection is already invalid
+ mc.cleanup()
+ return nil
+ }
+ if ctx.Done() == nil {
+ return nil
+ }
+
+ mc.watching = true
+ select {
+ default:
+ case <-ctx.Done():
+ return ctx.Err()
+ }
+ if mc.watcher == nil {
+ return nil
+ }
+
+ mc.watcher <- ctx
+
+ return nil
+}
+
+func (mc *mysqlConn) startWatcher() {
+ watcher := make(chan mysqlContext, 1)
+ mc.watcher = watcher
+ finished := make(chan struct{})
+ mc.finished = finished
+ go func() {
+ for {
+ var ctx mysqlContext
+ select {
+ case ctx = <-watcher:
+ case <-mc.closech:
+ return
+ }
+
+ select {
+ case <-ctx.Done():
+ mc.cancel(ctx.Err())
+ case <-finished:
+ case <-mc.closech:
+ return
+ }
+ }
+ }()
+}
+
+func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
+ nv.Value, err = converter{}.ConvertValue(nv.Value)
+ return
+}
+
+// ResetSession implements driver.SessionResetter.
+// (From Go 1.10)
+func (mc *mysqlConn) ResetSession(ctx context.Context) error {
+ if mc.closed.IsSet() {
+ return driver.ErrBadConn
+ }
+ return nil
+}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/const.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/const.go
similarity index 84%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/const.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/const.go
index 88cfff3..b1e6b85 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/const.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/const.go
@@ -9,7 +9,9 @@
package mysql
const (
- minProtocolVersion byte = 10
+ defaultAuthPlugin = "mysql_native_password"
+ defaultMaxAllowedPacket = 4 << 20 // 4 MiB
+ minProtocolVersion = 10
maxPacketSize = 1<<24 - 1
timeFormat = "2006-01-02 15:04:05.999999"
)
@@ -18,10 +20,11 @@ const (
// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
const (
- iOK byte = 0x00
- iLocalInFile byte = 0xfb
- iEOF byte = 0xfe
- iERR byte = 0xff
+ iOK byte = 0x00
+ iAuthMoreData byte = 0x01
+ iLocalInFile byte = 0xfb
+ iEOF byte = 0xfe
+ iERR byte = 0xff
)
// https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
@@ -87,8 +90,10 @@ const (
)
// https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType
+type fieldType byte
+
const (
- fieldTypeDecimal byte = iota
+ fieldTypeDecimal fieldType = iota
fieldTypeTiny
fieldTypeShort
fieldTypeLong
@@ -107,7 +112,7 @@ const (
fieldTypeBit
)
const (
- fieldTypeJSON byte = iota + 0xf5
+ fieldTypeJSON fieldType = iota + 0xf5
fieldTypeNewDecimal
fieldTypeEnum
fieldTypeSet
@@ -161,3 +166,9 @@ const (
statusInTransReadonly
statusSessionStateChanged
)
+
+const (
+ cachingSha2PasswordRequestPublicKey = 2
+ cachingSha2PasswordFastAuthSuccess = 3
+ cachingSha2PasswordPerformFullAuthentication = 4
+)
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/driver.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/driver.go
similarity index 66%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/driver.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/driver.go
index 899f955..ba12978 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/driver.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/driver.go
@@ -4,7 +4,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
-// Package mysql provides a MySQL driver for Go's database/sql package
+// Package mysql provides a MySQL driver for Go's database/sql package.
//
// The driver should be used via the database/sql package:
//
@@ -20,6 +20,7 @@ import (
"database/sql"
"database/sql/driver"
"net"
+ "sync"
)
// MySQLDriver is exported to make the driver directly accessible.
@@ -30,12 +31,17 @@ type MySQLDriver struct{}
// Custom dial functions must be registered with RegisterDial
type DialFunc func(addr string) (net.Conn, error)
-var dials map[string]DialFunc
+var (
+ dialsLock sync.RWMutex
+ dials map[string]DialFunc
+)
// RegisterDial registers a custom dial function. It can then be used by the
// network address mynet(addr), where mynet is the registered new network.
// addr is passed as a parameter to the dial function.
func RegisterDial(net string, dial DialFunc) {
+ dialsLock.Lock()
+ defer dialsLock.Unlock()
if dials == nil {
dials = make(map[string]DialFunc)
}
@@ -50,18 +56,21 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
// New mysqlConn
mc := &mysqlConn{
- maxPacketAllowed: maxPacketSize,
+ maxAllowedPacket: maxPacketSize,
maxWriteSize: maxPacketSize - 1,
+ closech: make(chan struct{}),
}
mc.cfg, err = ParseDSN(dsn)
if err != nil {
return nil, err
}
mc.parseTime = mc.cfg.ParseTime
- mc.strict = mc.cfg.Strict
// Connect to Server
- if dial, ok := dials[mc.cfg.Net]; ok {
+ dialsLock.RLock()
+ dial, ok := dials[mc.cfg.Net]
+ dialsLock.RUnlock()
+ if ok {
mc.netConn, err = dial(mc.cfg.Addr)
} else {
nd := net.Dialer{Timeout: mc.cfg.Timeout}
@@ -81,6 +90,9 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
}
}
+ // Call startWatcher for context support (From Go 1.8)
+ mc.startWatcher()
+
mc.buf = newBuffer(mc.netConn)
// Set I/O timeouts
@@ -88,20 +100,34 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
mc.writeTimeout = mc.cfg.WriteTimeout
// Reading Handshake Initialization Packet
- cipher, err := mc.readInitPacket()
+ authData, plugin, err := mc.readHandshakePacket()
if err != nil {
mc.cleanup()
return nil, err
}
+ if plugin == "" {
+ plugin = defaultAuthPlugin
+ }
// Send Client Authentication Packet
- if err = mc.writeAuthPacket(cipher); err != nil {
+ authResp, addNUL, err := mc.auth(authData, plugin)
+ if err != nil {
+ // try the default auth plugin, if using the requested plugin failed
+ errLog.Print("could not use requested auth plugin '"+plugin+"': ", err.Error())
+ plugin = defaultAuthPlugin
+ authResp, addNUL, err = mc.auth(authData, plugin)
+ if err != nil {
+ mc.cleanup()
+ return nil, err
+ }
+ }
+ if err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin); err != nil {
mc.cleanup()
return nil, err
}
// Handle response to auth packet, switch methods if possible
- if err = handleAuthResult(mc, cipher); err != nil {
+ if err = mc.handleAuthResult(authData, plugin); err != nil {
// Authentication failed and MySQL has already closed the connection
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
// Do not send COM_QUIT, just cleanup and return the error.
@@ -109,15 +135,19 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
return nil, err
}
- // Get max allowed packet size
- maxap, err := mc.getSystemVar("max_allowed_packet")
- if err != nil {
- mc.Close()
- return nil, err
+ if mc.cfg.MaxAllowedPacket > 0 {
+ mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket
+ } else {
+ // Get max allowed packet size
+ maxap, err := mc.getSystemVar("max_allowed_packet")
+ if err != nil {
+ mc.Close()
+ return nil, err
+ }
+ mc.maxAllowedPacket = stringToInt(maxap) - 1
}
- mc.maxPacketAllowed = stringToInt(maxap) - 1
- if mc.maxPacketAllowed < maxPacketSize {
- mc.maxWriteSize = mc.maxPacketAllowed
+ if mc.maxAllowedPacket < maxPacketSize {
+ mc.maxWriteSize = mc.maxAllowedPacket
}
// Handle DSN Params
@@ -130,38 +160,6 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
return mc, nil
}
-func handleAuthResult(mc *mysqlConn, cipher []byte) error {
- // Read Result Packet
- err := mc.readResultOK()
- if err == nil {
- return nil // auth successful
- }
-
- if mc.cfg == nil {
- return err // auth failed and retry not possible
- }
-
- // Retry auth if configured to do so.
- if mc.cfg.AllowOldPasswords && err == ErrOldPassword {
- // Retry with old authentication method. Note: there are edge cases
- // where this should work but doesn't; this is currently "wontfix":
- // https://github.com/go-sql-driver/mysql/issues/184
- if err = mc.writeOldAuthPacket(cipher); err != nil {
- return err
- }
- err = mc.readResultOK()
- } else if mc.cfg.AllowCleartextPasswords && err == ErrCleartextPassword {
- // Retry with clear text password for
- // http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
- // http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
- if err = mc.writeClearAuthPacket(); err != nil {
- return err
- }
- err = mc.readResultOK()
- }
- return err
-}
-
func init() {
sql.Register("mysql", &MySQLDriver{})
}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/dsn.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/dsn.go
similarity index 71%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/dsn.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/dsn.go
index 73138bc..be014ba 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/dsn.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/dsn.go
@@ -10,11 +10,14 @@ package mysql
import (
"bytes"
+ "crypto/rsa"
"crypto/tls"
"errors"
"fmt"
"net"
"net/url"
+ "sort"
+ "strconv"
"strings"
"time"
)
@@ -26,31 +29,84 @@ var (
errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations")
)
-// Config is a configuration parsed from a DSN string
+// Config is a configuration parsed from a DSN string.
+// If a new Config is created instead of being parsed from a DSN string,
+// the NewConfig function should be used, which sets default values.
type Config struct {
- User string // Username
- Passwd string // Password (requires User)
- Net string // Network type
- Addr string // Network address (requires Net)
- DBName string // Database name
- Params map[string]string // Connection parameters
- Collation string // Connection collation
- Loc *time.Location // Location for time.Time values
- TLSConfig string // TLS configuration name
- tls *tls.Config // TLS configuration
- Timeout time.Duration // Dial timeout
- ReadTimeout time.Duration // I/O read timeout
- WriteTimeout time.Duration // I/O write timeout
+ User string // Username
+ Passwd string // Password (requires User)
+ Net string // Network type
+ Addr string // Network address (requires Net)
+ DBName string // Database name
+ Params map[string]string // Connection parameters
+ Collation string // Connection collation
+ Loc *time.Location // Location for time.Time values
+ MaxAllowedPacket int // Max packet size allowed
+ ServerPubKey string // Server public key name
+ pubKey *rsa.PublicKey // Server public key
+ TLSConfig string // TLS configuration name
+ tls *tls.Config // TLS configuration
+ Timeout time.Duration // Dial timeout
+ ReadTimeout time.Duration // I/O read timeout
+ WriteTimeout time.Duration // I/O write timeout
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
AllowCleartextPasswords bool // Allows the cleartext client side plugin
+ AllowNativePasswords bool // Allows the native password authentication method
AllowOldPasswords bool // Allows the old insecure password method
ClientFoundRows bool // Return number of matching rows instead of rows changed
ColumnsWithAlias bool // Prepend table alias to column names
InterpolateParams bool // Interpolate placeholders into query string
MultiStatements bool // Allow multiple statements in one query
ParseTime bool // Parse time values to time.Time
- Strict bool // Return warnings as errors
+ RejectReadOnly bool // Reject read-only connections
+}
+
+// NewConfig creates a new Config and sets default values.
+func NewConfig() *Config {
+ return &Config{
+ Collation: defaultCollation,
+ Loc: time.UTC,
+ MaxAllowedPacket: defaultMaxAllowedPacket,
+ AllowNativePasswords: true,
+ }
+}
+
+func (cfg *Config) normalize() error {
+ if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
+ return errInvalidDSNUnsafeCollation
+ }
+
+ // Set default network if empty
+ if cfg.Net == "" {
+ cfg.Net = "tcp"
+ }
+
+ // Set default address if empty
+ if cfg.Addr == "" {
+ switch cfg.Net {
+ case "tcp":
+ cfg.Addr = "127.0.0.1:3306"
+ case "unix":
+ cfg.Addr = "/tmp/mysql.sock"
+ default:
+ return errors.New("default addr for network '" + cfg.Net + "' unknown")
+ }
+
+ } else if cfg.Net == "tcp" {
+ cfg.Addr = ensureHavePort(cfg.Addr)
+ }
+
+ if cfg.tls != nil {
+ if cfg.tls.ServerName == "" && !cfg.tls.InsecureSkipVerify {
+ host, _, err := net.SplitHostPort(cfg.Addr)
+ if err == nil {
+ cfg.tls.ServerName = host
+ }
+ }
+ }
+
+ return nil
}
// FormatDSN formats the given Config into a DSN string which can be passed to
@@ -99,6 +155,15 @@ func (cfg *Config) FormatDSN() string {
}
}
+ if !cfg.AllowNativePasswords {
+ if hasParam {
+ buf.WriteString("&allowNativePasswords=false")
+ } else {
+ hasParam = true
+ buf.WriteString("?allowNativePasswords=false")
+ }
+ }
+
if cfg.AllowOldPasswords {
if hasParam {
buf.WriteString("&allowOldPasswords=true")
@@ -183,15 +248,25 @@ func (cfg *Config) FormatDSN() string {
buf.WriteString(cfg.ReadTimeout.String())
}
- if cfg.Strict {
+ if cfg.RejectReadOnly {
if hasParam {
- buf.WriteString("&strict=true")
+ buf.WriteString("&rejectReadOnly=true")
} else {
hasParam = true
- buf.WriteString("?strict=true")
+ buf.WriteString("?rejectReadOnly=true")
}
}
+ if len(cfg.ServerPubKey) > 0 {
+ if hasParam {
+ buf.WriteString("&serverPubKey=")
+ } else {
+ hasParam = true
+ buf.WriteString("?serverPubKey=")
+ }
+ buf.WriteString(url.QueryEscape(cfg.ServerPubKey))
+ }
+
if cfg.Timeout > 0 {
if hasParam {
buf.WriteString("&timeout=")
@@ -222,9 +297,25 @@ func (cfg *Config) FormatDSN() string {
buf.WriteString(cfg.WriteTimeout.String())
}
+ if cfg.MaxAllowedPacket != defaultMaxAllowedPacket {
+ if hasParam {
+ buf.WriteString("&maxAllowedPacket=")
+ } else {
+ hasParam = true
+ buf.WriteString("?maxAllowedPacket=")
+ }
+ buf.WriteString(strconv.Itoa(cfg.MaxAllowedPacket))
+
+ }
+
// other params
if cfg.Params != nil {
- for param, value := range cfg.Params {
+ var params []string
+ for param := range cfg.Params {
+ params = append(params, param)
+ }
+ sort.Strings(params)
+ for _, param := range params {
if hasParam {
buf.WriteByte('&')
} else {
@@ -234,7 +325,7 @@ func (cfg *Config) FormatDSN() string {
buf.WriteString(param)
buf.WriteByte('=')
- buf.WriteString(url.QueryEscape(value))
+ buf.WriteString(url.QueryEscape(cfg.Params[param]))
}
}
@@ -244,10 +335,7 @@ func (cfg *Config) FormatDSN() string {
// ParseDSN parses the DSN string to a Config
func ParseDSN(dsn string) (cfg *Config, err error) {
// New config with some default values
- cfg = &Config{
- Loc: time.UTC,
- Collation: defaultCollation,
- }
+ cfg = NewConfig()
// [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN]
// Find the last '/' (since the password or the net addr might contain a '/')
@@ -315,28 +403,9 @@ func ParseDSN(dsn string) (cfg *Config, err error) {
return nil, errInvalidDSNNoSlash
}
- if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
- return nil, errInvalidDSNUnsafeCollation
+ if err = cfg.normalize(); err != nil {
+ return nil, err
}
-
- // Set default network if empty
- if cfg.Net == "" {
- cfg.Net = "tcp"
- }
-
- // Set default address if empty
- if cfg.Addr == "" {
- switch cfg.Net {
- case "tcp":
- cfg.Addr = "127.0.0.1:3306"
- case "unix":
- cfg.Addr = "/tmp/mysql.sock"
- default:
- return nil, errors.New("default addr for network '" + cfg.Net + "' unknown")
- }
-
- }
-
return
}
@@ -351,7 +420,6 @@ func parseDSNParams(cfg *Config, params string) (err error) {
// cfg params
switch value := param[1]; param[0] {
-
// Disable INFILE whitelist / enable all files
case "allowAllFiles":
var isBool bool
@@ -368,6 +436,14 @@ func parseDSNParams(cfg *Config, params string) (err error) {
return errors.New("invalid bool value: " + value)
}
+ // Use native password authentication
+ case "allowNativePasswords":
+ var isBool bool
+ cfg.AllowNativePasswords, isBool = readBool(value)
+ if !isBool {
+ return errors.New("invalid bool value: " + value)
+ }
+
// Use old authentication mode (pre MySQL 4.1)
case "allowOldPasswords":
var isBool bool
@@ -441,14 +517,32 @@ func parseDSNParams(cfg *Config, params string) (err error) {
return
}
- // Strict mode
- case "strict":
+ // Reject read-only connections
+ case "rejectReadOnly":
var isBool bool
- cfg.Strict, isBool = readBool(value)
+ cfg.RejectReadOnly, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
+ // Server public key
+ case "serverPubKey":
+ name, err := url.QueryUnescape(value)
+ if err != nil {
+ return fmt.Errorf("invalid value for server pub key name: %v", err)
+ }
+
+ if pubKey := getServerPubKey(name); pubKey != nil {
+ cfg.ServerPubKey = name
+ cfg.pubKey = pubKey
+ } else {
+ return errors.New("invalid value / unknown server pub key name: " + name)
+ }
+
+ // Strict mode
+ case "strict":
+ panic("strict mode has been removed. See https://github.com/go-sql-driver/mysql/wiki/strict-mode")
+
// Dial Timeout
case "timeout":
cfg.Timeout, err = time.ParseDuration(value)
@@ -475,14 +569,7 @@ func parseDSNParams(cfg *Config, params string) (err error) {
return fmt.Errorf("invalid value for TLS config name: %v", err)
}
- if tlsConfig, ok := tlsConfigRegister[name]; ok {
- if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
- host, _, err := net.SplitHostPort(cfg.Addr)
- if err == nil {
- tlsConfig.ServerName = host
- }
- }
-
+ if tlsConfig := getTLSConfigClone(name); tlsConfig != nil {
cfg.TLSConfig = name
cfg.tls = tlsConfig
} else {
@@ -496,7 +583,11 @@ func parseDSNParams(cfg *Config, params string) (err error) {
if err != nil {
return
}
-
+ case "maxAllowedPacket":
+ cfg.MaxAllowedPacket, err = strconv.Atoi(value)
+ if err != nil {
+ return
+ }
default:
// lazy init
if cfg.Params == nil {
@@ -511,3 +602,10 @@ func parseDSNParams(cfg *Config, params string) (err error) {
return
}
+
+func ensureHavePort(addr string) string {
+ if _, _, err := net.SplitHostPort(addr); err != nil {
+ return net.JoinHostPort(addr, "3306")
+ }
+ return addr
+}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/errors.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/errors.go
similarity index 61%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/errors.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/errors.go
index 1543a80..760782f 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/errors.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/errors.go
@@ -9,10 +9,8 @@
package mysql
import (
- "database/sql/driver"
"errors"
"fmt"
- "io"
"log"
"os"
)
@@ -22,14 +20,21 @@ var (
ErrInvalidConn = errors.New("invalid connection")
ErrMalformPkt = errors.New("malformed packet")
ErrNoTLS = errors.New("TLS requested but server does not support TLS")
- ErrOldPassword = errors.New("this user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
ErrCleartextPassword = errors.New("this user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN")
+ ErrNativePassword = errors.New("this user requires mysql native password authentication.")
+ ErrOldPassword = errors.New("this user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
ErrUnknownPlugin = errors.New("this authentication plugin is not supported")
ErrOldProtocol = errors.New("MySQL server does not support required protocol 41+")
ErrPktSync = errors.New("commands out of sync. You can't run this command now")
ErrPktSyncMul = errors.New("commands out of sync. Did you run multiple statements at once?")
ErrPktTooLarge = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server")
ErrBusyBuffer = errors.New("busy buffer")
+
+ // errBadConnNoWrite is used for connection errors where nothing was sent to the database yet.
+ // If this happens first in a function starting a database interaction, it should be replaced by driver.ErrBadConn
+ // to trigger a resend.
+ // See https://github.com/go-sql-driver/mysql/pull/302
+ errBadConnNoWrite = errors.New("bad connection")
)
var errLog = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))
@@ -58,74 +63,3 @@ type MySQLError struct {
func (me *MySQLError) Error() string {
return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
}
-
-// MySQLWarnings is an error type which represents a group of one or more MySQL
-// warnings
-type MySQLWarnings []MySQLWarning
-
-func (mws MySQLWarnings) Error() string {
- var msg string
- for i, warning := range mws {
- if i > 0 {
- msg += "\r\n"
- }
- msg += fmt.Sprintf(
- "%s %s: %s",
- warning.Level,
- warning.Code,
- warning.Message,
- )
- }
- return msg
-}
-
-// MySQLWarning is an error type which represents a single MySQL warning.
-// Warnings are returned in groups only. See MySQLWarnings
-type MySQLWarning struct {
- Level string
- Code string
- Message string
-}
-
-func (mc *mysqlConn) getWarnings() (err error) {
- rows, err := mc.Query("SHOW WARNINGS", nil)
- if err != nil {
- return
- }
-
- var warnings = MySQLWarnings{}
- var values = make([]driver.Value, 3)
-
- for {
- err = rows.Next(values)
- switch err {
- case nil:
- warning := MySQLWarning{}
-
- if raw, ok := values[0].([]byte); ok {
- warning.Level = string(raw)
- } else {
- warning.Level = fmt.Sprintf("%s", values[0])
- }
- if raw, ok := values[1].([]byte); ok {
- warning.Code = string(raw)
- } else {
- warning.Code = fmt.Sprintf("%s", values[1])
- }
- if raw, ok := values[2].([]byte); ok {
- warning.Message = string(raw)
- } else {
- warning.Message = fmt.Sprintf("%s", values[0])
- }
-
- warnings = append(warnings, warning)
-
- case io.EOF:
- return warnings
-
- default:
- rows.Close()
- return
- }
- }
-}
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/fields.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/fields.go
new file mode 100644
index 0000000..e1e2ece
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/fields.go
@@ -0,0 +1,194 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+import (
+ "database/sql"
+ "reflect"
+)
+
+func (mf *mysqlField) typeDatabaseName() string {
+ switch mf.fieldType {
+ case fieldTypeBit:
+ return "BIT"
+ case fieldTypeBLOB:
+ if mf.charSet != collations[binaryCollation] {
+ return "TEXT"
+ }
+ return "BLOB"
+ case fieldTypeDate:
+ return "DATE"
+ case fieldTypeDateTime:
+ return "DATETIME"
+ case fieldTypeDecimal:
+ return "DECIMAL"
+ case fieldTypeDouble:
+ return "DOUBLE"
+ case fieldTypeEnum:
+ return "ENUM"
+ case fieldTypeFloat:
+ return "FLOAT"
+ case fieldTypeGeometry:
+ return "GEOMETRY"
+ case fieldTypeInt24:
+ return "MEDIUMINT"
+ case fieldTypeJSON:
+ return "JSON"
+ case fieldTypeLong:
+ return "INT"
+ case fieldTypeLongBLOB:
+ if mf.charSet != collations[binaryCollation] {
+ return "LONGTEXT"
+ }
+ return "LONGBLOB"
+ case fieldTypeLongLong:
+ return "BIGINT"
+ case fieldTypeMediumBLOB:
+ if mf.charSet != collations[binaryCollation] {
+ return "MEDIUMTEXT"
+ }
+ return "MEDIUMBLOB"
+ case fieldTypeNewDate:
+ return "DATE"
+ case fieldTypeNewDecimal:
+ return "DECIMAL"
+ case fieldTypeNULL:
+ return "NULL"
+ case fieldTypeSet:
+ return "SET"
+ case fieldTypeShort:
+ return "SMALLINT"
+ case fieldTypeString:
+ if mf.charSet == collations[binaryCollation] {
+ return "BINARY"
+ }
+ return "CHAR"
+ case fieldTypeTime:
+ return "TIME"
+ case fieldTypeTimestamp:
+ return "TIMESTAMP"
+ case fieldTypeTiny:
+ return "TINYINT"
+ case fieldTypeTinyBLOB:
+ if mf.charSet != collations[binaryCollation] {
+ return "TINYTEXT"
+ }
+ return "TINYBLOB"
+ case fieldTypeVarChar:
+ if mf.charSet == collations[binaryCollation] {
+ return "VARBINARY"
+ }
+ return "VARCHAR"
+ case fieldTypeVarString:
+ if mf.charSet == collations[binaryCollation] {
+ return "VARBINARY"
+ }
+ return "VARCHAR"
+ case fieldTypeYear:
+ return "YEAR"
+ default:
+ return ""
+ }
+}
+
+var (
+ scanTypeFloat32 = reflect.TypeOf(float32(0))
+ scanTypeFloat64 = reflect.TypeOf(float64(0))
+ scanTypeInt8 = reflect.TypeOf(int8(0))
+ scanTypeInt16 = reflect.TypeOf(int16(0))
+ scanTypeInt32 = reflect.TypeOf(int32(0))
+ scanTypeInt64 = reflect.TypeOf(int64(0))
+ scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
+ scanTypeNullInt = reflect.TypeOf(sql.NullInt64{})
+ scanTypeNullTime = reflect.TypeOf(NullTime{})
+ scanTypeUint8 = reflect.TypeOf(uint8(0))
+ scanTypeUint16 = reflect.TypeOf(uint16(0))
+ scanTypeUint32 = reflect.TypeOf(uint32(0))
+ scanTypeUint64 = reflect.TypeOf(uint64(0))
+ scanTypeRawBytes = reflect.TypeOf(sql.RawBytes{})
+ scanTypeUnknown = reflect.TypeOf(new(interface{}))
+)
+
+type mysqlField struct {
+ tableName string
+ name string
+ length uint32
+ flags fieldFlag
+ fieldType fieldType
+ decimals byte
+ charSet uint8
+}
+
+func (mf *mysqlField) scanType() reflect.Type {
+ switch mf.fieldType {
+ case fieldTypeTiny:
+ if mf.flags&flagNotNULL != 0 {
+ if mf.flags&flagUnsigned != 0 {
+ return scanTypeUint8
+ }
+ return scanTypeInt8
+ }
+ return scanTypeNullInt
+
+ case fieldTypeShort, fieldTypeYear:
+ if mf.flags&flagNotNULL != 0 {
+ if mf.flags&flagUnsigned != 0 {
+ return scanTypeUint16
+ }
+ return scanTypeInt16
+ }
+ return scanTypeNullInt
+
+ case fieldTypeInt24, fieldTypeLong:
+ if mf.flags&flagNotNULL != 0 {
+ if mf.flags&flagUnsigned != 0 {
+ return scanTypeUint32
+ }
+ return scanTypeInt32
+ }
+ return scanTypeNullInt
+
+ case fieldTypeLongLong:
+ if mf.flags&flagNotNULL != 0 {
+ if mf.flags&flagUnsigned != 0 {
+ return scanTypeUint64
+ }
+ return scanTypeInt64
+ }
+ return scanTypeNullInt
+
+ case fieldTypeFloat:
+ if mf.flags&flagNotNULL != 0 {
+ return scanTypeFloat32
+ }
+ return scanTypeNullFloat
+
+ case fieldTypeDouble:
+ if mf.flags&flagNotNULL != 0 {
+ return scanTypeFloat64
+ }
+ return scanTypeNullFloat
+
+ case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar,
+ fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB,
+ fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB,
+ fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON,
+ fieldTypeTime:
+ return scanTypeRawBytes
+
+ case fieldTypeDate, fieldTypeNewDate,
+ fieldTypeTimestamp, fieldTypeDateTime:
+ // NullTime is always returned for more consistent behavior as it can
+ // handle both cases of parseTime regardless if the field is nullable.
+ return scanTypeNullTime
+
+ default:
+ return scanTypeUnknown
+ }
+}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/infile.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/infile.go
similarity index 98%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/infile.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/infile.go
index 0f975bb..273cb0b 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/infile.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/infile.go
@@ -147,7 +147,8 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
}
// send content packets
- if err == nil {
+ // if packetSize == 0, the Reader contains no data
+ if err == nil && packetSize > 0 {
data := make([]byte, 4+packetSize)
var n int
for err == nil {
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/packets.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/packets.go
similarity index 76%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/packets.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/packets.go
index 8d91665..170aaa0 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/packets.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/packets.go
@@ -25,26 +25,23 @@ import (
// Read packet to buffer 'data'
func (mc *mysqlConn) readPacket() ([]byte, error) {
- var payload []byte
+ var prevData []byte
for {
- // Read packet header
+ // read packet header
data, err := mc.buf.readNext(4)
if err != nil {
+ if cerr := mc.canceled.Value(); cerr != nil {
+ return nil, cerr
+ }
errLog.Print(err)
mc.Close()
- return nil, driver.ErrBadConn
+ return nil, ErrInvalidConn
}
- // Packet Length [24 bit]
+ // packet length [24 bit]
pktLen := int(uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16)
- if pktLen < 1 {
- errLog.Print(ErrMalformPkt)
- mc.Close()
- return nil, driver.ErrBadConn
- }
-
- // Check Packet Sync [8 bit]
+ // check packet sync [8 bit]
if data[3] != mc.sequence {
if data[3] > mc.sequence {
return nil, ErrPktSyncMul
@@ -53,26 +50,41 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
}
mc.sequence++
- // Read packet body [pktLen bytes]
+ // packets with length 0 terminate a previous packet which is a
+ // multiple of (2^24)−1 bytes long
+ if pktLen == 0 {
+ // there was no previous packet
+ if prevData == nil {
+ errLog.Print(ErrMalformPkt)
+ mc.Close()
+ return nil, ErrInvalidConn
+ }
+
+ return prevData, nil
+ }
+
+ // read packet body [pktLen bytes]
data, err = mc.buf.readNext(pktLen)
if err != nil {
+ if cerr := mc.canceled.Value(); cerr != nil {
+ return nil, cerr
+ }
errLog.Print(err)
mc.Close()
- return nil, driver.ErrBadConn
+ return nil, ErrInvalidConn
}
- isLastPacket := (pktLen < maxPacketSize)
+ // return data if this was the last packet
+ if pktLen < maxPacketSize {
+ // zero allocations for non-split packets
+ if prevData == nil {
+ return data, nil
+ }
- // Zero allocations for non-splitting packets
- if isLastPacket && payload == nil {
- return data, nil
+ return append(prevData, data...), nil
}
- payload = append(payload, data...)
-
- if isLastPacket {
- return payload, nil
- }
+ prevData = append(prevData, data...)
}
}
@@ -80,7 +92,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
func (mc *mysqlConn) writePacket(data []byte) error {
pktLen := len(data) - 4
- if pktLen > mc.maxPacketAllowed {
+ if pktLen > mc.maxAllowedPacket {
return ErrPktTooLarge
}
@@ -119,33 +131,47 @@ func (mc *mysqlConn) writePacket(data []byte) error {
// Handle error
if err == nil { // n != len(data)
+ mc.cleanup()
errLog.Print(ErrMalformPkt)
} else {
+ if cerr := mc.canceled.Value(); cerr != nil {
+ return cerr
+ }
+ if n == 0 && pktLen == len(data)-4 {
+ // only for the first loop iteration when nothing was written yet
+ return errBadConnNoWrite
+ }
+ mc.cleanup()
errLog.Print(err)
}
- return driver.ErrBadConn
+ return ErrInvalidConn
}
}
/******************************************************************************
-* Initialisation Process *
+* Initialization Process *
******************************************************************************/
// Handshake Initialization Packet
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake
-func (mc *mysqlConn) readInitPacket() ([]byte, error) {
- data, err := mc.readPacket()
+func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err error) {
+ data, err = mc.readPacket()
if err != nil {
- return nil, err
+ // for init we can rewrite this to ErrBadConn for sql.Driver to retry, since
+ // in connection initialization we don't risk retrying non-idempotent actions.
+ if err == ErrInvalidConn {
+ return nil, "", driver.ErrBadConn
+ }
+ return
}
if data[0] == iERR {
- return nil, mc.handleErrorPacket(data)
+ return nil, "", mc.handleErrorPacket(data)
}
// protocol version [1 byte]
if data[0] < minProtocolVersion {
- return nil, fmt.Errorf(
+ return nil, "", fmt.Errorf(
"unsupported protocol version %d. Version %d or higher is required",
data[0],
minProtocolVersion,
@@ -157,7 +183,7 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
pos := 1 + bytes.IndexByte(data[1:], 0x00) + 1 + 4
// first part of the password cipher [8 bytes]
- cipher := data[pos : pos+8]
+ authData := data[pos : pos+8]
// (filler) always 0x00 [1 byte]
pos += 8 + 1
@@ -165,10 +191,10 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
// capability flags (lower 2 bytes) [2 bytes]
mc.flags = clientFlag(binary.LittleEndian.Uint16(data[pos : pos+2]))
if mc.flags&clientProtocol41 == 0 {
- return nil, ErrOldProtocol
+ return nil, "", ErrOldProtocol
}
if mc.flags&clientSSL == 0 && mc.cfg.tls != nil {
- return nil, ErrNoTLS
+ return nil, "", ErrNoTLS
}
pos += 2
@@ -192,32 +218,32 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
//
// The official Python library uses the fixed length 12
// which seems to work but technically could have a hidden bug.
- cipher = append(cipher, data[pos:pos+12]...)
+ authData = append(authData, data[pos:pos+12]...)
+ pos += 13
- // TODO: Verify string termination
// EOF if version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2)
// \NUL otherwise
- //
- //if data[len(data)-1] == 0 {
- // return
- //}
- //return ErrMalformPkt
+ if end := bytes.IndexByte(data[pos:], 0x00); end != -1 {
+ plugin = string(data[pos : pos+end])
+ } else {
+ plugin = string(data[pos:])
+ }
// make a memory safe copy of the cipher slice
var b [20]byte
- copy(b[:], cipher)
- return b[:], nil
+ copy(b[:], authData)
+ return b[:], plugin, nil
}
// make a memory safe copy of the cipher slice
var b [8]byte
- copy(b[:], cipher)
- return b[:], nil
+ copy(b[:], authData)
+ return b[:], plugin, nil
}
// Client Authentication Packet
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse
-func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
+func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, addNUL bool, plugin string) error {
// Adjust client flags based on server support
clientFlags := clientProtocol41 |
clientSecureConn |
@@ -241,10 +267,19 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
clientFlags |= clientMultiStatements
}
- // User Password
- scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.Passwd))
+ // encode length of the auth plugin data
+ var authRespLEIBuf [9]byte
+ authRespLEI := appendLengthEncodedInteger(authRespLEIBuf[:0], uint64(len(authResp)))
+ if len(authRespLEI) > 1 {
+ // if the length can not be written in 1 byte, it must be written as a
+ // length encoded integer
+ clientFlags |= clientPluginAuthLenEncClientData
+ }
- pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + 1 + len(scrambleBuff) + 21 + 1
+ pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + len(authRespLEI) + len(authResp) + 21 + 1
+ if addNUL {
+ pktLen++
+ }
// To specify a db name
if n := len(mc.cfg.DBName); n > 0 {
@@ -255,9 +290,9 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
// Calculate packet length and get buffer with that size
data := mc.buf.takeSmallBuffer(pktLen + 4)
if data == nil {
- // can not take the buffer. Something must be wrong with the connection
+ // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer)
- return driver.ErrBadConn
+ return errBadConnNoWrite
}
// ClientFlags [32 bit]
@@ -312,9 +347,13 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
data[pos] = 0x00
pos++
- // ScrambleBuffer [length encoded integer]
- data[pos] = byte(len(scrambleBuff))
- pos += 1 + copy(data[pos+1:], scrambleBuff)
+ // Auth Data [length encoded integer]
+ pos += copy(data[pos:], authRespLEI)
+ pos += copy(data[pos:], authResp)
+ if addNUL {
+ data[pos] = 0x00
+ pos++
+ }
// Databasename [null terminated string]
if len(mc.cfg.DBName) > 0 {
@@ -323,52 +362,32 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
pos++
}
- // Assume native client during response
- pos += copy(data[pos:], "mysql_native_password")
+ pos += copy(data[pos:], plugin)
data[pos] = 0x00
// Send Auth packet
return mc.writePacket(data)
}
-// Client old authentication packet
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
-func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {
- // User password
- scrambleBuff := scrambleOldPassword(cipher, []byte(mc.cfg.Passwd))
-
- // Calculate the packet length and add a tailing 0
- pktLen := len(scrambleBuff) + 1
- data := mc.buf.takeSmallBuffer(4 + pktLen)
+func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte, addNUL bool) error {
+ pktLen := 4 + len(authData)
+ if addNUL {
+ pktLen++
+ }
+ data := mc.buf.takeSmallBuffer(pktLen)
if data == nil {
- // can not take the buffer. Something must be wrong with the connection
+ // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer)
- return driver.ErrBadConn
+ return errBadConnNoWrite
}
- // Add the scrambled password [null terminated string]
- copy(data[4:], scrambleBuff)
- data[4+pktLen-1] = 0x00
-
- return mc.writePacket(data)
-}
-
-// Client clear text authentication packet
-// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
-func (mc *mysqlConn) writeClearAuthPacket() error {
- // Calculate the packet length and add a tailing 0
- pktLen := len(mc.cfg.Passwd) + 1
- data := mc.buf.takeSmallBuffer(4 + pktLen)
- if data == nil {
- // can not take the buffer. Something must be wrong with the connection
- errLog.Print(ErrBusyBuffer)
- return driver.ErrBadConn
+ // Add the auth data [EOF]
+ copy(data[4:], authData)
+ if addNUL {
+ data[pktLen-1] = 0x00
}
- // Add the clear password [null terminated string]
- copy(data[4:], mc.cfg.Passwd)
- data[4+pktLen-1] = 0x00
-
return mc.writePacket(data)
}
@@ -382,9 +401,9 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error {
data := mc.buf.takeSmallBuffer(4 + 1)
if data == nil {
- // can not take the buffer. Something must be wrong with the connection
+ // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer)
- return driver.ErrBadConn
+ return errBadConnNoWrite
}
// Add command byte
@@ -401,9 +420,9 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
pktLen := 1 + len(arg)
data := mc.buf.takeBuffer(pktLen + 4)
if data == nil {
- // can not take the buffer. Something must be wrong with the connection
+ // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer)
- return driver.ErrBadConn
+ return errBadConnNoWrite
}
// Add command byte
@@ -422,9 +441,9 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
data := mc.buf.takeSmallBuffer(4 + 1 + 4)
if data == nil {
- // can not take the buffer. Something must be wrong with the connection
+ // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer)
- return driver.ErrBadConn
+ return errBadConnNoWrite
}
// Add command byte
@@ -444,37 +463,50 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
* Result Packets *
******************************************************************************/
+func (mc *mysqlConn) readAuthResult() ([]byte, string, error) {
+ data, err := mc.readPacket()
+ if err != nil {
+ return nil, "", err
+ }
+
+ // packet indicator
+ switch data[0] {
+
+ case iOK:
+ return nil, "", mc.handleOkPacket(data)
+
+ case iAuthMoreData:
+ return data[1:], "", err
+
+ case iEOF:
+ if len(data) < 1 {
+ // https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
+ return nil, "mysql_old_password", nil
+ }
+ pluginEndIndex := bytes.IndexByte(data, 0x00)
+ if pluginEndIndex < 0 {
+ return nil, "", ErrMalformPkt
+ }
+ plugin := string(data[1:pluginEndIndex])
+ authData := data[pluginEndIndex+1:]
+ return authData, plugin, nil
+
+ default: // Error otherwise
+ return nil, "", mc.handleErrorPacket(data)
+ }
+}
+
// Returns error if Packet is not an 'Result OK'-Packet
func (mc *mysqlConn) readResultOK() error {
data, err := mc.readPacket()
- if err == nil {
- // packet indicator
- switch data[0] {
-
- case iOK:
- return mc.handleOkPacket(data)
-
- case iEOF:
- if len(data) > 1 {
- plugin := string(data[1:bytes.IndexByte(data, 0x00)])
- if plugin == "mysql_old_password" {
- // using old_passwords
- return ErrOldPassword
- } else if plugin == "mysql_clear_password" {
- // using clear text password
- return ErrCleartextPassword
- } else {
- return ErrUnknownPlugin
- }
- } else {
- return ErrOldPassword
- }
-
- default: // Error otherwise
- return mc.handleErrorPacket(data)
- }
+ if err != nil {
+ return err
}
- return err
+
+ if data[0] == iOK {
+ return mc.handleOkPacket(data)
+ }
+ return mc.handleErrorPacket(data)
}
// Result Set Header Packet
@@ -517,6 +549,22 @@ func (mc *mysqlConn) handleErrorPacket(data []byte) error {
// Error Number [16 bit uint]
errno := binary.LittleEndian.Uint16(data[1:3])
+ // 1792: ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION
+ // 1290: ER_OPTION_PREVENTS_STATEMENT (returned by Aurora during failover)
+ if (errno == 1792 || errno == 1290) && mc.cfg.RejectReadOnly {
+ // Oops; we are connected to a read-only connection, and won't be able
+ // to issue any write statements. Since RejectReadOnly is configured,
+ // we throw away this connection hoping this one would have write
+ // permission. This is specifically for a possible race condition
+ // during failover (e.g. on AWS Aurora). See README.md for more.
+ //
+ // We explicitly close the connection before returning
+ // driver.ErrBadConn to ensure that `database/sql` purges this
+ // connection and initiates a new one for next statement next time.
+ mc.Close()
+ return driver.ErrBadConn
+ }
+
pos := 3
// SQL State [optional: # + 5bytes string]
@@ -551,19 +599,12 @@ func (mc *mysqlConn) handleOkPacket(data []byte) error {
// server_status [2 bytes]
mc.status = readStatus(data[1+n+m : 1+n+m+2])
- if err := mc.discardResults(); err != nil {
- return err
- }
-
- // warning count [2 bytes]
- if !mc.strict {
+ if mc.status&statusMoreResultsExists != 0 {
return nil
}
- pos := 1 + n + m + 2
- if binary.LittleEndian.Uint16(data[pos:pos+2]) > 0 {
- return mc.getWarnings()
- }
+ // warning count [2 bytes]
+
return nil
}
@@ -635,14 +676,21 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
if err != nil {
return nil, err
}
+ pos += n
// Filler [uint8]
+ pos++
+
// Charset [charset, collation uint8]
+ columns[i].charSet = data[pos]
+ pos += 2
+
// Length [uint32]
- pos += n + 1 + 2 + 4
+ columns[i].length = binary.LittleEndian.Uint32(data[pos : pos+4])
+ pos += 4
// Field type [uint8]
- columns[i].fieldType = data[pos]
+ columns[i].fieldType = fieldType(data[pos])
pos++
// Flags [uint16]
@@ -665,6 +713,10 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
func (rows *textRows) readRow(dest []driver.Value) error {
mc := rows.mc
+ if rows.rs.done {
+ return io.EOF
+ }
+
data, err := mc.readPacket()
if err != nil {
return err
@@ -674,10 +726,10 @@ func (rows *textRows) readRow(dest []driver.Value) error {
if data[0] == iEOF && len(data) == 5 {
// server_status [2 bytes]
rows.mc.status = readStatus(data[3:])
- if err := rows.mc.discardResults(); err != nil {
- return err
+ rows.rs.done = true
+ if !rows.HasNextResultSet() {
+ rows.mc = nil
}
- rows.mc = nil
return io.EOF
}
if data[0] == iERR {
@@ -699,7 +751,7 @@ func (rows *textRows) readRow(dest []driver.Value) error {
if !mc.parseTime {
continue
} else {
- switch rows.columns[i].fieldType {
+ switch rows.rs.columns[i].fieldType {
case fieldTypeTimestamp, fieldTypeDateTime,
fieldTypeDate, fieldTypeNewDate:
dest[i], err = parseDateTime(
@@ -729,16 +781,19 @@ func (rows *textRows) readRow(dest []driver.Value) error {
func (mc *mysqlConn) readUntilEOF() error {
for {
data, err := mc.readPacket()
-
- // No Err and no EOF Packet
- if err == nil && data[0] != iEOF {
- continue
- }
- if err == nil && data[0] == iEOF && len(data) == 5 {
- mc.status = readStatus(data[3:])
+ if err != nil {
+ return err
}
- return err // Err or EOF
+ switch data[0] {
+ case iERR:
+ return mc.handleErrorPacket(data)
+ case iEOF:
+ if len(data) == 5 {
+ mc.status = readStatus(data[3:])
+ }
+ return nil
+ }
}
}
@@ -768,14 +823,7 @@ func (stmt *mysqlStmt) readPrepareResultPacket() (uint16, error) {
// Reserved [8 bit]
// Warning count [16 bit uint]
- if !stmt.mc.strict {
- return columnCount, nil
- }
- // Check for warnings count > 0, only available in MySQL > 4.1
- if len(data) >= 12 && binary.LittleEndian.Uint16(data[10:12]) > 0 {
- return columnCount, stmt.mc.getWarnings()
- }
return columnCount, nil
}
return 0, err
@@ -783,7 +831,7 @@ func (stmt *mysqlStmt) readPrepareResultPacket() (uint16, error) {
// http://dev.mysql.com/doc/internals/en/com-stmt-send-long-data.html
func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error {
- maxLen := stmt.mc.maxPacketAllowed - 1
+ maxLen := stmt.mc.maxAllowedPacket - 1
pktLen := maxLen
// After the header (bytes 0-3) follows before the data:
@@ -792,7 +840,7 @@ func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error {
// 2 bytes paramID
const dataOffset = 1 + 4 + 2
- // Can not use the write buffer since
+ // Cannot use the write buffer since
// a) the buffer is too small
// b) it is in use
data := make([]byte, 4+1+4+2+len(arg))
@@ -847,6 +895,12 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
const minPktLen = 4 + 1 + 4 + 1 + 4
mc := stmt.mc
+ // Determine threshould dynamically to avoid packet size shortage.
+ longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1)
+ if longDataSize < 64 {
+ longDataSize = 64
+ }
+
// Reset packet-sequence
mc.sequence = 0
@@ -858,9 +912,9 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
data = mc.buf.takeCompleteBuffer()
}
if data == nil {
- // can not take the buffer. Something must be wrong with the connection
+ // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer)
- return driver.ErrBadConn
+ return errBadConnNoWrite
}
// command [1 byte]
@@ -919,7 +973,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
// build NULL-bitmap
if arg == nil {
nullMask[i/8] |= 1 << (uint(i) & 7)
- paramTypes[i+i] = fieldTypeNULL
+ paramTypes[i+i] = byte(fieldTypeNULL)
paramTypes[i+i+1] = 0x00
continue
}
@@ -927,7 +981,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
// cache types and values
switch v := arg.(type) {
case int64:
- paramTypes[i+i] = fieldTypeLongLong
+ paramTypes[i+i] = byte(fieldTypeLongLong)
paramTypes[i+i+1] = 0x00
if cap(paramValues)-len(paramValues)-8 >= 0 {
@@ -943,7 +997,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
}
case float64:
- paramTypes[i+i] = fieldTypeDouble
+ paramTypes[i+i] = byte(fieldTypeDouble)
paramTypes[i+i+1] = 0x00
if cap(paramValues)-len(paramValues)-8 >= 0 {
@@ -959,7 +1013,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
}
case bool:
- paramTypes[i+i] = fieldTypeTiny
+ paramTypes[i+i] = byte(fieldTypeTiny)
paramTypes[i+i+1] = 0x00
if v {
@@ -971,10 +1025,10 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
case []byte:
// Common case (non-nil value) first
if v != nil {
- paramTypes[i+i] = fieldTypeString
+ paramTypes[i+i] = byte(fieldTypeString)
paramTypes[i+i+1] = 0x00
- if len(v) < mc.maxPacketAllowed-pos-len(paramValues)-(len(args)-(i+1))*64 {
+ if len(v) < longDataSize {
paramValues = appendLengthEncodedInteger(paramValues,
uint64(len(v)),
)
@@ -989,14 +1043,14 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
// Handle []byte(nil) as a NULL value
nullMask[i/8] |= 1 << (uint(i) & 7)
- paramTypes[i+i] = fieldTypeNULL
+ paramTypes[i+i] = byte(fieldTypeNULL)
paramTypes[i+i+1] = 0x00
case string:
- paramTypes[i+i] = fieldTypeString
+ paramTypes[i+i] = byte(fieldTypeString)
paramTypes[i+i+1] = 0x00
- if len(v) < mc.maxPacketAllowed-pos-len(paramValues)-(len(args)-(i+1))*64 {
+ if len(v) < longDataSize {
paramValues = appendLengthEncodedInteger(paramValues,
uint64(len(v)),
)
@@ -1008,23 +1062,25 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
}
case time.Time:
- paramTypes[i+i] = fieldTypeString
+ paramTypes[i+i] = byte(fieldTypeString)
paramTypes[i+i+1] = 0x00
- var val []byte
+ var a [64]byte
+ var b = a[:0]
+
if v.IsZero() {
- val = []byte("0000-00-00")
+ b = append(b, "0000-00-00"...)
} else {
- val = []byte(v.In(mc.cfg.Loc).Format(timeFormat))
+ b = v.In(mc.cfg.Loc).AppendFormat(b, timeFormat)
}
paramValues = appendLengthEncodedInteger(paramValues,
- uint64(len(val)),
+ uint64(len(b)),
)
- paramValues = append(paramValues, val...)
+ paramValues = append(paramValues, b...)
default:
- return fmt.Errorf("can not convert type: %T", arg)
+ return fmt.Errorf("cannot convert type: %T", arg)
}
}
@@ -1057,8 +1113,6 @@ func (mc *mysqlConn) discardResults() error {
if err := mc.readUntilEOF(); err != nil {
return err
}
- } else {
- mc.status &^= statusMoreResultsExists
}
}
return nil
@@ -1076,16 +1130,17 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
// EOF Packet
if data[0] == iEOF && len(data) == 5 {
rows.mc.status = readStatus(data[3:])
- if err := rows.mc.discardResults(); err != nil {
- return err
+ rows.rs.done = true
+ if !rows.HasNextResultSet() {
+ rows.mc = nil
}
- rows.mc = nil
return io.EOF
}
+ mc := rows.mc
rows.mc = nil
// Error otherwise
- return rows.mc.handleErrorPacket(data)
+ return mc.handleErrorPacket(data)
}
// NULL-bitmap, [(column-count + 7 + 2) / 8 bytes]
@@ -1101,14 +1156,14 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
}
// Convert to byte-coded string
- switch rows.columns[i].fieldType {
+ switch rows.rs.columns[i].fieldType {
case fieldTypeNULL:
dest[i] = nil
continue
// Numeric Types
case fieldTypeTiny:
- if rows.columns[i].flags&flagUnsigned != 0 {
+ if rows.rs.columns[i].flags&flagUnsigned != 0 {
dest[i] = int64(data[pos])
} else {
dest[i] = int64(int8(data[pos]))
@@ -1117,7 +1172,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
continue
case fieldTypeShort, fieldTypeYear:
- if rows.columns[i].flags&flagUnsigned != 0 {
+ if rows.rs.columns[i].flags&flagUnsigned != 0 {
dest[i] = int64(binary.LittleEndian.Uint16(data[pos : pos+2]))
} else {
dest[i] = int64(int16(binary.LittleEndian.Uint16(data[pos : pos+2])))
@@ -1126,7 +1181,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
continue
case fieldTypeInt24, fieldTypeLong:
- if rows.columns[i].flags&flagUnsigned != 0 {
+ if rows.rs.columns[i].flags&flagUnsigned != 0 {
dest[i] = int64(binary.LittleEndian.Uint32(data[pos : pos+4]))
} else {
dest[i] = int64(int32(binary.LittleEndian.Uint32(data[pos : pos+4])))
@@ -1135,7 +1190,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
continue
case fieldTypeLongLong:
- if rows.columns[i].flags&flagUnsigned != 0 {
+ if rows.rs.columns[i].flags&flagUnsigned != 0 {
val := binary.LittleEndian.Uint64(data[pos : pos+8])
if val > math.MaxInt64 {
dest[i] = uint64ToString(val)
@@ -1149,7 +1204,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
continue
case fieldTypeFloat:
- dest[i] = float32(math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4])))
+ dest[i] = math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4]))
pos += 4
continue
@@ -1189,10 +1244,10 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
case isNull:
dest[i] = nil
continue
- case rows.columns[i].fieldType == fieldTypeTime:
+ case rows.rs.columns[i].fieldType == fieldTypeTime:
// database/sql does not support an equivalent to TIME, return a string
var dstlen uint8
- switch decimals := rows.columns[i].decimals; decimals {
+ switch decimals := rows.rs.columns[i].decimals; decimals {
case 0x00, 0x1f:
dstlen = 8
case 1, 2, 3, 4, 5, 6:
@@ -1200,18 +1255,18 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
default:
return fmt.Errorf(
"protocol error, illegal decimals value %d",
- rows.columns[i].decimals,
+ rows.rs.columns[i].decimals,
)
}
- dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true)
+ dest[i], err = formatBinaryTime(data[pos:pos+int(num)], dstlen)
case rows.mc.parseTime:
dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc)
default:
var dstlen uint8
- if rows.columns[i].fieldType == fieldTypeDate {
+ if rows.rs.columns[i].fieldType == fieldTypeDate {
dstlen = 10
} else {
- switch decimals := rows.columns[i].decimals; decimals {
+ switch decimals := rows.rs.columns[i].decimals; decimals {
case 0x00, 0x1f:
dstlen = 19
case 1, 2, 3, 4, 5, 6:
@@ -1219,11 +1274,11 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
default:
return fmt.Errorf(
"protocol error, illegal decimals value %d",
- rows.columns[i].decimals,
+ rows.rs.columns[i].decimals,
)
}
}
- dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, false)
+ dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen)
}
if err == nil {
@@ -1235,7 +1290,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
// Please report if this happens!
default:
- return fmt.Errorf("unknown field type %d", rows.columns[i].fieldType)
+ return fmt.Errorf("unknown field type %d", rows.rs.columns[i].fieldType)
}
}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/result.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/result.go
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/result.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/result.go
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/rows.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/rows.go
new file mode 100644
index 0000000..d3b1e28
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/rows.go
@@ -0,0 +1,216 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+import (
+ "database/sql/driver"
+ "io"
+ "math"
+ "reflect"
+)
+
+type resultSet struct {
+ columns []mysqlField
+ columnNames []string
+ done bool
+}
+
+type mysqlRows struct {
+ mc *mysqlConn
+ rs resultSet
+ finish func()
+}
+
+type binaryRows struct {
+ mysqlRows
+}
+
+type textRows struct {
+ mysqlRows
+}
+
+func (rows *mysqlRows) Columns() []string {
+ if rows.rs.columnNames != nil {
+ return rows.rs.columnNames
+ }
+
+ columns := make([]string, len(rows.rs.columns))
+ if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias {
+ for i := range columns {
+ if tableName := rows.rs.columns[i].tableName; len(tableName) > 0 {
+ columns[i] = tableName + "." + rows.rs.columns[i].name
+ } else {
+ columns[i] = rows.rs.columns[i].name
+ }
+ }
+ } else {
+ for i := range columns {
+ columns[i] = rows.rs.columns[i].name
+ }
+ }
+
+ rows.rs.columnNames = columns
+ return columns
+}
+
+func (rows *mysqlRows) ColumnTypeDatabaseTypeName(i int) string {
+ return rows.rs.columns[i].typeDatabaseName()
+}
+
+// func (rows *mysqlRows) ColumnTypeLength(i int) (length int64, ok bool) {
+// return int64(rows.rs.columns[i].length), true
+// }
+
+func (rows *mysqlRows) ColumnTypeNullable(i int) (nullable, ok bool) {
+ return rows.rs.columns[i].flags&flagNotNULL == 0, true
+}
+
+func (rows *mysqlRows) ColumnTypePrecisionScale(i int) (int64, int64, bool) {
+ column := rows.rs.columns[i]
+ decimals := int64(column.decimals)
+
+ switch column.fieldType {
+ case fieldTypeDecimal, fieldTypeNewDecimal:
+ if decimals > 0 {
+ return int64(column.length) - 2, decimals, true
+ }
+ return int64(column.length) - 1, decimals, true
+ case fieldTypeTimestamp, fieldTypeDateTime, fieldTypeTime:
+ return decimals, decimals, true
+ case fieldTypeFloat, fieldTypeDouble:
+ if decimals == 0x1f {
+ return math.MaxInt64, math.MaxInt64, true
+ }
+ return math.MaxInt64, decimals, true
+ }
+
+ return 0, 0, false
+}
+
+func (rows *mysqlRows) ColumnTypeScanType(i int) reflect.Type {
+ return rows.rs.columns[i].scanType()
+}
+
+func (rows *mysqlRows) Close() (err error) {
+ if f := rows.finish; f != nil {
+ f()
+ rows.finish = nil
+ }
+
+ mc := rows.mc
+ if mc == nil {
+ return nil
+ }
+ if err := mc.error(); err != nil {
+ return err
+ }
+
+ // Remove unread packets from stream
+ if !rows.rs.done {
+ err = mc.readUntilEOF()
+ }
+ if err == nil {
+ if err = mc.discardResults(); err != nil {
+ return err
+ }
+ }
+
+ rows.mc = nil
+ return err
+}
+
+func (rows *mysqlRows) HasNextResultSet() (b bool) {
+ if rows.mc == nil {
+ return false
+ }
+ return rows.mc.status&statusMoreResultsExists != 0
+}
+
+func (rows *mysqlRows) nextResultSet() (int, error) {
+ if rows.mc == nil {
+ return 0, io.EOF
+ }
+ if err := rows.mc.error(); err != nil {
+ return 0, err
+ }
+
+ // Remove unread packets from stream
+ if !rows.rs.done {
+ if err := rows.mc.readUntilEOF(); err != nil {
+ return 0, err
+ }
+ rows.rs.done = true
+ }
+
+ if !rows.HasNextResultSet() {
+ rows.mc = nil
+ return 0, io.EOF
+ }
+ rows.rs = resultSet{}
+ return rows.mc.readResultSetHeaderPacket()
+}
+
+func (rows *mysqlRows) nextNotEmptyResultSet() (int, error) {
+ for {
+ resLen, err := rows.nextResultSet()
+ if err != nil {
+ return 0, err
+ }
+
+ if resLen > 0 {
+ return resLen, nil
+ }
+
+ rows.rs.done = true
+ }
+}
+
+func (rows *binaryRows) NextResultSet() error {
+ resLen, err := rows.nextNotEmptyResultSet()
+ if err != nil {
+ return err
+ }
+
+ rows.rs.columns, err = rows.mc.readColumns(resLen)
+ return err
+}
+
+func (rows *binaryRows) Next(dest []driver.Value) error {
+ if mc := rows.mc; mc != nil {
+ if err := mc.error(); err != nil {
+ return err
+ }
+
+ // Fetch next row from stream
+ return rows.readRow(dest)
+ }
+ return io.EOF
+}
+
+func (rows *textRows) NextResultSet() (err error) {
+ resLen, err := rows.nextNotEmptyResultSet()
+ if err != nil {
+ return err
+ }
+
+ rows.rs.columns, err = rows.mc.readColumns(resLen)
+ return err
+}
+
+func (rows *textRows) Next(dest []driver.Value) error {
+ if mc := rows.mc; mc != nil {
+ if err := mc.error(); err != nil {
+ return err
+ }
+
+ // Fetch next row from stream
+ return rows.readRow(dest)
+ }
+ return io.EOF
+}
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/statement.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/statement.go
new file mode 100644
index 0000000..ce7fe4c
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/statement.go
@@ -0,0 +1,211 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+import (
+ "database/sql/driver"
+ "fmt"
+ "io"
+ "reflect"
+ "strconv"
+)
+
+type mysqlStmt struct {
+ mc *mysqlConn
+ id uint32
+ paramCount int
+}
+
+func (stmt *mysqlStmt) Close() error {
+ if stmt.mc == nil || stmt.mc.closed.IsSet() {
+ // driver.Stmt.Close can be called more than once, thus this function
+ // has to be idempotent.
+ // See also Issue #450 and golang/go#16019.
+ //errLog.Print(ErrInvalidConn)
+ return driver.ErrBadConn
+ }
+
+ err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id)
+ stmt.mc = nil
+ return err
+}
+
+func (stmt *mysqlStmt) NumInput() int {
+ return stmt.paramCount
+}
+
+func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
+ return converter{}
+}
+
+func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
+ if stmt.mc.closed.IsSet() {
+ errLog.Print(ErrInvalidConn)
+ return nil, driver.ErrBadConn
+ }
+ // Send command
+ err := stmt.writeExecutePacket(args)
+ if err != nil {
+ return nil, stmt.mc.markBadConn(err)
+ }
+
+ mc := stmt.mc
+
+ mc.affectedRows = 0
+ mc.insertId = 0
+
+ // Read Result
+ resLen, err := mc.readResultSetHeaderPacket()
+ if err != nil {
+ return nil, err
+ }
+
+ if resLen > 0 {
+ // Columns
+ if err = mc.readUntilEOF(); err != nil {
+ return nil, err
+ }
+
+ // Rows
+ if err := mc.readUntilEOF(); err != nil {
+ return nil, err
+ }
+ }
+
+ if err := mc.discardResults(); err != nil {
+ return nil, err
+ }
+
+ return &mysqlResult{
+ affectedRows: int64(mc.affectedRows),
+ insertId: int64(mc.insertId),
+ }, nil
+}
+
+func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
+ return stmt.query(args)
+}
+
+func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
+ if stmt.mc.closed.IsSet() {
+ errLog.Print(ErrInvalidConn)
+ return nil, driver.ErrBadConn
+ }
+ // Send command
+ err := stmt.writeExecutePacket(args)
+ if err != nil {
+ return nil, stmt.mc.markBadConn(err)
+ }
+
+ mc := stmt.mc
+
+ // Read Result
+ resLen, err := mc.readResultSetHeaderPacket()
+ if err != nil {
+ return nil, err
+ }
+
+ rows := new(binaryRows)
+
+ if resLen > 0 {
+ rows.mc = mc
+ rows.rs.columns, err = mc.readColumns(resLen)
+ } else {
+ rows.rs.done = true
+
+ switch err := rows.NextResultSet(); err {
+ case nil, io.EOF:
+ return rows, nil
+ default:
+ return nil, err
+ }
+ }
+
+ return rows, err
+}
+
+type converter struct{}
+
+// ConvertValue mirrors the reference/default converter in database/sql/driver
+// with _one_ exception. We support uint64 with their high bit and the default
+// implementation does not. This function should be kept in sync with
+// database/sql/driver defaultConverter.ConvertValue() except for that
+// deliberate difference.
+func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
+ if driver.IsValue(v) {
+ return v, nil
+ }
+
+ if vr, ok := v.(driver.Valuer); ok {
+ sv, err := callValuerValue(vr)
+ if err != nil {
+ return nil, err
+ }
+ if !driver.IsValue(sv) {
+ return nil, fmt.Errorf("non-Value type %T returned from Value", sv)
+ }
+ return sv, nil
+ }
+
+ rv := reflect.ValueOf(v)
+ switch rv.Kind() {
+ case reflect.Ptr:
+ // indirect pointers
+ if rv.IsNil() {
+ return nil, nil
+ } else {
+ return c.ConvertValue(rv.Elem().Interface())
+ }
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return rv.Int(), nil
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
+ return int64(rv.Uint()), nil
+ case reflect.Uint64:
+ u64 := rv.Uint()
+ if u64 >= 1<<63 {
+ return strconv.FormatUint(u64, 10), nil
+ }
+ return int64(u64), nil
+ case reflect.Float32, reflect.Float64:
+ return rv.Float(), nil
+ case reflect.Bool:
+ return rv.Bool(), nil
+ case reflect.Slice:
+ ek := rv.Type().Elem().Kind()
+ if ek == reflect.Uint8 {
+ return rv.Bytes(), nil
+ }
+ return nil, fmt.Errorf("unsupported type %T, a slice of %s", v, ek)
+ case reflect.String:
+ return rv.String(), nil
+ }
+ return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
+}
+
+var valuerReflectType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
+
+// callValuerValue returns vr.Value(), with one exception:
+// If vr.Value is an auto-generated method on a pointer type and the
+// pointer is nil, it would panic at runtime in the panicwrap
+// method. Treat it like nil instead.
+//
+// This is so people can implement driver.Value on value types and
+// still use nil pointers to those types to mean nil/NULL, just like
+// string/*string.
+//
+// This is an exact copy of the same-named unexported function from the
+// database/sql package.
+func callValuerValue(vr driver.Valuer) (v driver.Value, err error) {
+ if rv := reflect.ValueOf(vr); rv.Kind() == reflect.Ptr &&
+ rv.IsNil() &&
+ rv.Type().Elem().Implements(valuerReflectType) {
+ return nil, nil
+ }
+ return vr.Value()
+}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/transaction.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/transaction.go
similarity index 88%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/transaction.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/transaction.go
index 33c749b..417d727 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/transaction.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/transaction.go
@@ -13,7 +13,7 @@ type mysqlTx struct {
}
func (tx *mysqlTx) Commit() (err error) {
- if tx.mc == nil || tx.mc.netConn == nil {
+ if tx.mc == nil || tx.mc.closed.IsSet() {
return ErrInvalidConn
}
err = tx.mc.exec("COMMIT")
@@ -22,7 +22,7 @@ func (tx *mysqlTx) Commit() (err error) {
}
func (tx *mysqlTx) Rollback() (err error) {
- if tx.mc == nil || tx.mc.netConn == nil {
+ if tx.mc == nil || tx.mc.closed.IsSet() {
return ErrInvalidConn
}
err = tx.mc.exec("ROLLBACK")
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/utils.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/utils.go
similarity index 70%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/utils.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/utils.go
index d523b7f..cb3650b 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/go-sql-driver/mysql/utils.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/go-sql-driver/mysql/utils.go
@@ -9,23 +9,32 @@
package mysql
import (
- "crypto/sha1"
"crypto/tls"
+ "database/sql"
"database/sql/driver"
"encoding/binary"
+ "errors"
"fmt"
"io"
+ "strconv"
"strings"
+ "sync"
+ "sync/atomic"
"time"
)
+// Registry for custom tls.Configs
var (
- tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
+ tlsConfigLock sync.RWMutex
+ tlsConfigRegistry map[string]*tls.Config
)
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
// Use the key as a value in the DSN where tls=value.
//
+// Note: The provided tls.Config is exclusively owned by the driver after
+// registering it.
+//
// rootCertPool := x509.NewCertPool()
// pem, err := ioutil.ReadFile("/path/ca-cert.pem")
// if err != nil {
@@ -51,19 +60,32 @@ func RegisterTLSConfig(key string, config *tls.Config) error {
return fmt.Errorf("key '%s' is reserved", key)
}
- if tlsConfigRegister == nil {
- tlsConfigRegister = make(map[string]*tls.Config)
+ tlsConfigLock.Lock()
+ if tlsConfigRegistry == nil {
+ tlsConfigRegistry = make(map[string]*tls.Config)
}
- tlsConfigRegister[key] = config
+ tlsConfigRegistry[key] = config
+ tlsConfigLock.Unlock()
return nil
}
// DeregisterTLSConfig removes the tls.Config associated with key.
func DeregisterTLSConfig(key string) {
- if tlsConfigRegister != nil {
- delete(tlsConfigRegister, key)
+ tlsConfigLock.Lock()
+ if tlsConfigRegistry != nil {
+ delete(tlsConfigRegistry, key)
}
+ tlsConfigLock.Unlock()
+}
+
+func getTLSConfigClone(key string) (config *tls.Config) {
+ tlsConfigLock.RLock()
+ if v, ok := tlsConfigRegistry[key]; ok {
+ config = v.Clone()
+ }
+ tlsConfigLock.RUnlock()
+ return
}
// Returns the bool value of the input.
@@ -80,119 +102,6 @@ func readBool(input string) (value bool, valid bool) {
return
}
-/******************************************************************************
-* Authentication *
-******************************************************************************/
-
-// Encrypt password using 4.1+ method
-func scramblePassword(scramble, password []byte) []byte {
- if len(password) == 0 {
- return nil
- }
-
- // stage1Hash = SHA1(password)
- crypt := sha1.New()
- crypt.Write(password)
- stage1 := crypt.Sum(nil)
-
- // scrambleHash = SHA1(scramble + SHA1(stage1Hash))
- // inner Hash
- crypt.Reset()
- crypt.Write(stage1)
- hash := crypt.Sum(nil)
-
- // outer Hash
- crypt.Reset()
- crypt.Write(scramble)
- crypt.Write(hash)
- scramble = crypt.Sum(nil)
-
- // token = scrambleHash XOR stage1Hash
- for i := range scramble {
- scramble[i] ^= stage1[i]
- }
- return scramble
-}
-
-// Encrypt password using pre 4.1 (old password) method
-// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
-type myRnd struct {
- seed1, seed2 uint32
-}
-
-const myRndMaxVal = 0x3FFFFFFF
-
-// Pseudo random number generator
-func newMyRnd(seed1, seed2 uint32) *myRnd {
- return &myRnd{
- seed1: seed1 % myRndMaxVal,
- seed2: seed2 % myRndMaxVal,
- }
-}
-
-// Tested to be equivalent to MariaDB's floating point variant
-// http://play.golang.org/p/QHvhd4qved
-// http://play.golang.org/p/RG0q4ElWDx
-func (r *myRnd) NextByte() byte {
- r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
- r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
-
- return byte(uint64(r.seed1) * 31 / myRndMaxVal)
-}
-
-// Generate binary hash from byte string using insecure pre 4.1 method
-func pwHash(password []byte) (result [2]uint32) {
- var add uint32 = 7
- var tmp uint32
-
- result[0] = 1345345333
- result[1] = 0x12345671
-
- for _, c := range password {
- // skip spaces and tabs in password
- if c == ' ' || c == '\t' {
- continue
- }
-
- tmp = uint32(c)
- result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
- result[1] += (result[1] << 8) ^ result[0]
- add += tmp
- }
-
- // Remove sign bit (1<<31)-1)
- result[0] &= 0x7FFFFFFF
- result[1] &= 0x7FFFFFFF
-
- return
-}
-
-// Encrypt password using insecure pre 4.1 method
-func scrambleOldPassword(scramble, password []byte) []byte {
- if len(password) == 0 {
- return nil
- }
-
- scramble = scramble[:8]
-
- hashPw := pwHash(password)
- hashSc := pwHash(scramble)
-
- r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
-
- var out [8]byte
- for i := range out {
- out[i] = r.NextByte() + 64
- }
-
- mask := r.NextByte()
- for i := range out {
- out[i] ^= mask
- }
-
- return out[:]
-}
-
/******************************************************************************
* Time related utils *
******************************************************************************/
@@ -321,87 +230,104 @@ var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
-func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {
+func appendMicrosecs(dst, src []byte, decimals int) []byte {
+ if decimals <= 0 {
+ return dst
+ }
+ if len(src) == 0 {
+ return append(dst, ".000000"[:decimals+1]...)
+ }
+
+ microsecs := binary.LittleEndian.Uint32(src[:4])
+ p1 := byte(microsecs / 10000)
+ microsecs -= 10000 * uint32(p1)
+ p2 := byte(microsecs / 100)
+ microsecs -= 100 * uint32(p2)
+ p3 := byte(microsecs)
+
+ switch decimals {
+ default:
+ return append(dst, '.',
+ digits10[p1], digits01[p1],
+ digits10[p2], digits01[p2],
+ digits10[p3], digits01[p3],
+ )
+ case 1:
+ return append(dst, '.',
+ digits10[p1],
+ )
+ case 2:
+ return append(dst, '.',
+ digits10[p1], digits01[p1],
+ )
+ case 3:
+ return append(dst, '.',
+ digits10[p1], digits01[p1],
+ digits10[p2],
+ )
+ case 4:
+ return append(dst, '.',
+ digits10[p1], digits01[p1],
+ digits10[p2], digits01[p2],
+ )
+ case 5:
+ return append(dst, '.',
+ digits10[p1], digits01[p1],
+ digits10[p2], digits01[p2],
+ digits10[p3],
+ )
+ }
+}
+
+func formatBinaryDateTime(src []byte, length uint8) (driver.Value, error) {
// length expects the deterministic length of the zero value,
// negative time and 100+ hours are automatically added if needed
if len(src) == 0 {
- if justTime {
- return zeroDateTime[11 : 11+length], nil
- }
return zeroDateTime[:length], nil
}
- var dst []byte // return value
- var pt, p1, p2, p3 byte // current digit pair
- var zOffs byte // offset of value in zeroDateTime
- if justTime {
- switch length {
- case
- 8, // time (can be up to 10 when negative and 100+ hours)
- 10, 11, 12, 13, 14, 15: // time with fractional seconds
- default:
- return nil, fmt.Errorf("illegal TIME length %d", length)
+ var dst []byte // return value
+ var p1, p2, p3 byte // current digit pair
+
+ switch length {
+ case 10, 19, 21, 22, 23, 24, 25, 26:
+ default:
+ t := "DATE"
+ if length > 10 {
+ t += "TIME"
}
- switch len(src) {
- case 8, 12:
- default:
- return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
- }
- // +2 to enable negative time and 100+ hours
- dst = make([]byte, 0, length+2)
- if src[0] == 1 {
- dst = append(dst, '-')
- }
- if src[1] != 0 {
- hour := uint16(src[1])*24 + uint16(src[5])
- pt = byte(hour / 100)
- p1 = byte(hour - 100*uint16(pt))
- dst = append(dst, digits01[pt])
- } else {
- p1 = src[5]
- }
- zOffs = 11
- src = src[6:]
- } else {
- switch length {
- case 10, 19, 21, 22, 23, 24, 25, 26:
- default:
- t := "DATE"
- if length > 10 {
- t += "TIME"
- }
- return nil, fmt.Errorf("illegal %s length %d", t, length)
- }
- switch len(src) {
- case 4, 7, 11:
- default:
- t := "DATE"
- if length > 10 {
- t += "TIME"
- }
- return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
- }
- dst = make([]byte, 0, length)
- // start with the date
- year := binary.LittleEndian.Uint16(src[:2])
- pt = byte(year / 100)
- p1 = byte(year - 100*uint16(pt))
- p2, p3 = src[2], src[3]
- dst = append(dst,
- digits10[pt], digits01[pt],
- digits10[p1], digits01[p1], '-',
- digits10[p2], digits01[p2], '-',
- digits10[p3], digits01[p3],
- )
- if length == 10 {
- return dst, nil
- }
- if len(src) == 4 {
- return append(dst, zeroDateTime[10:length]...), nil
- }
- dst = append(dst, ' ')
- p1 = src[4] // hour
- src = src[5:]
+ return nil, fmt.Errorf("illegal %s length %d", t, length)
}
+ switch len(src) {
+ case 4, 7, 11:
+ default:
+ t := "DATE"
+ if length > 10 {
+ t += "TIME"
+ }
+ return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
+ }
+ dst = make([]byte, 0, length)
+ // start with the date
+ year := binary.LittleEndian.Uint16(src[:2])
+ pt := year / 100
+ p1 = byte(year - 100*uint16(pt))
+ p2, p3 = src[2], src[3]
+ dst = append(dst,
+ digits10[pt], digits01[pt],
+ digits10[p1], digits01[p1], '-',
+ digits10[p2], digits01[p2], '-',
+ digits10[p3], digits01[p3],
+ )
+ if length == 10 {
+ return dst, nil
+ }
+ if len(src) == 4 {
+ return append(dst, zeroDateTime[10:length]...), nil
+ }
+ dst = append(dst, ' ')
+ p1 = src[4] // hour
+ src = src[5:]
+
// p1 is 2-digit hour, src is after hour
p2, p3 = src[0], src[1]
dst = append(dst,
@@ -409,51 +335,49 @@ func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value
digits10[p2], digits01[p2], ':',
digits10[p3], digits01[p3],
)
- if length <= byte(len(dst)) {
- return dst, nil
- }
- src = src[2:]
+ return appendMicrosecs(dst, src[2:], int(length)-20), nil
+}
+
+func formatBinaryTime(src []byte, length uint8) (driver.Value, error) {
+ // length expects the deterministic length of the zero value,
+ // negative time and 100+ hours are automatically added if needed
if len(src) == 0 {
- return append(dst, zeroDateTime[19:zOffs+length]...), nil
+ return zeroDateTime[11 : 11+length], nil
}
- microsecs := binary.LittleEndian.Uint32(src[:4])
- p1 = byte(microsecs / 10000)
- microsecs -= 10000 * uint32(p1)
- p2 = byte(microsecs / 100)
- microsecs -= 100 * uint32(p2)
- p3 = byte(microsecs)
- switch decimals := zOffs + length - 20; decimals {
+ var dst []byte // return value
+
+ switch length {
+ case
+ 8, // time (can be up to 10 when negative and 100+ hours)
+ 10, 11, 12, 13, 14, 15: // time with fractional seconds
default:
- return append(dst, '.',
- digits10[p1], digits01[p1],
- digits10[p2], digits01[p2],
- digits10[p3], digits01[p3],
- ), nil
- case 1:
- return append(dst, '.',
- digits10[p1],
- ), nil
- case 2:
- return append(dst, '.',
- digits10[p1], digits01[p1],
- ), nil
- case 3:
- return append(dst, '.',
- digits10[p1], digits01[p1],
- digits10[p2],
- ), nil
- case 4:
- return append(dst, '.',
- digits10[p1], digits01[p1],
- digits10[p2], digits01[p2],
- ), nil
- case 5:
- return append(dst, '.',
- digits10[p1], digits01[p1],
- digits10[p2], digits01[p2],
- digits10[p3],
- ), nil
+ return nil, fmt.Errorf("illegal TIME length %d", length)
}
+ switch len(src) {
+ case 8, 12:
+ default:
+ return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
+ }
+ // +2 to enable negative time and 100+ hours
+ dst = make([]byte, 0, length+2)
+ if src[0] == 1 {
+ dst = append(dst, '-')
+ }
+ days := binary.LittleEndian.Uint32(src[1:5])
+ hours := int64(days)*24 + int64(src[5])
+
+ if hours >= 100 {
+ dst = strconv.AppendInt(dst, hours, 10)
+ } else {
+ dst = append(dst, digits10[hours], digits01[hours])
+ }
+
+ min, sec := src[6], src[7]
+ dst = append(dst, ':',
+ digits10[min], digits01[min], ':',
+ digits10[sec], digits01[sec],
+ )
+ return appendMicrosecs(dst, src[8:], int(length)-9), nil
}
/******************************************************************************
@@ -519,7 +443,7 @@ func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
// Check data length
if len(b) >= n {
- return b[n-int(num) : n], false, n, nil
+ return b[n-int(num) : n : n], false, n, nil
}
return nil, false, n, io.EOF
}
@@ -548,8 +472,8 @@ func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
if len(b) == 0 {
return 0, true, 1
}
- switch b[0] {
+ switch b[0] {
// 251: NULL
case 0xfb:
return 0, true, 1
@@ -738,3 +662,94 @@ func escapeStringQuotes(buf []byte, v string) []byte {
return buf[:pos]
}
+
+/******************************************************************************
+* Sync utils *
+******************************************************************************/
+
+// noCopy may be embedded into structs which must not be copied
+// after the first use.
+//
+// See https://github.com/golang/go/issues/8005#issuecomment-190753527
+// for details.
+type noCopy struct{}
+
+// Lock is a no-op used by -copylocks checker from `go vet`.
+func (*noCopy) Lock() {}
+
+// atomicBool is a wrapper around uint32 for usage as a boolean value with
+// atomic access.
+type atomicBool struct {
+ _noCopy noCopy
+ value uint32
+}
+
+// IsSet returns wether the current boolean value is true
+func (ab *atomicBool) IsSet() bool {
+ return atomic.LoadUint32(&ab.value) > 0
+}
+
+// Set sets the value of the bool regardless of the previous value
+func (ab *atomicBool) Set(value bool) {
+ if value {
+ atomic.StoreUint32(&ab.value, 1)
+ } else {
+ atomic.StoreUint32(&ab.value, 0)
+ }
+}
+
+// TrySet sets the value of the bool and returns wether the value changed
+func (ab *atomicBool) TrySet(value bool) bool {
+ if value {
+ return atomic.SwapUint32(&ab.value, 1) == 0
+ }
+ return atomic.SwapUint32(&ab.value, 0) > 0
+}
+
+// atomicError is a wrapper for atomically accessed error values
+type atomicError struct {
+ _noCopy noCopy
+ value atomic.Value
+}
+
+// Set sets the error value regardless of the previous value.
+// The value must not be nil
+func (ae *atomicError) Set(value error) {
+ ae.value.Store(value)
+}
+
+// Value returns the current error value
+func (ae *atomicError) Value() error {
+ if v := ae.value.Load(); v != nil {
+ // this will panic if the value doesn't implement the error interface
+ return v.(error)
+ }
+ return nil
+}
+
+func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
+ dargs := make([]driver.Value, len(named))
+ for n, param := range named {
+ if len(param.Name) > 0 {
+ // TODO: support the use of Named Parameters #561
+ return nil, errors.New("mysql: driver does not support the use of Named Parameters")
+ }
+ dargs[n] = param.Value
+ }
+ return dargs, nil
+}
+
+func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
+ switch sql.IsolationLevel(level) {
+ case sql.LevelRepeatableRead:
+ return "REPEATABLE READ", nil
+ case sql.LevelReadCommitted:
+ return "READ COMMITTED", nil
+ case sql.LevelReadUncommitted:
+ return "READ UNCOMMITTED", nil
+ case sql.LevelSerializable:
+ return "SERIALIZABLE", nil
+ default:
+ return "", fmt.Errorf("mysql: unsupported isolation level: %v", level)
+ }
+}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/jmoiron/sqlx/LICENSE b/vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/LICENSE
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/jmoiron/sqlx/LICENSE
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/LICENSE
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/jmoiron/sqlx/bind.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/bind.go
similarity index 76%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/jmoiron/sqlx/bind.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/bind.go
index 564635c..0fdc443 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/jmoiron/sqlx/bind.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/bind.go
@@ -21,13 +21,13 @@ const (
// BindType returns the bindtype for a given database given a drivername.
func BindType(driverName string) int {
switch driverName {
- case "postgres", "pgx":
+ case "postgres", "pgx", "pq-timeouts", "cloudsqlpostgres":
return DOLLAR
case "mysql":
return QUESTION
case "sqlite3":
return QUESTION
- case "oci8":
+ case "oci8", "ora", "goracle":
return NAMED
}
return UNKNOWN
@@ -43,27 +43,28 @@ func Rebind(bindType int, query string) string {
return query
}
- qb := []byte(query)
// Add space enough for 10 params before we have to allocate
- rqb := make([]byte, 0, len(qb)+10)
- j := 1
- for _, b := range qb {
- if b == '?' {
- switch bindType {
- case DOLLAR:
- rqb = append(rqb, '$')
- case NAMED:
- rqb = append(rqb, ':', 'a', 'r', 'g')
- }
- for _, b := range strconv.Itoa(j) {
- rqb = append(rqb, byte(b))
- }
- j++
- } else {
- rqb = append(rqb, b)
+ rqb := make([]byte, 0, len(query)+10)
+
+ var i, j int
+
+ for i = strings.Index(query, "?"); i != -1; i = strings.Index(query, "?") {
+ rqb = append(rqb, query[:i]...)
+
+ switch bindType {
+ case DOLLAR:
+ rqb = append(rqb, '$')
+ case NAMED:
+ rqb = append(rqb, ':', 'a', 'r', 'g')
}
+
+ j++
+ rqb = strconv.AppendInt(rqb, int64(j), 10)
+
+ query = query[i+1:]
}
- return string(rqb)
+
+ return string(append(rqb, query...))
}
// Experimental implementation of Rebind which uses a bytes.Buffer. The code is
@@ -112,7 +113,8 @@ func In(query string, args ...interface{}) (string, []interface{}, error) {
v := reflect.ValueOf(arg)
t := reflectx.Deref(v.Type())
- if t.Kind() == reflect.Slice {
+ // []byte is a driver.Value type so it should not be expanded
+ if t.Kind() == reflect.Slice && t != reflect.TypeOf([]byte{}) {
meta[i].length = v.Len()
meta[i].v = v
@@ -135,9 +137,9 @@ func In(query string, args ...interface{}) (string, []interface{}, error) {
}
newArgs := make([]interface{}, 0, flatArgsCount)
+ buf := bytes.NewBuffer(make([]byte, 0, len(query)+len(", ?")*flatArgsCount))
var arg, offset int
- var buf bytes.Buffer
for i := strings.IndexByte(query[offset:], '?'); i != -1; i = strings.IndexByte(query[offset:], '?') {
if arg >= len(meta) {
@@ -163,13 +165,12 @@ func In(query string, args ...interface{}) (string, []interface{}, error) {
// write everything up to and including our ? character
buf.WriteString(query[:offset+i+1])
- newArgs = append(newArgs, argMeta.v.Index(0).Interface())
-
for si := 1; si < argMeta.length; si++ {
buf.WriteString(", ?")
- newArgs = append(newArgs, argMeta.v.Index(si).Interface())
}
+ newArgs = appendReflectSlice(newArgs, argMeta.v, argMeta.length)
+
// slice the query and reset the offset. this avoids some bookkeeping for
// the write after the loop
query = query[offset+i+1:]
@@ -184,3 +185,24 @@ func In(query string, args ...interface{}) (string, []interface{}, error) {
return buf.String(), newArgs, nil
}
+
+func appendReflectSlice(args []interface{}, v reflect.Value, vlen int) []interface{} {
+ switch val := v.Interface().(type) {
+ case []interface{}:
+ args = append(args, val...)
+ case []int:
+ for i := range val {
+ args = append(args, val[i])
+ }
+ case []string:
+ for i := range val {
+ args = append(args, val[i])
+ }
+ default:
+ for si := 0; si < vlen; si++ {
+ args = append(args, v.Index(si).Interface())
+ }
+ }
+
+ return args
+}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/jmoiron/sqlx/doc.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/doc.go
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/jmoiron/sqlx/doc.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/doc.go
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/jmoiron/sqlx/named.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/named.go
similarity index 92%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/jmoiron/sqlx/named.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/named.go
index 4df8095..69eb954 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/jmoiron/sqlx/named.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/named.go
@@ -36,6 +36,7 @@ func (n *NamedStmt) Close() error {
}
// Exec executes a named statement using the struct passed.
+// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) Exec(arg interface{}) (sql.Result, error) {
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
if err != nil {
@@ -45,6 +46,7 @@ func (n *NamedStmt) Exec(arg interface{}) (sql.Result, error) {
}
// Query executes a named statement using the struct argument, returning rows.
+// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) Query(arg interface{}) (*sql.Rows, error) {
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
if err != nil {
@@ -56,6 +58,7 @@ func (n *NamedStmt) Query(arg interface{}) (*sql.Rows, error) {
// QueryRow executes a named statement against the database. Because sqlx cannot
// create a *sql.Row with an error condition pre-set for binding errors, sqlx
// returns a *sqlx.Row instead.
+// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) QueryRow(arg interface{}) *Row {
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
if err != nil {
@@ -65,6 +68,7 @@ func (n *NamedStmt) QueryRow(arg interface{}) *Row {
}
// MustExec execs a NamedStmt, panicing on error
+// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) MustExec(arg interface{}) sql.Result {
res, err := n.Exec(arg)
if err != nil {
@@ -74,6 +78,7 @@ func (n *NamedStmt) MustExec(arg interface{}) sql.Result {
}
// Queryx using this NamedStmt
+// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) Queryx(arg interface{}) (*Rows, error) {
r, err := n.Query(arg)
if err != nil {
@@ -84,11 +89,13 @@ func (n *NamedStmt) Queryx(arg interface{}) (*Rows, error) {
// QueryRowx this NamedStmt. Because of limitations with QueryRow, this is
// an alias for QueryRow.
+// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) QueryRowx(arg interface{}) *Row {
return n.QueryRow(arg)
}
// Select using this NamedStmt
+// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) Select(dest interface{}, arg interface{}) error {
rows, err := n.Queryx(arg)
if err != nil {
@@ -100,6 +107,7 @@ func (n *NamedStmt) Select(dest interface{}, arg interface{}) error {
}
// Get using this NamedStmt
+// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) Get(dest interface{}, arg interface{}) error {
r := n.QueryRowx(arg)
return r.scanAny(dest, false)
@@ -155,16 +163,18 @@ func bindArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{
v = v.Elem()
}
- fields := m.TraversalsByName(v.Type(), names)
- for i, t := range fields {
+ err := m.TraversalsByNameFunc(v.Type(), names, func(i int, t []int) error {
if len(t) == 0 {
- return arglist, fmt.Errorf("could not find name %s in %#v", names[i], arg)
+ return fmt.Errorf("could not find name %s in %#v", names[i], arg)
}
+
val := reflectx.FieldByIndexesReadOnly(v, t)
arglist = append(arglist, val.Interface())
- }
- return arglist, nil
+ return nil
+ })
+
+ return arglist, err
}
// like bindArgs, but for maps.
@@ -250,7 +260,7 @@ func compileNamedQuery(qs []byte, bindType int) (query string, names []string, e
inName = true
name = []byte{}
// if we're in a name, and this is an allowed character, continue
- } else if inName && (unicode.IsOneOf(allowedBindRunes, rune(b)) || b == '_') && i != last {
+ } else if inName && (unicode.IsOneOf(allowedBindRunes, rune(b)) || b == '_' || b == '.') && i != last {
// append the byte to the name if we are in a name and not on the last byte
name = append(name, b)
// if we're in a name and it's not an allowed character, the name is done
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/named_context.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/named_context.go
new file mode 100644
index 0000000..9405007
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/named_context.go
@@ -0,0 +1,132 @@
+// +build go1.8
+
+package sqlx
+
+import (
+ "context"
+ "database/sql"
+)
+
+// A union interface of contextPreparer and binder, required to be able to
+// prepare named statements with context (as the bindtype must be determined).
+type namedPreparerContext interface {
+ PreparerContext
+ binder
+}
+
+func prepareNamedContext(ctx context.Context, p namedPreparerContext, query string) (*NamedStmt, error) {
+ bindType := BindType(p.DriverName())
+ q, args, err := compileNamedQuery([]byte(query), bindType)
+ if err != nil {
+ return nil, err
+ }
+ stmt, err := PreparexContext(ctx, p, q)
+ if err != nil {
+ return nil, err
+ }
+ return &NamedStmt{
+ QueryString: q,
+ Params: args,
+ Stmt: stmt,
+ }, nil
+}
+
+// ExecContext executes a named statement using the struct passed.
+// Any named placeholder parameters are replaced with fields from arg.
+func (n *NamedStmt) ExecContext(ctx context.Context, arg interface{}) (sql.Result, error) {
+ args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
+ if err != nil {
+ return *new(sql.Result), err
+ }
+ return n.Stmt.ExecContext(ctx, args...)
+}
+
+// QueryContext executes a named statement using the struct argument, returning rows.
+// Any named placeholder parameters are replaced with fields from arg.
+func (n *NamedStmt) QueryContext(ctx context.Context, arg interface{}) (*sql.Rows, error) {
+ args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
+ if err != nil {
+ return nil, err
+ }
+ return n.Stmt.QueryContext(ctx, args...)
+}
+
+// QueryRowContext executes a named statement against the database. Because sqlx cannot
+// create a *sql.Row with an error condition pre-set for binding errors, sqlx
+// returns a *sqlx.Row instead.
+// Any named placeholder parameters are replaced with fields from arg.
+func (n *NamedStmt) QueryRowContext(ctx context.Context, arg interface{}) *Row {
+ args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
+ if err != nil {
+ return &Row{err: err}
+ }
+ return n.Stmt.QueryRowxContext(ctx, args...)
+}
+
+// MustExecContext execs a NamedStmt, panicing on error
+// Any named placeholder parameters are replaced with fields from arg.
+func (n *NamedStmt) MustExecContext(ctx context.Context, arg interface{}) sql.Result {
+ res, err := n.ExecContext(ctx, arg)
+ if err != nil {
+ panic(err)
+ }
+ return res
+}
+
+// QueryxContext using this NamedStmt
+// Any named placeholder parameters are replaced with fields from arg.
+func (n *NamedStmt) QueryxContext(ctx context.Context, arg interface{}) (*Rows, error) {
+ r, err := n.QueryContext(ctx, arg)
+ if err != nil {
+ return nil, err
+ }
+ return &Rows{Rows: r, Mapper: n.Stmt.Mapper, unsafe: isUnsafe(n)}, err
+}
+
+// QueryRowxContext this NamedStmt. Because of limitations with QueryRow, this is
+// an alias for QueryRow.
+// Any named placeholder parameters are replaced with fields from arg.
+func (n *NamedStmt) QueryRowxContext(ctx context.Context, arg interface{}) *Row {
+ return n.QueryRowContext(ctx, arg)
+}
+
+// SelectContext using this NamedStmt
+// Any named placeholder parameters are replaced with fields from arg.
+func (n *NamedStmt) SelectContext(ctx context.Context, dest interface{}, arg interface{}) error {
+ rows, err := n.QueryxContext(ctx, arg)
+ if err != nil {
+ return err
+ }
+ // if something happens here, we want to make sure the rows are Closed
+ defer rows.Close()
+ return scanAll(rows, dest, false)
+}
+
+// GetContext using this NamedStmt
+// Any named placeholder parameters are replaced with fields from arg.
+func (n *NamedStmt) GetContext(ctx context.Context, dest interface{}, arg interface{}) error {
+ r := n.QueryRowxContext(ctx, arg)
+ return r.scanAny(dest, false)
+}
+
+// NamedQueryContext binds a named query and then runs Query on the result using the
+// provided Ext (sqlx.Tx, sqlx.Db). It works with both structs and with
+// map[string]interface{} types.
+func NamedQueryContext(ctx context.Context, e ExtContext, query string, arg interface{}) (*Rows, error) {
+ q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
+ if err != nil {
+ return nil, err
+ }
+ return e.QueryxContext(ctx, q, args...)
+}
+
+// NamedExecContext uses BindStruct to get a query executable by the driver and
+// then runs Exec on the result. Returns an error from the binding
+// or the query excution itself.
+func NamedExecContext(ctx context.Context, e ExtContext, query string, arg interface{}) (sql.Result, error) {
+ q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
+ if err != nil {
+ return nil, err
+ }
+ return e.ExecContext(ctx, q, args...)
+}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/jmoiron/sqlx/reflectx/reflect.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/reflectx/reflect.go
similarity index 65%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/jmoiron/sqlx/reflectx/reflect.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/reflectx/reflect.go
index 5728011..73c21eb 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/jmoiron/sqlx/reflectx/reflect.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/reflectx/reflect.go
@@ -1,20 +1,19 @@
// Package reflectx implements extensions to the standard reflect lib suitable
-// for implementing marshaling and unmarshaling packages. The main Mapper type
-// allows for Go-compatible named atribute access, including accessing embedded
+// for implementing marshalling and unmarshalling packages. The main Mapper type
+// allows for Go-compatible named attribute access, including accessing embedded
// struct attributes and the ability to use functions and struct tags to
// customize field names.
//
package reflectx
import (
- "fmt"
"reflect"
"runtime"
"strings"
"sync"
)
-// A FieldInfo is a collection of metadata about a struct field.
+// A FieldInfo is metadata for a struct field.
type FieldInfo struct {
Index []int
Path string
@@ -41,7 +40,8 @@ func (f StructMap) GetByPath(path string) *FieldInfo {
}
// GetByTraversal returns a *FieldInfo for a given integer path. It is
-// analagous to reflect.FieldByIndex.
+// analogous to reflect.FieldByIndex, but using the cached traversal
+// rather than re-executing the reflect machinery each time.
func (f StructMap) GetByTraversal(index []int) *FieldInfo {
if len(index) == 0 {
return nil
@@ -58,8 +58,8 @@ func (f StructMap) GetByTraversal(index []int) *FieldInfo {
}
// Mapper is a general purpose mapper of names to struct fields. A Mapper
-// behaves like most marshallers, optionally obeying a field tag for name
-// mapping and a function to provide a basic mapping of fields to names.
+// behaves like most marshallers in the standard library, obeying a field tag
+// for name mapping but also providing a basic transform function.
type Mapper struct {
cache map[reflect.Type]*StructMap
tagName string
@@ -68,8 +68,8 @@ type Mapper struct {
mutex sync.Mutex
}
-// NewMapper returns a new mapper which optionally obeys the field tag given
-// by tagName. If tagName is the empty string, it is ignored.
+// NewMapper returns a new mapper using the tagName as its struct field tag.
+// If tagName is the empty string, it is ignored.
func NewMapper(tagName string) *Mapper {
return &Mapper{
cache: make(map[reflect.Type]*StructMap),
@@ -127,7 +127,7 @@ func (m *Mapper) FieldMap(v reflect.Value) map[string]reflect.Value {
return r
}
-// FieldByName returns a field by the its mapped name as a reflect.Value.
+// FieldByName returns a field by its mapped name as a reflect.Value.
// Panics if v's Kind is not Struct or v is not Indirectable to a struct Kind.
// Returns zero Value if the name is not found.
func (m *Mapper) FieldByName(v reflect.Value, name string) reflect.Value {
@@ -166,27 +166,47 @@ func (m *Mapper) FieldsByName(v reflect.Value, names []string) []reflect.Value {
// traversals for each mapped name. Panics if t is not a struct or Indirectable
// to a struct. Returns empty int slice for each name not found.
func (m *Mapper) TraversalsByName(t reflect.Type, names []string) [][]int {
- t = Deref(t)
- mustBe(t, reflect.Struct)
- tm := m.TypeMap(t)
-
r := make([][]int, 0, len(names))
- for _, name := range names {
- fi, ok := tm.Names[name]
- if !ok {
+ m.TraversalsByNameFunc(t, names, func(_ int, i []int) error {
+ if i == nil {
r = append(r, []int{})
} else {
- r = append(r, fi.Index)
+ r = append(r, i)
}
- }
+
+ return nil
+ })
return r
}
-// FieldByIndexes returns a value for a particular struct traversal.
+// TraversalsByNameFunc traverses the mapped names and calls fn with the index of
+// each name and the struct traversal represented by that name. Panics if t is not
+// a struct or Indirectable to a struct. Returns the first error returned by fn or nil.
+func (m *Mapper) TraversalsByNameFunc(t reflect.Type, names []string, fn func(int, []int) error) error {
+ t = Deref(t)
+ mustBe(t, reflect.Struct)
+ tm := m.TypeMap(t)
+ for i, name := range names {
+ fi, ok := tm.Names[name]
+ if !ok {
+ if err := fn(i, nil); err != nil {
+ return err
+ }
+ } else {
+ if err := fn(i, fi.Index); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// FieldByIndexes returns a value for the field given by the struct traversal
+// for the given value.
func FieldByIndexes(v reflect.Value, indexes []int) reflect.Value {
for _, i := range indexes {
v = reflect.Indirect(v).Field(i)
- // if this is a pointer, it's possible it is nil
+ // if this is a pointer and it's nil, allocate a new value and set it
if v.Kind() == reflect.Ptr && v.IsNil() {
alloc := reflect.New(Deref(v.Type()))
v.Set(alloc)
@@ -225,13 +245,12 @@ type kinder interface {
// mustBe checks a value against a kind, panicing with a reflect.ValueError
// if the kind isn't that which is required.
func mustBe(v kinder, expected reflect.Kind) {
- k := v.Kind()
- if k != expected {
+ if k := v.Kind(); k != expected {
panic(&reflect.ValueError{Method: methodName(), Kind: k})
}
}
-// methodName is returns the caller of the function calling methodName
+// methodName returns the caller of the function calling methodName
func methodName() string {
pc, _, _, _ := runtime.Caller(2)
f := runtime.FuncForPC(pc)
@@ -257,19 +276,92 @@ func apnd(is []int, i int) []int {
return x
}
+type mapf func(string) string
+
+// parseName parses the tag and the target name for the given field using
+// the tagName (eg 'json' for `json:"foo"` tags), mapFunc for mapping the
+// field's name to a target name, and tagMapFunc for mapping the tag to
+// a target name.
+func parseName(field reflect.StructField, tagName string, mapFunc, tagMapFunc mapf) (tag, fieldName string) {
+ // first, set the fieldName to the field's name
+ fieldName = field.Name
+ // if a mapFunc is set, use that to override the fieldName
+ if mapFunc != nil {
+ fieldName = mapFunc(fieldName)
+ }
+
+ // if there's no tag to look for, return the field name
+ if tagName == "" {
+ return "", fieldName
+ }
+
+ // if this tag is not set using the normal convention in the tag,
+ // then return the fieldname.. this check is done because according
+ // to the reflect documentation:
+ // If the tag does not have the conventional format,
+ // the value returned by Get is unspecified.
+ // which doesn't sound great.
+ if !strings.Contains(string(field.Tag), tagName+":") {
+ return "", fieldName
+ }
+
+ // at this point we're fairly sure that we have a tag, so lets pull it out
+ tag = field.Tag.Get(tagName)
+
+ // if we have a mapper function, call it on the whole tag
+ // XXX: this is a change from the old version, which pulled out the name
+ // before the tagMapFunc could be run, but I think this is the right way
+ if tagMapFunc != nil {
+ tag = tagMapFunc(tag)
+ }
+
+ // finally, split the options from the name
+ parts := strings.Split(tag, ",")
+ fieldName = parts[0]
+
+ return tag, fieldName
+}
+
+// parseOptions parses options out of a tag string, skipping the name
+func parseOptions(tag string) map[string]string {
+ parts := strings.Split(tag, ",")
+ options := make(map[string]string, len(parts))
+ if len(parts) > 1 {
+ for _, opt := range parts[1:] {
+ // short circuit potentially expensive split op
+ if strings.Contains(opt, "=") {
+ kv := strings.Split(opt, "=")
+ options[kv[0]] = kv[1]
+ continue
+ }
+ options[opt] = ""
+ }
+ }
+ return options
+}
+
// getMapping returns a mapping for the t type, using the tagName, mapFunc and
// tagMapFunc to determine the canonical names of fields.
-func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc func(string) string) *StructMap {
+func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc mapf) *StructMap {
m := []*FieldInfo{}
root := &FieldInfo{}
queue := []typeQueue{}
queue = append(queue, typeQueue{Deref(t), root, ""})
+QueueLoop:
for len(queue) != 0 {
// pop the first item off of the queue
tq := queue[0]
queue = queue[1:]
+
+ // ignore recursive field
+ for p := tq.fi.Parent; p != nil; p = p.Parent {
+ if tq.fi.Field.Type == p.Field.Type {
+ continue QueueLoop
+ }
+ }
+
nChildren := 0
if tq.t.Kind() == reflect.Struct {
nChildren = tq.t.NumField()
@@ -278,55 +370,33 @@ func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc func(string)
// iterate through all of its fields
for fieldPos := 0; fieldPos < nChildren; fieldPos++ {
+
f := tq.t.Field(fieldPos)
- fi := FieldInfo{}
- fi.Field = f
- fi.Zero = reflect.New(f.Type).Elem()
- fi.Options = map[string]string{}
-
- var tag, name string
- if tagName != "" && strings.Contains(string(f.Tag), tagName+":") {
- tag = f.Tag.Get(tagName)
- name = tag
- } else {
- if mapFunc != nil {
- name = mapFunc(f.Name)
- }
- }
-
- parts := strings.Split(name, ",")
- if len(parts) > 1 {
- name = parts[0]
- for _, opt := range parts[1:] {
- kv := strings.Split(opt, "=")
- if len(kv) > 1 {
- fi.Options[kv[0]] = kv[1]
- } else {
- fi.Options[kv[0]] = ""
- }
- }
- }
-
- if tagMapFunc != nil {
- tag = tagMapFunc(tag)
- }
-
- fi.Name = name
-
- if tq.pp == "" || (tq.pp == "" && tag == "") {
- fi.Path = fi.Name
- } else {
- fi.Path = fmt.Sprintf("%s.%s", tq.pp, fi.Name)
- }
+ // parse the tag and the target name using the mapping options for this field
+ tag, name := parseName(f, tagName, mapFunc, tagMapFunc)
// if the name is "-", disabled via a tag, skip it
if name == "-" {
continue
}
+ fi := FieldInfo{
+ Field: f,
+ Name: name,
+ Zero: reflect.New(f.Type).Elem(),
+ Options: parseOptions(tag),
+ }
+
+ // if the path is empty this path is just the name
+ if tq.pp == "" {
+ fi.Path = fi.Name
+ } else {
+ fi.Path = tq.pp + "." + fi.Name
+ }
+
// skip unexported fields
- if len(f.PkgPath) != 0 {
+ if len(f.PkgPath) != 0 && !f.Anonymous {
continue
}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/jmoiron/sqlx/sqlx.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/sqlx.go
similarity index 91%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/jmoiron/sqlx/sqlx.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/sqlx.go
index b1ba4cf..4385c3f 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/jmoiron/sqlx/sqlx.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/sqlx.go
@@ -10,6 +10,7 @@ import (
"path/filepath"
"reflect"
"strings"
+ "sync"
"github.com/jmoiron/sqlx/reflectx"
)
@@ -17,7 +18,7 @@ import (
// Although the NameMapper is convenient, in practice it should not
// be relied on except for application code. If you are writing a library
// that uses sqlx, you should be aware that the name mappings you expect
-// can be overridded by your user's application.
+// can be overridden by your user's application.
// NameMapper is used to map column names to struct field names. By default,
// it uses strings.ToLower to lowercase struct field names. It can be set
@@ -30,8 +31,14 @@ var origMapper = reflect.ValueOf(NameMapper)
// importers have time to customize the NameMapper.
var mpr *reflectx.Mapper
+// mprMu protects mpr.
+var mprMu sync.Mutex
+
// mapper returns a valid mapper using the configured NameMapper func.
func mapper() *reflectx.Mapper {
+ mprMu.Lock()
+ defer mprMu.Unlock()
+
if mpr == nil {
mpr = reflectx.NewMapperFunc("db", NameMapper)
} else if origMapper != reflect.ValueOf(NameMapper) {
@@ -221,6 +228,14 @@ func (r *Row) Columns() ([]string, error) {
return r.rows.Columns()
}
+// ColumnTypes returns the underlying sql.Rows.ColumnTypes(), or the deferred error
+func (r *Row) ColumnTypes() ([]*sql.ColumnType, error) {
+ if r.err != nil {
+ return []*sql.ColumnType{}, r.err
+ }
+ return r.rows.ColumnTypes()
+}
+
// Err returns the error encountered while scanning.
func (r *Row) Err() error {
return r.err
@@ -289,21 +304,26 @@ func (db *DB) BindNamed(query string, arg interface{}) (string, []interface{}, e
}
// NamedQuery using this DB.
+// Any named placeholder parameters are replaced with fields from arg.
func (db *DB) NamedQuery(query string, arg interface{}) (*Rows, error) {
return NamedQuery(db, query, arg)
}
// NamedExec using this DB.
+// Any named placeholder parameters are replaced with fields from arg.
func (db *DB) NamedExec(query string, arg interface{}) (sql.Result, error) {
return NamedExec(db, query, arg)
}
// Select using this DB.
+// Any placeholder parameters are replaced with supplied args.
func (db *DB) Select(dest interface{}, query string, args ...interface{}) error {
return Select(db, dest, query, args...)
}
// Get using this DB.
+// Any placeholder parameters are replaced with supplied args.
+// An error is returned if the result set is empty.
func (db *DB) Get(dest interface{}, query string, args ...interface{}) error {
return Get(db, dest, query, args...)
}
@@ -328,6 +348,7 @@ func (db *DB) Beginx() (*Tx, error) {
}
// Queryx queries the database and returns an *sqlx.Rows.
+// Any placeholder parameters are replaced with supplied args.
func (db *DB) Queryx(query string, args ...interface{}) (*Rows, error) {
r, err := db.DB.Query(query, args...)
if err != nil {
@@ -337,12 +358,14 @@ func (db *DB) Queryx(query string, args ...interface{}) (*Rows, error) {
}
// QueryRowx queries the database and returns an *sqlx.Row.
+// Any placeholder parameters are replaced with supplied args.
func (db *DB) QueryRowx(query string, args ...interface{}) *Row {
rows, err := db.DB.Query(query, args...)
return &Row{rows: rows, err: err, unsafe: db.unsafe, Mapper: db.Mapper}
}
// MustExec (panic) runs MustExec using this database.
+// Any placeholder parameters are replaced with supplied args.
func (db *DB) MustExec(query string, args ...interface{}) sql.Result {
return MustExec(db, query, args...)
}
@@ -387,21 +410,25 @@ func (tx *Tx) BindNamed(query string, arg interface{}) (string, []interface{}, e
}
// NamedQuery within a transaction.
+// Any named placeholder parameters are replaced with fields from arg.
func (tx *Tx) NamedQuery(query string, arg interface{}) (*Rows, error) {
return NamedQuery(tx, query, arg)
}
// NamedExec a named query within a transaction.
+// Any named placeholder parameters are replaced with fields from arg.
func (tx *Tx) NamedExec(query string, arg interface{}) (sql.Result, error) {
return NamedExec(tx, query, arg)
}
// Select within a transaction.
+// Any placeholder parameters are replaced with supplied args.
func (tx *Tx) Select(dest interface{}, query string, args ...interface{}) error {
return Select(tx, dest, query, args...)
}
// Queryx within a transaction.
+// Any placeholder parameters are replaced with supplied args.
func (tx *Tx) Queryx(query string, args ...interface{}) (*Rows, error) {
r, err := tx.Tx.Query(query, args...)
if err != nil {
@@ -411,17 +438,21 @@ func (tx *Tx) Queryx(query string, args ...interface{}) (*Rows, error) {
}
// QueryRowx within a transaction.
+// Any placeholder parameters are replaced with supplied args.
func (tx *Tx) QueryRowx(query string, args ...interface{}) *Row {
rows, err := tx.Tx.Query(query, args...)
return &Row{rows: rows, err: err, unsafe: tx.unsafe, Mapper: tx.Mapper}
}
// Get within a transaction.
+// Any placeholder parameters are replaced with supplied args.
+// An error is returned if the result set is empty.
func (tx *Tx) Get(dest interface{}, query string, args ...interface{}) error {
return Get(tx, dest, query, args...)
}
// MustExec runs MustExec within a transaction.
+// Any placeholder parameters are replaced with supplied args.
func (tx *Tx) MustExec(query string, args ...interface{}) sql.Result {
return MustExec(tx, query, args...)
}
@@ -478,28 +509,34 @@ func (s *Stmt) Unsafe() *Stmt {
}
// Select using the prepared statement.
+// Any placeholder parameters are replaced with supplied args.
func (s *Stmt) Select(dest interface{}, args ...interface{}) error {
return Select(&qStmt{s}, dest, "", args...)
}
// Get using the prepared statement.
+// Any placeholder parameters are replaced with supplied args.
+// An error is returned if the result set is empty.
func (s *Stmt) Get(dest interface{}, args ...interface{}) error {
return Get(&qStmt{s}, dest, "", args...)
}
// MustExec (panic) using this statement. Note that the query portion of the error
// output will be blank, as Stmt does not expose its query.
+// Any placeholder parameters are replaced with supplied args.
func (s *Stmt) MustExec(args ...interface{}) sql.Result {
return MustExec(&qStmt{s}, "", args...)
}
// QueryRowx using this statement.
+// Any placeholder parameters are replaced with supplied args.
func (s *Stmt) QueryRowx(args ...interface{}) *Row {
qs := &qStmt{s}
return qs.QueryRowx("", args...)
}
// Queryx using this statement.
+// Any placeholder parameters are replaced with supplied args.
func (s *Stmt) Queryx(args ...interface{}) (*Rows, error) {
qs := &qStmt{s}
return qs.Queryx("", args...)
@@ -564,7 +601,7 @@ func (r *Rows) StructScan(dest interface{}) error {
return errors.New("must pass a pointer, not a value, to StructScan destination")
}
- v = reflect.Indirect(v)
+ v = v.Elem()
if !r.started {
columns, err := r.Columns()
@@ -576,7 +613,7 @@ func (r *Rows) StructScan(dest interface{}) error {
r.fields = m.TraversalsByName(v.Type(), columns)
// if we are not unsafe and are missing fields, return an error
if f, err := missingFields(r.fields); err != nil && !r.unsafe {
- return fmt.Errorf("missing destination name %s", columns[f])
+ return fmt.Errorf("missing destination name %s in %T", columns[f], dest)
}
r.values = make([]interface{}, len(columns))
r.started = true
@@ -598,10 +635,14 @@ func (r *Rows) StructScan(dest interface{}) error {
func Connect(driverName, dataSourceName string) (*DB, error) {
db, err := Open(driverName, dataSourceName)
if err != nil {
- return db, err
+ return nil, err
}
err = db.Ping()
- return db, err
+ if err != nil {
+ db.Close()
+ return nil, err
+ }
+ return db, nil
}
// MustConnect connects to a database and panics on error.
@@ -626,6 +667,7 @@ func Preparex(p Preparer, query string) (*Stmt, error) {
// into dest, which must be a slice. If the slice elements are scannable, then
// the result set must have only one column. Otherwise, StructScan is used.
// The *sql.Rows are closed automatically.
+// Any placeholder parameters are replaced with supplied args.
func Select(q Queryer, dest interface{}, query string, args ...interface{}) error {
rows, err := q.Queryx(query, args...)
if err != nil {
@@ -639,6 +681,8 @@ func Select(q Queryer, dest interface{}, query string, args ...interface{}) erro
// Get does a QueryRow using the provided Queryer, and scans the resulting row
// to dest. If dest is scannable, the result must only have one column. Otherwise,
// StructScan is used. Get will return sql.ErrNoRows like row.Scan would.
+// Any placeholder parameters are replaced with supplied args.
+// An error is returned if the result set is empty.
func Get(q Queryer, dest interface{}, query string, args ...interface{}) error {
r := q.QueryRowx(query, args...)
return r.scanAny(dest, false)
@@ -669,6 +713,7 @@ func LoadFile(e Execer, path string) (*sql.Result, error) {
}
// MustExec execs the query using e and panics if there was an error.
+// Any placeholder parameters are replaced with supplied args.
func MustExec(e Execer, query string, args ...interface{}) sql.Result {
res, err := e.Exec(query, args...)
if err != nil {
@@ -691,6 +736,10 @@ func (r *Row) scanAny(dest interface{}, structOnly bool) error {
if r.err != nil {
return r.err
}
+ if r.rows == nil {
+ r.err = sql.ErrNoRows
+ return r.err
+ }
defer r.rows.Close()
v := reflect.ValueOf(dest)
@@ -726,7 +775,7 @@ func (r *Row) scanAny(dest interface{}, structOnly bool) error {
fields := m.TraversalsByName(v.Type(), columns)
// if we are not unsafe and are missing fields, return an error
if f, err := missingFields(fields); err != nil && !r.unsafe {
- return fmt.Errorf("missing destination name %s", columns[f])
+ return fmt.Errorf("missing destination name %s in %T", columns[f], dest)
}
values := make([]interface{}, len(columns))
@@ -744,7 +793,7 @@ func (r *Row) StructScan(dest interface{}) error {
}
// SliceScan a row, returning a []interface{} with values similar to MapScan.
-// This function is primarly intended for use where the number of columns
+// This function is primarily intended for use where the number of columns
// is not known. Because you can pass an []interface{} directly to Scan,
// it's recommended that you do that as it will not have to allocate new
// slices per row.
@@ -779,7 +828,7 @@ func SliceScan(r ColScanner) ([]interface{}, error) {
// executes SQL from input). Please do not use this as a primary interface!
// This will modify the map sent to it in place, so reuse the same map with
// care. Columns which occur more than once in the result will overwrite
-// eachother!
+// each other!
func MapScan(r ColScanner, dest map[string]interface{}) error {
// ignore r.started, since we needn't use reflect for anything.
columns, err := r.Columns()
@@ -892,7 +941,7 @@ func scanAll(rows rowsi, dest interface{}, structOnly bool) error {
fields := m.TraversalsByName(base, columns)
// if we are not unsafe and are missing fields, return an error
if f, err := missingFields(fields); err != nil && !isUnsafe(rows) {
- return fmt.Errorf("missing destination name %s", columns[f])
+ return fmt.Errorf("missing destination name %s in %T", columns[f], dest)
}
values = make([]interface{}, len(columns))
@@ -902,6 +951,9 @@ func scanAll(rows rowsi, dest interface{}, structOnly bool) error {
v = reflect.Indirect(vp)
err = fieldsByTraversal(v, fields, values, true)
+ if err != nil {
+ return err
+ }
// scan into the struct field pointers and append to our results
err = rows.Scan(values...)
@@ -919,6 +971,9 @@ func scanAll(rows rowsi, dest interface{}, structOnly bool) error {
for rows.Next() {
vp = reflect.New(base)
err = rows.Scan(vp.Interface())
+ if err != nil {
+ return err
+ }
// append
if isPtr {
direct.Set(reflect.Append(direct, vp))
@@ -937,7 +992,7 @@ func scanAll(rows rowsi, dest interface{}, structOnly bool) error {
// anyway) works on a rows object.
// StructScan all rows from an sql.Rows or an sqlx.Rows into the dest slice.
-// StructScan will scan in the entire rows result, so if you need do not want to
+// StructScan will scan in the entire rows result, so if you do not want to
// allocate structs for the entire result, use Queryx and see sqlx.Rows.StructScan.
// If rows is sqlx.Rows, it will use its mapper, otherwise it will use the default.
func StructScan(rows rowsi, dest interface{}) error {
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/sqlx_context.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/sqlx_context.go
new file mode 100644
index 0000000..d58ff33
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/jmoiron/sqlx/sqlx_context.go
@@ -0,0 +1,348 @@
+// +build go1.8
+
+package sqlx
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+ "io/ioutil"
+ "path/filepath"
+ "reflect"
+)
+
+// ConnectContext to a database and verify with a ping.
+func ConnectContext(ctx context.Context, driverName, dataSourceName string) (*DB, error) {
+ db, err := Open(driverName, dataSourceName)
+ if err != nil {
+ return db, err
+ }
+ err = db.PingContext(ctx)
+ return db, err
+}
+
+// QueryerContext is an interface used by GetContext and SelectContext
+type QueryerContext interface {
+ QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
+ QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
+ QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row
+}
+
+// PreparerContext is an interface used by PreparexContext.
+type PreparerContext interface {
+ PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
+}
+
+// ExecerContext is an interface used by MustExecContext and LoadFileContext
+type ExecerContext interface {
+ ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
+}
+
+// ExtContext is a union interface which can bind, query, and exec, with Context
+// used by NamedQueryContext and NamedExecContext.
+type ExtContext interface {
+ binder
+ QueryerContext
+ ExecerContext
+}
+
+// SelectContext executes a query using the provided Queryer, and StructScans
+// each row into dest, which must be a slice. If the slice elements are
+// scannable, then the result set must have only one column. Otherwise,
+// StructScan is used. The *sql.Rows are closed automatically.
+// Any placeholder parameters are replaced with supplied args.
+func SelectContext(ctx context.Context, q QueryerContext, dest interface{}, query string, args ...interface{}) error {
+ rows, err := q.QueryxContext(ctx, query, args...)
+ if err != nil {
+ return err
+ }
+ // if something happens here, we want to make sure the rows are Closed
+ defer rows.Close()
+ return scanAll(rows, dest, false)
+}
+
+// PreparexContext prepares a statement.
+//
+// The provided context is used for the preparation of the statement, not for
+// the execution of the statement.
+func PreparexContext(ctx context.Context, p PreparerContext, query string) (*Stmt, error) {
+ s, err := p.PrepareContext(ctx, query)
+ if err != nil {
+ return nil, err
+ }
+ return &Stmt{Stmt: s, unsafe: isUnsafe(p), Mapper: mapperFor(p)}, err
+}
+
+// GetContext does a QueryRow using the provided Queryer, and scans the
+// resulting row to dest. If dest is scannable, the result must only have one
+// column. Otherwise, StructScan is used. Get will return sql.ErrNoRows like
+// row.Scan would. Any placeholder parameters are replaced with supplied args.
+// An error is returned if the result set is empty.
+func GetContext(ctx context.Context, q QueryerContext, dest interface{}, query string, args ...interface{}) error {
+ r := q.QueryRowxContext(ctx, query, args...)
+ return r.scanAny(dest, false)
+}
+
+// LoadFileContext exec's every statement in a file (as a single call to Exec).
+// LoadFileContext may return a nil *sql.Result if errors are encountered
+// locating or reading the file at path. LoadFile reads the entire file into
+// memory, so it is not suitable for loading large data dumps, but can be useful
+// for initializing schemas or loading indexes.
+//
+// FIXME: this does not really work with multi-statement files for mattn/go-sqlite3
+// or the go-mysql-driver/mysql drivers; pq seems to be an exception here. Detecting
+// this by requiring something with DriverName() and then attempting to split the
+// queries will be difficult to get right, and its current driver-specific behavior
+// is deemed at least not complex in its incorrectness.
+func LoadFileContext(ctx context.Context, e ExecerContext, path string) (*sql.Result, error) {
+ realpath, err := filepath.Abs(path)
+ if err != nil {
+ return nil, err
+ }
+ contents, err := ioutil.ReadFile(realpath)
+ if err != nil {
+ return nil, err
+ }
+ res, err := e.ExecContext(ctx, string(contents))
+ return &res, err
+}
+
+// MustExecContext execs the query using e and panics if there was an error.
+// Any placeholder parameters are replaced with supplied args.
+func MustExecContext(ctx context.Context, e ExecerContext, query string, args ...interface{}) sql.Result {
+ res, err := e.ExecContext(ctx, query, args...)
+ if err != nil {
+ panic(err)
+ }
+ return res
+}
+
+// PrepareNamedContext returns an sqlx.NamedStmt
+func (db *DB) PrepareNamedContext(ctx context.Context, query string) (*NamedStmt, error) {
+ return prepareNamedContext(ctx, db, query)
+}
+
+// NamedQueryContext using this DB.
+// Any named placeholder parameters are replaced with fields from arg.
+func (db *DB) NamedQueryContext(ctx context.Context, query string, arg interface{}) (*Rows, error) {
+ return NamedQueryContext(ctx, db, query, arg)
+}
+
+// NamedExecContext using this DB.
+// Any named placeholder parameters are replaced with fields from arg.
+func (db *DB) NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error) {
+ return NamedExecContext(ctx, db, query, arg)
+}
+
+// SelectContext using this DB.
+// Any placeholder parameters are replaced with supplied args.
+func (db *DB) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
+ return SelectContext(ctx, db, dest, query, args...)
+}
+
+// GetContext using this DB.
+// Any placeholder parameters are replaced with supplied args.
+// An error is returned if the result set is empty.
+func (db *DB) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
+ return GetContext(ctx, db, dest, query, args...)
+}
+
+// PreparexContext returns an sqlx.Stmt instead of a sql.Stmt.
+//
+// The provided context is used for the preparation of the statement, not for
+// the execution of the statement.
+func (db *DB) PreparexContext(ctx context.Context, query string) (*Stmt, error) {
+ return PreparexContext(ctx, db, query)
+}
+
+// QueryxContext queries the database and returns an *sqlx.Rows.
+// Any placeholder parameters are replaced with supplied args.
+func (db *DB) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
+ r, err := db.DB.QueryContext(ctx, query, args...)
+ if err != nil {
+ return nil, err
+ }
+ return &Rows{Rows: r, unsafe: db.unsafe, Mapper: db.Mapper}, err
+}
+
+// QueryRowxContext queries the database and returns an *sqlx.Row.
+// Any placeholder parameters are replaced with supplied args.
+func (db *DB) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row {
+ rows, err := db.DB.QueryContext(ctx, query, args...)
+ return &Row{rows: rows, err: err, unsafe: db.unsafe, Mapper: db.Mapper}
+}
+
+// MustBeginTx starts a transaction, and panics on error. Returns an *sqlx.Tx instead
+// of an *sql.Tx.
+//
+// The provided context is used until the transaction is committed or rolled
+// back. If the context is canceled, the sql package will roll back the
+// transaction. Tx.Commit will return an error if the context provided to
+// MustBeginContext is canceled.
+func (db *DB) MustBeginTx(ctx context.Context, opts *sql.TxOptions) *Tx {
+ tx, err := db.BeginTxx(ctx, opts)
+ if err != nil {
+ panic(err)
+ }
+ return tx
+}
+
+// MustExecContext (panic) runs MustExec using this database.
+// Any placeholder parameters are replaced with supplied args.
+func (db *DB) MustExecContext(ctx context.Context, query string, args ...interface{}) sql.Result {
+ return MustExecContext(ctx, db, query, args...)
+}
+
+// BeginTxx begins a transaction and returns an *sqlx.Tx instead of an
+// *sql.Tx.
+//
+// The provided context is used until the transaction is committed or rolled
+// back. If the context is canceled, the sql package will roll back the
+// transaction. Tx.Commit will return an error if the context provided to
+// BeginxContext is canceled.
+func (db *DB) BeginTxx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
+ tx, err := db.DB.BeginTx(ctx, opts)
+ if err != nil {
+ return nil, err
+ }
+ return &Tx{Tx: tx, driverName: db.driverName, unsafe: db.unsafe, Mapper: db.Mapper}, err
+}
+
+// StmtxContext returns a version of the prepared statement which runs within a
+// transaction. Provided stmt can be either *sql.Stmt or *sqlx.Stmt.
+func (tx *Tx) StmtxContext(ctx context.Context, stmt interface{}) *Stmt {
+ var s *sql.Stmt
+ switch v := stmt.(type) {
+ case Stmt:
+ s = v.Stmt
+ case *Stmt:
+ s = v.Stmt
+ case sql.Stmt:
+ s = &v
+ case *sql.Stmt:
+ s = v
+ default:
+ panic(fmt.Sprintf("non-statement type %v passed to Stmtx", reflect.ValueOf(stmt).Type()))
+ }
+ return &Stmt{Stmt: tx.StmtContext(ctx, s), Mapper: tx.Mapper}
+}
+
+// NamedStmtContext returns a version of the prepared statement which runs
+// within a transaction.
+func (tx *Tx) NamedStmtContext(ctx context.Context, stmt *NamedStmt) *NamedStmt {
+ return &NamedStmt{
+ QueryString: stmt.QueryString,
+ Params: stmt.Params,
+ Stmt: tx.StmtxContext(ctx, stmt.Stmt),
+ }
+}
+
+// PreparexContext returns an sqlx.Stmt instead of a sql.Stmt.
+//
+// The provided context is used for the preparation of the statement, not for
+// the execution of the statement.
+func (tx *Tx) PreparexContext(ctx context.Context, query string) (*Stmt, error) {
+ return PreparexContext(ctx, tx, query)
+}
+
+// PrepareNamedContext returns an sqlx.NamedStmt
+func (tx *Tx) PrepareNamedContext(ctx context.Context, query string) (*NamedStmt, error) {
+ return prepareNamedContext(ctx, tx, query)
+}
+
+// MustExecContext runs MustExecContext within a transaction.
+// Any placeholder parameters are replaced with supplied args.
+func (tx *Tx) MustExecContext(ctx context.Context, query string, args ...interface{}) sql.Result {
+ return MustExecContext(ctx, tx, query, args...)
+}
+
+// QueryxContext within a transaction and context.
+// Any placeholder parameters are replaced with supplied args.
+func (tx *Tx) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
+ r, err := tx.Tx.QueryContext(ctx, query, args...)
+ if err != nil {
+ return nil, err
+ }
+ return &Rows{Rows: r, unsafe: tx.unsafe, Mapper: tx.Mapper}, err
+}
+
+// SelectContext within a transaction and context.
+// Any placeholder parameters are replaced with supplied args.
+func (tx *Tx) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
+ return SelectContext(ctx, tx, dest, query, args...)
+}
+
+// GetContext within a transaction and context.
+// Any placeholder parameters are replaced with supplied args.
+// An error is returned if the result set is empty.
+func (tx *Tx) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
+ return GetContext(ctx, tx, dest, query, args...)
+}
+
+// QueryRowxContext within a transaction and context.
+// Any placeholder parameters are replaced with supplied args.
+func (tx *Tx) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row {
+ rows, err := tx.Tx.QueryContext(ctx, query, args...)
+ return &Row{rows: rows, err: err, unsafe: tx.unsafe, Mapper: tx.Mapper}
+}
+
+// NamedExecContext using this Tx.
+// Any named placeholder parameters are replaced with fields from arg.
+func (tx *Tx) NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error) {
+ return NamedExecContext(ctx, tx, query, arg)
+}
+
+// SelectContext using the prepared statement.
+// Any placeholder parameters are replaced with supplied args.
+func (s *Stmt) SelectContext(ctx context.Context, dest interface{}, args ...interface{}) error {
+ return SelectContext(ctx, &qStmt{s}, dest, "", args...)
+}
+
+// GetContext using the prepared statement.
+// Any placeholder parameters are replaced with supplied args.
+// An error is returned if the result set is empty.
+func (s *Stmt) GetContext(ctx context.Context, dest interface{}, args ...interface{}) error {
+ return GetContext(ctx, &qStmt{s}, dest, "", args...)
+}
+
+// MustExecContext (panic) using this statement. Note that the query portion of
+// the error output will be blank, as Stmt does not expose its query.
+// Any placeholder parameters are replaced with supplied args.
+func (s *Stmt) MustExecContext(ctx context.Context, args ...interface{}) sql.Result {
+ return MustExecContext(ctx, &qStmt{s}, "", args...)
+}
+
+// QueryRowxContext using this statement.
+// Any placeholder parameters are replaced with supplied args.
+func (s *Stmt) QueryRowxContext(ctx context.Context, args ...interface{}) *Row {
+ qs := &qStmt{s}
+ return qs.QueryRowxContext(ctx, "", args...)
+}
+
+// QueryxContext using this statement.
+// Any placeholder parameters are replaced with supplied args.
+func (s *Stmt) QueryxContext(ctx context.Context, args ...interface{}) (*Rows, error) {
+ qs := &qStmt{s}
+ return qs.QueryxContext(ctx, "", args...)
+}
+
+func (q *qStmt) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
+ return q.Stmt.QueryContext(ctx, args...)
+}
+
+func (q *qStmt) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
+ r, err := q.Stmt.QueryContext(ctx, args...)
+ if err != nil {
+ return nil, err
+ }
+ return &Rows{Rows: r, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper}, err
+}
+
+func (q *qStmt) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row {
+ rows, err := q.Stmt.QueryContext(ctx, args...)
+ return &Row{rows: rows, err: err, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper}
+}
+
+func (q *qStmt) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
+ return q.Stmt.ExecContext(ctx, args...)
+}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/juju/errors/LICENSE b/vendor/github.com/siddontang/go-mysql/vendor/github.com/juju/errors/LICENSE
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/juju/errors/LICENSE
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/juju/errors/LICENSE
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/juju/errors/doc.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/juju/errors/doc.go
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/juju/errors/doc.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/juju/errors/doc.go
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/juju/errors/error.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/juju/errors/error.go
similarity index 86%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/juju/errors/error.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/juju/errors/error.go
index 8c51c45..b7df735 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/juju/errors/error.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/juju/errors/error.go
@@ -124,6 +124,33 @@ func (e *Err) Error() string {
return fmt.Sprintf("%s: %v", e.message, err)
}
+// Format implements fmt.Formatter
+// When printing errors with %+v it also prints the stack trace.
+// %#v unsurprisingly will print the real underlying type.
+func (e *Err) Format(s fmt.State, verb rune) {
+ switch verb {
+ case 'v':
+ switch {
+ case s.Flag('+'):
+ fmt.Fprintf(s, "%s", ErrorStack(e))
+ return
+ case s.Flag('#'):
+ // avoid infinite recursion by wrapping e into a type
+ // that doesn't implement Formatter.
+ fmt.Fprintf(s, "%#v", (*unformatter)(e))
+ return
+ }
+ fallthrough
+ case 's':
+ fmt.Fprintf(s, "%s", e.Error())
+ }
+}
+
+// helper for Format
+type unformatter Err
+
+func (unformatter) Format() { /* break the fmt.Formatter interface */ }
+
// SetLocation records the source location of the error at callDepth stack
// frames above the call.
func (e *Err) SetLocation(callDepth int) {
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/juju/errors/errortypes.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/juju/errors/errortypes.go
similarity index 92%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/juju/errors/errortypes.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/juju/errors/errortypes.go
index 10b3b19..9b731c4 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/juju/errors/errortypes.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/juju/errors/errortypes.go
@@ -282,3 +282,28 @@ func IsMethodNotAllowed(err error) bool {
_, ok := err.(*methodNotAllowed)
return ok
}
+
+// forbidden represents an error when a request cannot be completed because of
+// missing privileges
+type forbidden struct {
+ Err
+}
+
+// Forbiddenf returns an error which satistifes IsForbidden()
+func Forbiddenf(format string, args ...interface{}) error {
+ return &forbidden{wrap(nil, format, "", args...)}
+}
+
+// NewForbidden returns an error which wraps err that satisfies
+// IsForbidden().
+func NewForbidden(err error, msg string) error {
+ return &forbidden{wrap(err, msg, "")}
+}
+
+// IsForbidden reports whether err was created with Forbiddenf() or
+// NewForbidden().
+func IsForbidden(err error) bool {
+ err = Cause(err)
+ _, ok := err.(*forbidden)
+ return ok
+}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/juju/errors/functions.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/juju/errors/functions.go
similarity index 99%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/juju/errors/functions.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/juju/errors/functions.go
index 994208d..f86b09b 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/juju/errors/functions.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/juju/errors/functions.go
@@ -8,7 +8,7 @@ import (
"strings"
)
-// New is a drop in replacement for the standard libary errors module that records
+// New is a drop in replacement for the standard library errors module that records
// the location that the error is created.
//
// For example:
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/juju/errors/path.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/juju/errors/path.go
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/juju/errors/path.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/juju/errors/path.go
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/benchmark.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/benchmark.go
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/benchmark.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/benchmark.go
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/check.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/check.go
similarity index 96%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/check.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/check.go
index c99392a..fc535bc 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/check.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/check.go
@@ -86,6 +86,7 @@ type C struct {
logb *logger
logw io.Writer
done chan *C
+ parallel chan *C
reason string
mustFail bool
tempDir *tempDir
@@ -533,6 +534,7 @@ type RunConf struct {
BenchmarkTime time.Duration // Defaults to 1 second
BenchmarkMem bool
KeepWorkDir bool
+ Exclude string
}
// Create a new suiteRunner able to run all methods in the given suite.
@@ -577,6 +579,17 @@ func newSuiteRunner(suite interface{}, runConf *RunConf) *suiteRunner {
}
}
+ var excludeRegexp *regexp.Regexp
+ if conf.Exclude != "" {
+ if regexp, err := regexp.Compile(conf.Exclude); err != nil {
+ msg := "Bad exclude expression: " + err.Error()
+ runner.tracker.result.RunError = errors.New(msg)
+ return runner
+ } else {
+ excludeRegexp = regexp
+ }
+ }
+
for i := 0; i != suiteNumMethods; i++ {
method := newMethod(suiteValue, i)
switch method.Info.Name {
@@ -597,7 +610,9 @@ func newSuiteRunner(suite interface{}, runConf *RunConf) *suiteRunner {
continue
}
if filterRegexp == nil || method.matches(filterRegexp) {
- runner.tests = append(runner.tests, method)
+ if excludeRegexp == nil || !method.matches(excludeRegexp) {
+ runner.tests = append(runner.tests, method)
+ }
}
}
}
@@ -611,13 +626,23 @@ func (runner *suiteRunner) run() *Result {
if runner.checkFixtureArgs() {
c := runner.runFixture(runner.setUpSuite, "", nil)
if c == nil || c.status() == succeededSt {
+ var delayedC []*C
for i := 0; i != len(runner.tests); i++ {
- c := runner.runTest(runner.tests[i])
+ c := runner.forkTest(runner.tests[i])
+ select {
+ case <-c.done:
+ case <-c.parallel:
+ delayedC = append(delayedC, c)
+ }
if c.status() == fixturePanickedSt {
runner.skipTests(missedSt, runner.tests[i+1:])
break
}
}
+ // Wait those parallel tests finish.
+ for _, delayed := range delayedC {
+ <-delayed.done
+ }
} else if c != nil && c.status() == skippedSt {
runner.skipTests(skippedSt, runner.tests)
} else {
@@ -655,6 +680,7 @@ func (runner *suiteRunner) forkCall(method *methodType, kind funcKind, testName
logw: logw,
tempDir: runner.tempDir,
done: make(chan *C, 1),
+ parallel: make(chan *C, 1),
timer: timer{benchTime: runner.benchTime},
startTime: time.Now(),
benchMem: runner.benchMem,
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/checkers.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/checkers.go
similarity index 99%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/checkers.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/checkers.go
index bac3387..3749545 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/checkers.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/checkers.go
@@ -212,7 +212,7 @@ type hasLenChecker struct {
// The HasLen checker verifies that the obtained value has the
// provided length. In many cases this is superior to using Equals
-// in conjuction with the len function because in case the check
+// in conjunction with the len function because in case the check
// fails the value itself will be printed, instead of its length,
// providing more details for figuring the problem.
//
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/checkers2.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/checkers2.go
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/checkers2.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/checkers2.go
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/compare.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/compare.go
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/compare.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/compare.go
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/helpers.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/helpers.go
similarity index 98%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/helpers.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/helpers.go
index 58a733b..68e861d 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/helpers.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/helpers.go
@@ -76,6 +76,11 @@ func (c *C) Skip(reason string) {
c.stopNow()
}
+// Parallel will mark the test run parallel within a test suite.
+func (c *C) Parallel() {
+ c.parallel <- c
+}
+
// -----------------------------------------------------------------------
// Basic logging.
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/printer.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/printer.go
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/printer.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/printer.go
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/run.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/run.go
similarity index 95%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/run.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/run.go
index da8fd79..afa631f 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/pingcap/check/run.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/pingcap/check/run.go
@@ -42,8 +42,11 @@ var (
newBenchMem = flag.Bool("check.bmem", false, "Report memory benchmarks")
newListFlag = flag.Bool("check.list", false, "List the names of all tests that will be run")
newWorkFlag = flag.Bool("check.work", false, "Display and do not remove the test working directory")
+ newExcludeFlag = flag.String("check.exclude", "", "Regular expression to exclude tests to run")
)
+var CustomVerboseFlag bool
+
// TestingT runs all test suites registered with the Suite function,
// printing results to stdout, and reporting any failures back to
// the "testing" package.
@@ -54,12 +57,13 @@ func TestingT(testingT *testing.T) {
}
conf := &RunConf{
Filter: *oldFilterFlag + *newFilterFlag,
- Verbose: *oldVerboseFlag || *newVerboseFlag,
+ Verbose: *oldVerboseFlag || *newVerboseFlag || CustomVerboseFlag,
Stream: *oldStreamFlag || *newStreamFlag,
Benchmark: *oldBenchFlag || *newBenchFlag,
BenchmarkTime: benchTime,
BenchmarkMem: *newBenchMem,
KeepWorkDir: *oldWorkFlag || *newWorkFlag,
+ Exclude: *newExcludeFlag,
}
if *oldListFlag || *newListFlag {
w := bufio.NewWriter(os.Stdout)
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/satori/go.uuid/LICENSE b/vendor/github.com/siddontang/go-mysql/vendor/github.com/satori/go.uuid/LICENSE
similarity index 94%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/satori/go.uuid/LICENSE
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/satori/go.uuid/LICENSE
index 488357b..926d549 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/satori/go.uuid/LICENSE
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/satori/go.uuid/LICENSE
@@ -1,4 +1,4 @@
-Copyright (C) 2013-2016 by Maxim Bublis
+Copyright (C) 2013-2018 by Maxim Bublis
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/satori/go.uuid/codec.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/satori/go.uuid/codec.go
new file mode 100644
index 0000000..656892c
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/satori/go.uuid/codec.go
@@ -0,0 +1,206 @@
+// Copyright (C) 2013-2018 by Maxim Bublis
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package uuid
+
+import (
+ "bytes"
+ "encoding/hex"
+ "fmt"
+)
+
+// FromBytes returns UUID converted from raw byte slice input.
+// It will return error if the slice isn't 16 bytes long.
+func FromBytes(input []byte) (u UUID, err error) {
+ err = u.UnmarshalBinary(input)
+ return
+}
+
+// FromBytesOrNil returns UUID converted from raw byte slice input.
+// Same behavior as FromBytes, but returns a Nil UUID on error.
+func FromBytesOrNil(input []byte) UUID {
+ uuid, err := FromBytes(input)
+ if err != nil {
+ return Nil
+ }
+ return uuid
+}
+
+// FromString returns UUID parsed from string input.
+// Input is expected in a form accepted by UnmarshalText.
+func FromString(input string) (u UUID, err error) {
+ err = u.UnmarshalText([]byte(input))
+ return
+}
+
+// FromStringOrNil returns UUID parsed from string input.
+// Same behavior as FromString, but returns a Nil UUID on error.
+func FromStringOrNil(input string) UUID {
+ uuid, err := FromString(input)
+ if err != nil {
+ return Nil
+ }
+ return uuid
+}
+
+// MarshalText implements the encoding.TextMarshaler interface.
+// The encoding is the same as returned by String.
+func (u UUID) MarshalText() (text []byte, err error) {
+ text = []byte(u.String())
+ return
+}
+
+// UnmarshalText implements the encoding.TextUnmarshaler interface.
+// Following formats are supported:
+// "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
+// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}",
+// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
+// "6ba7b8109dad11d180b400c04fd430c8"
+// ABNF for supported UUID text representation follows:
+// uuid := canonical | hashlike | braced | urn
+// plain := canonical | hashlike
+// canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct
+// hashlike := 12hexoct
+// braced := '{' plain '}'
+// urn := URN ':' UUID-NID ':' plain
+// URN := 'urn'
+// UUID-NID := 'uuid'
+// 12hexoct := 6hexoct 6hexoct
+// 6hexoct := 4hexoct 2hexoct
+// 4hexoct := 2hexoct 2hexoct
+// 2hexoct := hexoct hexoct
+// hexoct := hexdig hexdig
+// hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |
+// 'a' | 'b' | 'c' | 'd' | 'e' | 'f' |
+// 'A' | 'B' | 'C' | 'D' | 'E' | 'F'
+func (u *UUID) UnmarshalText(text []byte) (err error) {
+ switch len(text) {
+ case 32:
+ return u.decodeHashLike(text)
+ case 36:
+ return u.decodeCanonical(text)
+ case 38:
+ return u.decodeBraced(text)
+ case 41:
+ fallthrough
+ case 45:
+ return u.decodeURN(text)
+ default:
+ return fmt.Errorf("uuid: incorrect UUID length: %s", text)
+ }
+}
+
+// decodeCanonical decodes UUID string in format
+// "6ba7b810-9dad-11d1-80b4-00c04fd430c8".
+func (u *UUID) decodeCanonical(t []byte) (err error) {
+ if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' {
+ return fmt.Errorf("uuid: incorrect UUID format %s", t)
+ }
+
+ src := t[:]
+ dst := u[:]
+
+ for i, byteGroup := range byteGroups {
+ if i > 0 {
+ src = src[1:] // skip dash
+ }
+ _, err = hex.Decode(dst[:byteGroup/2], src[:byteGroup])
+ if err != nil {
+ return
+ }
+ src = src[byteGroup:]
+ dst = dst[byteGroup/2:]
+ }
+
+ return
+}
+
+// decodeHashLike decodes UUID string in format
+// "6ba7b8109dad11d180b400c04fd430c8".
+func (u *UUID) decodeHashLike(t []byte) (err error) {
+ src := t[:]
+ dst := u[:]
+
+ if _, err = hex.Decode(dst, src); err != nil {
+ return err
+ }
+ return
+}
+
+// decodeBraced decodes UUID string in format
+// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" or in format
+// "{6ba7b8109dad11d180b400c04fd430c8}".
+func (u *UUID) decodeBraced(t []byte) (err error) {
+ l := len(t)
+
+ if t[0] != '{' || t[l-1] != '}' {
+ return fmt.Errorf("uuid: incorrect UUID format %s", t)
+ }
+
+ return u.decodePlain(t[1 : l-1])
+}
+
+// decodeURN decodes UUID string in format
+// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in format
+// "urn:uuid:6ba7b8109dad11d180b400c04fd430c8".
+func (u *UUID) decodeURN(t []byte) (err error) {
+ total := len(t)
+
+ urn_uuid_prefix := t[:9]
+
+ if !bytes.Equal(urn_uuid_prefix, urnPrefix) {
+ return fmt.Errorf("uuid: incorrect UUID format: %s", t)
+ }
+
+ return u.decodePlain(t[9:total])
+}
+
+// decodePlain decodes UUID string in canonical format
+// "6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in hash-like format
+// "6ba7b8109dad11d180b400c04fd430c8".
+func (u *UUID) decodePlain(t []byte) (err error) {
+ switch len(t) {
+ case 32:
+ return u.decodeHashLike(t)
+ case 36:
+ return u.decodeCanonical(t)
+ default:
+ return fmt.Errorf("uuid: incorrrect UUID length: %s", t)
+ }
+}
+
+// MarshalBinary implements the encoding.BinaryMarshaler interface.
+func (u UUID) MarshalBinary() (data []byte, err error) {
+ data = u.Bytes()
+ return
+}
+
+// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
+// It will return error if the slice isn't 16 bytes long.
+func (u *UUID) UnmarshalBinary(data []byte) (err error) {
+ if len(data) != Size {
+ err = fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data))
+ return
+ }
+ copy(u[:], data)
+
+ return
+}
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/satori/go.uuid/generator.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/satori/go.uuid/generator.go
new file mode 100644
index 0000000..3f2f1da
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/satori/go.uuid/generator.go
@@ -0,0 +1,239 @@
+// Copyright (C) 2013-2018 by Maxim Bublis
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package uuid
+
+import (
+ "crypto/md5"
+ "crypto/rand"
+ "crypto/sha1"
+ "encoding/binary"
+ "hash"
+ "net"
+ "os"
+ "sync"
+ "time"
+)
+
+// Difference in 100-nanosecond intervals between
+// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970).
+const epochStart = 122192928000000000
+
+var (
+ global = newDefaultGenerator()
+
+ epochFunc = unixTimeFunc
+ posixUID = uint32(os.Getuid())
+ posixGID = uint32(os.Getgid())
+)
+
+// NewV1 returns UUID based on current timestamp and MAC address.
+func NewV1() UUID {
+ return global.NewV1()
+}
+
+// NewV2 returns DCE Security UUID based on POSIX UID/GID.
+func NewV2(domain byte) UUID {
+ return global.NewV2(domain)
+}
+
+// NewV3 returns UUID based on MD5 hash of namespace UUID and name.
+func NewV3(ns UUID, name string) UUID {
+ return global.NewV3(ns, name)
+}
+
+// NewV4 returns random generated UUID.
+func NewV4() UUID {
+ return global.NewV4()
+}
+
+// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name.
+func NewV5(ns UUID, name string) UUID {
+ return global.NewV5(ns, name)
+}
+
+// Generator provides interface for generating UUIDs.
+type Generator interface {
+ NewV1() UUID
+ NewV2(domain byte) UUID
+ NewV3(ns UUID, name string) UUID
+ NewV4() UUID
+ NewV5(ns UUID, name string) UUID
+}
+
+// Default generator implementation.
+type generator struct {
+ storageOnce sync.Once
+ storageMutex sync.Mutex
+
+ lastTime uint64
+ clockSequence uint16
+ hardwareAddr [6]byte
+}
+
+func newDefaultGenerator() Generator {
+ return &generator{}
+}
+
+// NewV1 returns UUID based on current timestamp and MAC address.
+func (g *generator) NewV1() UUID {
+ u := UUID{}
+
+ timeNow, clockSeq, hardwareAddr := g.getStorage()
+
+ binary.BigEndian.PutUint32(u[0:], uint32(timeNow))
+ binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32))
+ binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48))
+ binary.BigEndian.PutUint16(u[8:], clockSeq)
+
+ copy(u[10:], hardwareAddr)
+
+ u.SetVersion(V1)
+ u.SetVariant(VariantRFC4122)
+
+ return u
+}
+
+// NewV2 returns DCE Security UUID based on POSIX UID/GID.
+func (g *generator) NewV2(domain byte) UUID {
+ u := UUID{}
+
+ timeNow, clockSeq, hardwareAddr := g.getStorage()
+
+ switch domain {
+ case DomainPerson:
+ binary.BigEndian.PutUint32(u[0:], posixUID)
+ case DomainGroup:
+ binary.BigEndian.PutUint32(u[0:], posixGID)
+ }
+
+ binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32))
+ binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48))
+ binary.BigEndian.PutUint16(u[8:], clockSeq)
+ u[9] = domain
+
+ copy(u[10:], hardwareAddr)
+
+ u.SetVersion(V2)
+ u.SetVariant(VariantRFC4122)
+
+ return u
+}
+
+// NewV3 returns UUID based on MD5 hash of namespace UUID and name.
+func (g *generator) NewV3(ns UUID, name string) UUID {
+ u := newFromHash(md5.New(), ns, name)
+ u.SetVersion(V3)
+ u.SetVariant(VariantRFC4122)
+
+ return u
+}
+
+// NewV4 returns random generated UUID.
+func (g *generator) NewV4() UUID {
+ u := UUID{}
+ g.safeRandom(u[:])
+ u.SetVersion(V4)
+ u.SetVariant(VariantRFC4122)
+
+ return u
+}
+
+// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name.
+func (g *generator) NewV5(ns UUID, name string) UUID {
+ u := newFromHash(sha1.New(), ns, name)
+ u.SetVersion(V5)
+ u.SetVariant(VariantRFC4122)
+
+ return u
+}
+
+func (g *generator) initStorage() {
+ g.initClockSequence()
+ g.initHardwareAddr()
+}
+
+func (g *generator) initClockSequence() {
+ buf := make([]byte, 2)
+ g.safeRandom(buf)
+ g.clockSequence = binary.BigEndian.Uint16(buf)
+}
+
+func (g *generator) initHardwareAddr() {
+ interfaces, err := net.Interfaces()
+ if err == nil {
+ for _, iface := range interfaces {
+ if len(iface.HardwareAddr) >= 6 {
+ copy(g.hardwareAddr[:], iface.HardwareAddr)
+ return
+ }
+ }
+ }
+
+ // Initialize hardwareAddr randomly in case
+ // of real network interfaces absence
+ g.safeRandom(g.hardwareAddr[:])
+
+ // Set multicast bit as recommended in RFC 4122
+ g.hardwareAddr[0] |= 0x01
+}
+
+func (g *generator) safeRandom(dest []byte) {
+ if _, err := rand.Read(dest); err != nil {
+ panic(err)
+ }
+}
+
+// Returns UUID v1/v2 storage state.
+// Returns epoch timestamp, clock sequence, and hardware address.
+func (g *generator) getStorage() (uint64, uint16, []byte) {
+ g.storageOnce.Do(g.initStorage)
+
+ g.storageMutex.Lock()
+ defer g.storageMutex.Unlock()
+
+ timeNow := epochFunc()
+ // Clock changed backwards since last UUID generation.
+ // Should increase clock sequence.
+ if timeNow <= g.lastTime {
+ g.clockSequence++
+ }
+ g.lastTime = timeNow
+
+ return timeNow, g.clockSequence, g.hardwareAddr[:]
+}
+
+// Returns difference in 100-nanosecond intervals between
+// UUID epoch (October 15, 1582) and current time.
+// This is default epoch calculation function.
+func unixTimeFunc() uint64 {
+ return epochStart + uint64(time.Now().UnixNano()/100)
+}
+
+// Returns UUID based on hashing of namespace UUID and name.
+func newFromHash(h hash.Hash, ns UUID, name string) UUID {
+ u := UUID{}
+ h.Write(ns[:])
+ h.Write([]byte(name))
+ copy(u[:], h.Sum(nil))
+
+ return u
+}
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/satori/go.uuid/sql.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/satori/go.uuid/sql.go
new file mode 100644
index 0000000..56759d3
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/satori/go.uuid/sql.go
@@ -0,0 +1,78 @@
+// Copyright (C) 2013-2018 by Maxim Bublis
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package uuid
+
+import (
+ "database/sql/driver"
+ "fmt"
+)
+
+// Value implements the driver.Valuer interface.
+func (u UUID) Value() (driver.Value, error) {
+ return u.String(), nil
+}
+
+// Scan implements the sql.Scanner interface.
+// A 16-byte slice is handled by UnmarshalBinary, while
+// a longer byte slice or a string is handled by UnmarshalText.
+func (u *UUID) Scan(src interface{}) error {
+ switch src := src.(type) {
+ case []byte:
+ if len(src) == Size {
+ return u.UnmarshalBinary(src)
+ }
+ return u.UnmarshalText(src)
+
+ case string:
+ return u.UnmarshalText([]byte(src))
+ }
+
+ return fmt.Errorf("uuid: cannot convert %T to UUID", src)
+}
+
+// NullUUID can be used with the standard sql package to represent a
+// UUID value that can be NULL in the database
+type NullUUID struct {
+ UUID UUID
+ Valid bool
+}
+
+// Value implements the driver.Valuer interface.
+func (u NullUUID) Value() (driver.Value, error) {
+ if !u.Valid {
+ return nil, nil
+ }
+ // Delegate to UUID Value function
+ return u.UUID.Value()
+}
+
+// Scan implements the sql.Scanner interface.
+func (u *NullUUID) Scan(src interface{}) error {
+ if src == nil {
+ u.UUID, u.Valid = Nil, false
+ return nil
+ }
+
+ // Delegate to UUID Scan function
+ u.Valid = true
+ return u.UUID.Scan(src)
+}
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/satori/go.uuid/uuid.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/satori/go.uuid/uuid.go
new file mode 100644
index 0000000..a2b8e2c
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/satori/go.uuid/uuid.go
@@ -0,0 +1,161 @@
+// Copyright (C) 2013-2018 by Maxim Bublis
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// Package uuid provides implementation of Universally Unique Identifier (UUID).
+// Supported versions are 1, 3, 4 and 5 (as specified in RFC 4122) and
+// version 2 (as specified in DCE 1.1).
+package uuid
+
+import (
+ "bytes"
+ "encoding/hex"
+)
+
+// Size of a UUID in bytes.
+const Size = 16
+
+// UUID representation compliant with specification
+// described in RFC 4122.
+type UUID [Size]byte
+
+// UUID versions
+const (
+ _ byte = iota
+ V1
+ V2
+ V3
+ V4
+ V5
+)
+
+// UUID layout variants.
+const (
+ VariantNCS byte = iota
+ VariantRFC4122
+ VariantMicrosoft
+ VariantFuture
+)
+
+// UUID DCE domains.
+const (
+ DomainPerson = iota
+ DomainGroup
+ DomainOrg
+)
+
+// String parse helpers.
+var (
+ urnPrefix = []byte("urn:uuid:")
+ byteGroups = []int{8, 4, 4, 4, 12}
+)
+
+// Nil is special form of UUID that is specified to have all
+// 128 bits set to zero.
+var Nil = UUID{}
+
+// Predefined namespace UUIDs.
+var (
+ NamespaceDNS = Must(FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
+ NamespaceURL = Must(FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
+ NamespaceOID = Must(FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
+ NamespaceX500 = Must(FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
+)
+
+// Equal returns true if u1 and u2 equals, otherwise returns false.
+func Equal(u1 UUID, u2 UUID) bool {
+ return bytes.Equal(u1[:], u2[:])
+}
+
+// Version returns algorithm version used to generate UUID.
+func (u UUID) Version() byte {
+ return u[6] >> 4
+}
+
+// Variant returns UUID layout variant.
+func (u UUID) Variant() byte {
+ switch {
+ case (u[8] >> 7) == 0x00:
+ return VariantNCS
+ case (u[8] >> 6) == 0x02:
+ return VariantRFC4122
+ case (u[8] >> 5) == 0x06:
+ return VariantMicrosoft
+ case (u[8] >> 5) == 0x07:
+ fallthrough
+ default:
+ return VariantFuture
+ }
+}
+
+// Bytes returns bytes slice representation of UUID.
+func (u UUID) Bytes() []byte {
+ return u[:]
+}
+
+// Returns canonical string representation of UUID:
+// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
+func (u UUID) String() string {
+ buf := make([]byte, 36)
+
+ hex.Encode(buf[0:8], u[0:4])
+ buf[8] = '-'
+ hex.Encode(buf[9:13], u[4:6])
+ buf[13] = '-'
+ hex.Encode(buf[14:18], u[6:8])
+ buf[18] = '-'
+ hex.Encode(buf[19:23], u[8:10])
+ buf[23] = '-'
+ hex.Encode(buf[24:], u[10:])
+
+ return string(buf)
+}
+
+// SetVersion sets version bits.
+func (u *UUID) SetVersion(v byte) {
+ u[6] = (u[6] & 0x0f) | (v << 4)
+}
+
+// SetVariant sets variant bits.
+func (u *UUID) SetVariant(v byte) {
+ switch v {
+ case VariantNCS:
+ u[8] = (u[8]&(0xff>>1) | (0x00 << 7))
+ case VariantRFC4122:
+ u[8] = (u[8]&(0xff>>2) | (0x02 << 6))
+ case VariantMicrosoft:
+ u[8] = (u[8]&(0xff>>3) | (0x06 << 5))
+ case VariantFuture:
+ fallthrough
+ default:
+ u[8] = (u[8]&(0xff>>3) | (0x07 << 5))
+ }
+}
+
+// Must is a helper that wraps a call to a function returning (UUID, error)
+// and panics if the error is non-nil. It is intended for use in variable
+// initializations such as
+// var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000"));
+func Must(u UUID, err error) UUID {
+ if err != nil {
+ panic(err)
+ }
+ return u
+}
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/shopspring/decimal/LICENSE b/vendor/github.com/siddontang/go-mysql/vendor/github.com/shopspring/decimal/LICENSE
new file mode 100644
index 0000000..ad2148a
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/shopspring/decimal/LICENSE
@@ -0,0 +1,45 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Spring, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+- Based on https://github.com/oguzbilgic/fpd, which has the following license:
+"""
+The MIT License (MIT)
+
+Copyright (c) 2013 Oguz Bilgic
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+"""
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/shopspring/decimal/decimal-go.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/shopspring/decimal/decimal-go.go
new file mode 100644
index 0000000..e08a15c
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/shopspring/decimal/decimal-go.go
@@ -0,0 +1,414 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Multiprecision decimal numbers.
+// For floating-point formatting only; not general purpose.
+// Only operations are assign and (binary) left/right shift.
+// Can do binary floating point in multiprecision decimal precisely
+// because 2 divides 10; cannot do decimal floating point
+// in multiprecision binary precisely.
+package decimal
+
+type decimal struct {
+ d [800]byte // digits, big-endian representation
+ nd int // number of digits used
+ dp int // decimal point
+ neg bool // negative flag
+ trunc bool // discarded nonzero digits beyond d[:nd]
+}
+
+func (a *decimal) String() string {
+ n := 10 + a.nd
+ if a.dp > 0 {
+ n += a.dp
+ }
+ if a.dp < 0 {
+ n += -a.dp
+ }
+
+ buf := make([]byte, n)
+ w := 0
+ switch {
+ case a.nd == 0:
+ return "0"
+
+ case a.dp <= 0:
+ // zeros fill space between decimal point and digits
+ buf[w] = '0'
+ w++
+ buf[w] = '.'
+ w++
+ w += digitZero(buf[w : w+-a.dp])
+ w += copy(buf[w:], a.d[0:a.nd])
+
+ case a.dp < a.nd:
+ // decimal point in middle of digits
+ w += copy(buf[w:], a.d[0:a.dp])
+ buf[w] = '.'
+ w++
+ w += copy(buf[w:], a.d[a.dp:a.nd])
+
+ default:
+ // zeros fill space between digits and decimal point
+ w += copy(buf[w:], a.d[0:a.nd])
+ w += digitZero(buf[w : w+a.dp-a.nd])
+ }
+ return string(buf[0:w])
+}
+
+func digitZero(dst []byte) int {
+ for i := range dst {
+ dst[i] = '0'
+ }
+ return len(dst)
+}
+
+// trim trailing zeros from number.
+// (They are meaningless; the decimal point is tracked
+// independent of the number of digits.)
+func trim(a *decimal) {
+ for a.nd > 0 && a.d[a.nd-1] == '0' {
+ a.nd--
+ }
+ if a.nd == 0 {
+ a.dp = 0
+ }
+}
+
+// Assign v to a.
+func (a *decimal) Assign(v uint64) {
+ var buf [24]byte
+
+ // Write reversed decimal in buf.
+ n := 0
+ for v > 0 {
+ v1 := v / 10
+ v -= 10 * v1
+ buf[n] = byte(v + '0')
+ n++
+ v = v1
+ }
+
+ // Reverse again to produce forward decimal in a.d.
+ a.nd = 0
+ for n--; n >= 0; n-- {
+ a.d[a.nd] = buf[n]
+ a.nd++
+ }
+ a.dp = a.nd
+ trim(a)
+}
+
+// Maximum shift that we can do in one pass without overflow.
+// A uint has 32 or 64 bits, and we have to be able to accommodate 9<> 63)
+const maxShift = uintSize - 4
+
+// Binary shift right (/ 2) by k bits. k <= maxShift to avoid overflow.
+func rightShift(a *decimal, k uint) {
+ r := 0 // read pointer
+ w := 0 // write pointer
+
+ // Pick up enough leading digits to cover first shift.
+ var n uint
+ for ; n>>k == 0; r++ {
+ if r >= a.nd {
+ if n == 0 {
+ // a == 0; shouldn't get here, but handle anyway.
+ a.nd = 0
+ return
+ }
+ for n>>k == 0 {
+ n = n * 10
+ r++
+ }
+ break
+ }
+ c := uint(a.d[r])
+ n = n*10 + c - '0'
+ }
+ a.dp -= r - 1
+
+ var mask uint = (1 << k) - 1
+
+ // Pick up a digit, put down a digit.
+ for ; r < a.nd; r++ {
+ c := uint(a.d[r])
+ dig := n >> k
+ n &= mask
+ a.d[w] = byte(dig + '0')
+ w++
+ n = n*10 + c - '0'
+ }
+
+ // Put down extra digits.
+ for n > 0 {
+ dig := n >> k
+ n &= mask
+ if w < len(a.d) {
+ a.d[w] = byte(dig + '0')
+ w++
+ } else if dig > 0 {
+ a.trunc = true
+ }
+ n = n * 10
+ }
+
+ a.nd = w
+ trim(a)
+}
+
+// Cheat sheet for left shift: table indexed by shift count giving
+// number of new digits that will be introduced by that shift.
+//
+// For example, leftcheats[4] = {2, "625"}. That means that
+// if we are shifting by 4 (multiplying by 16), it will add 2 digits
+// when the string prefix is "625" through "999", and one fewer digit
+// if the string prefix is "000" through "624".
+//
+// Credit for this trick goes to Ken.
+
+type leftCheat struct {
+ delta int // number of new digits
+ cutoff string // minus one digit if original < a.
+}
+
+var leftcheats = []leftCheat{
+ // Leading digits of 1/2^i = 5^i.
+ // 5^23 is not an exact 64-bit floating point number,
+ // so have to use bc for the math.
+ // Go up to 60 to be large enough for 32bit and 64bit platforms.
+ /*
+ seq 60 | sed 's/^/5^/' | bc |
+ awk 'BEGIN{ print "\t{ 0, \"\" }," }
+ {
+ log2 = log(2)/log(10)
+ printf("\t{ %d, \"%s\" },\t// * %d\n",
+ int(log2*NR+1), $0, 2**NR)
+ }'
+ */
+ {0, ""},
+ {1, "5"}, // * 2
+ {1, "25"}, // * 4
+ {1, "125"}, // * 8
+ {2, "625"}, // * 16
+ {2, "3125"}, // * 32
+ {2, "15625"}, // * 64
+ {3, "78125"}, // * 128
+ {3, "390625"}, // * 256
+ {3, "1953125"}, // * 512
+ {4, "9765625"}, // * 1024
+ {4, "48828125"}, // * 2048
+ {4, "244140625"}, // * 4096
+ {4, "1220703125"}, // * 8192
+ {5, "6103515625"}, // * 16384
+ {5, "30517578125"}, // * 32768
+ {5, "152587890625"}, // * 65536
+ {6, "762939453125"}, // * 131072
+ {6, "3814697265625"}, // * 262144
+ {6, "19073486328125"}, // * 524288
+ {7, "95367431640625"}, // * 1048576
+ {7, "476837158203125"}, // * 2097152
+ {7, "2384185791015625"}, // * 4194304
+ {7, "11920928955078125"}, // * 8388608
+ {8, "59604644775390625"}, // * 16777216
+ {8, "298023223876953125"}, // * 33554432
+ {8, "1490116119384765625"}, // * 67108864
+ {9, "7450580596923828125"}, // * 134217728
+ {9, "37252902984619140625"}, // * 268435456
+ {9, "186264514923095703125"}, // * 536870912
+ {10, "931322574615478515625"}, // * 1073741824
+ {10, "4656612873077392578125"}, // * 2147483648
+ {10, "23283064365386962890625"}, // * 4294967296
+ {10, "116415321826934814453125"}, // * 8589934592
+ {11, "582076609134674072265625"}, // * 17179869184
+ {11, "2910383045673370361328125"}, // * 34359738368
+ {11, "14551915228366851806640625"}, // * 68719476736
+ {12, "72759576141834259033203125"}, // * 137438953472
+ {12, "363797880709171295166015625"}, // * 274877906944
+ {12, "1818989403545856475830078125"}, // * 549755813888
+ {13, "9094947017729282379150390625"}, // * 1099511627776
+ {13, "45474735088646411895751953125"}, // * 2199023255552
+ {13, "227373675443232059478759765625"}, // * 4398046511104
+ {13, "1136868377216160297393798828125"}, // * 8796093022208
+ {14, "5684341886080801486968994140625"}, // * 17592186044416
+ {14, "28421709430404007434844970703125"}, // * 35184372088832
+ {14, "142108547152020037174224853515625"}, // * 70368744177664
+ {15, "710542735760100185871124267578125"}, // * 140737488355328
+ {15, "3552713678800500929355621337890625"}, // * 281474976710656
+ {15, "17763568394002504646778106689453125"}, // * 562949953421312
+ {16, "88817841970012523233890533447265625"}, // * 1125899906842624
+ {16, "444089209850062616169452667236328125"}, // * 2251799813685248
+ {16, "2220446049250313080847263336181640625"}, // * 4503599627370496
+ {16, "11102230246251565404236316680908203125"}, // * 9007199254740992
+ {17, "55511151231257827021181583404541015625"}, // * 18014398509481984
+ {17, "277555756156289135105907917022705078125"}, // * 36028797018963968
+ {17, "1387778780781445675529539585113525390625"}, // * 72057594037927936
+ {18, "6938893903907228377647697925567626953125"}, // * 144115188075855872
+ {18, "34694469519536141888238489627838134765625"}, // * 288230376151711744
+ {18, "173472347597680709441192448139190673828125"}, // * 576460752303423488
+ {19, "867361737988403547205962240695953369140625"}, // * 1152921504606846976
+}
+
+// Is the leading prefix of b lexicographically less than s?
+func prefixIsLessThan(b []byte, s string) bool {
+ for i := 0; i < len(s); i++ {
+ if i >= len(b) {
+ return true
+ }
+ if b[i] != s[i] {
+ return b[i] < s[i]
+ }
+ }
+ return false
+}
+
+// Binary shift left (* 2) by k bits. k <= maxShift to avoid overflow.
+func leftShift(a *decimal, k uint) {
+ delta := leftcheats[k].delta
+ if prefixIsLessThan(a.d[0:a.nd], leftcheats[k].cutoff) {
+ delta--
+ }
+
+ r := a.nd // read index
+ w := a.nd + delta // write index
+
+ // Pick up a digit, put down a digit.
+ var n uint
+ for r--; r >= 0; r-- {
+ n += (uint(a.d[r]) - '0') << k
+ quo := n / 10
+ rem := n - 10*quo
+ w--
+ if w < len(a.d) {
+ a.d[w] = byte(rem + '0')
+ } else if rem != 0 {
+ a.trunc = true
+ }
+ n = quo
+ }
+
+ // Put down extra digits.
+ for n > 0 {
+ quo := n / 10
+ rem := n - 10*quo
+ w--
+ if w < len(a.d) {
+ a.d[w] = byte(rem + '0')
+ } else if rem != 0 {
+ a.trunc = true
+ }
+ n = quo
+ }
+
+ a.nd += delta
+ if a.nd >= len(a.d) {
+ a.nd = len(a.d)
+ }
+ a.dp += delta
+ trim(a)
+}
+
+// Binary shift left (k > 0) or right (k < 0).
+func (a *decimal) Shift(k int) {
+ switch {
+ case a.nd == 0:
+ // nothing to do: a == 0
+ case k > 0:
+ for k > maxShift {
+ leftShift(a, maxShift)
+ k -= maxShift
+ }
+ leftShift(a, uint(k))
+ case k < 0:
+ for k < -maxShift {
+ rightShift(a, maxShift)
+ k += maxShift
+ }
+ rightShift(a, uint(-k))
+ }
+}
+
+// If we chop a at nd digits, should we round up?
+func shouldRoundUp(a *decimal, nd int) bool {
+ if nd < 0 || nd >= a.nd {
+ return false
+ }
+ if a.d[nd] == '5' && nd+1 == a.nd { // exactly halfway - round to even
+ // if we truncated, a little higher than what's recorded - always round up
+ if a.trunc {
+ return true
+ }
+ return nd > 0 && (a.d[nd-1]-'0')%2 != 0
+ }
+ // not halfway - digit tells all
+ return a.d[nd] >= '5'
+}
+
+// Round a to nd digits (or fewer).
+// If nd is zero, it means we're rounding
+// just to the left of the digits, as in
+// 0.09 -> 0.1.
+func (a *decimal) Round(nd int) {
+ if nd < 0 || nd >= a.nd {
+ return
+ }
+ if shouldRoundUp(a, nd) {
+ a.RoundUp(nd)
+ } else {
+ a.RoundDown(nd)
+ }
+}
+
+// Round a down to nd digits (or fewer).
+func (a *decimal) RoundDown(nd int) {
+ if nd < 0 || nd >= a.nd {
+ return
+ }
+ a.nd = nd
+ trim(a)
+}
+
+// Round a up to nd digits (or fewer).
+func (a *decimal) RoundUp(nd int) {
+ if nd < 0 || nd >= a.nd {
+ return
+ }
+
+ // round up
+ for i := nd - 1; i >= 0; i-- {
+ c := a.d[i]
+ if c < '9' { // can stop after this digit
+ a.d[i]++
+ a.nd = i + 1
+ return
+ }
+ }
+
+ // Number is all 9s.
+ // Change to single 1 with adjusted decimal point.
+ a.d[0] = '1'
+ a.nd = 1
+ a.dp++
+}
+
+// Extract integer part, rounded appropriately.
+// No guarantees about overflow.
+func (a *decimal) RoundedInteger() uint64 {
+ if a.dp > 20 {
+ return 0xFFFFFFFFFFFFFFFF
+ }
+ var i int
+ n := uint64(0)
+ for i = 0; i < a.dp && i < a.nd; i++ {
+ n = n*10 + uint64(a.d[i]-'0')
+ }
+ for ; i < a.dp; i++ {
+ n *= 10
+ }
+ if shouldRoundUp(a, a.dp) {
+ n++
+ }
+ return n
+}
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/shopspring/decimal/decimal.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/shopspring/decimal/decimal.go
new file mode 100644
index 0000000..134ece2
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/shopspring/decimal/decimal.go
@@ -0,0 +1,1434 @@
+// Package decimal implements an arbitrary precision fixed-point decimal.
+//
+// To use as part of a struct:
+//
+// type Struct struct {
+// Number Decimal
+// }
+//
+// The zero-value of a Decimal is 0, as you would expect.
+//
+// The best way to create a new Decimal is to use decimal.NewFromString, ex:
+//
+// n, err := decimal.NewFromString("-123.4567")
+// n.String() // output: "-123.4567"
+//
+// NOTE: This can "only" represent numbers with a maximum of 2^31 digits
+// after the decimal point.
+package decimal
+
+import (
+ "database/sql/driver"
+ "encoding/binary"
+ "fmt"
+ "math"
+ "math/big"
+ "strconv"
+ "strings"
+)
+
+// DivisionPrecision is the number of decimal places in the result when it
+// doesn't divide exactly.
+//
+// Example:
+//
+// d1 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(3)
+// d1.String() // output: "0.6666666666666667"
+// d2 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(30000)
+// d2.String() // output: "0.0000666666666667"
+// d3 := decimal.NewFromFloat(20000).Div(decimal.NewFromFloat(3)
+// d3.String() // output: "6666.6666666666666667"
+// decimal.DivisionPrecision = 3
+// d4 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(3)
+// d4.String() // output: "0.667"
+//
+var DivisionPrecision = 16
+
+// MarshalJSONWithoutQuotes should be set to true if you want the decimal to
+// be JSON marshaled as a number, instead of as a string.
+// WARNING: this is dangerous for decimals with many digits, since many JSON
+// unmarshallers (ex: Javascript's) will unmarshal JSON numbers to IEEE 754
+// double-precision floating point numbers, which means you can potentially
+// silently lose precision.
+var MarshalJSONWithoutQuotes = false
+
+// Zero constant, to make computations faster.
+var Zero = New(0, 1)
+
+// fiveDec used in Cash Rounding
+var fiveDec = New(5, 0)
+
+var zeroInt = big.NewInt(0)
+var oneInt = big.NewInt(1)
+var twoInt = big.NewInt(2)
+var fourInt = big.NewInt(4)
+var fiveInt = big.NewInt(5)
+var tenInt = big.NewInt(10)
+var twentyInt = big.NewInt(20)
+
+// Decimal represents a fixed-point decimal. It is immutable.
+// number = value * 10 ^ exp
+type Decimal struct {
+ value *big.Int
+
+ // NOTE(vadim): this must be an int32, because we cast it to float64 during
+ // calculations. If exp is 64 bit, we might lose precision.
+ // If we cared about being able to represent every possible decimal, we
+ // could make exp a *big.Int but it would hurt performance and numbers
+ // like that are unrealistic.
+ exp int32
+}
+
+// New returns a new fixed-point decimal, value * 10 ^ exp.
+func New(value int64, exp int32) Decimal {
+ return Decimal{
+ value: big.NewInt(value),
+ exp: exp,
+ }
+}
+
+// NewFromBigInt returns a new Decimal from a big.Int, value * 10 ^ exp
+func NewFromBigInt(value *big.Int, exp int32) Decimal {
+ return Decimal{
+ value: big.NewInt(0).Set(value),
+ exp: exp,
+ }
+}
+
+// NewFromString returns a new Decimal from a string representation.
+//
+// Example:
+//
+// d, err := NewFromString("-123.45")
+// d2, err := NewFromString(".0001")
+//
+func NewFromString(value string) (Decimal, error) {
+ originalInput := value
+ var intString string
+ var exp int64
+
+ // Check if number is using scientific notation
+ eIndex := strings.IndexAny(value, "Ee")
+ if eIndex != -1 {
+ expInt, err := strconv.ParseInt(value[eIndex+1:], 10, 32)
+ if err != nil {
+ if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
+ return Decimal{}, fmt.Errorf("can't convert %s to decimal: fractional part too long", value)
+ }
+ return Decimal{}, fmt.Errorf("can't convert %s to decimal: exponent is not numeric", value)
+ }
+ value = value[:eIndex]
+ exp = expInt
+ }
+
+ parts := strings.Split(value, ".")
+ if len(parts) == 1 {
+ // There is no decimal point, we can just parse the original string as
+ // an int
+ intString = value
+ } else if len(parts) == 2 {
+ // strip the insignificant digits for more accurate comparisons.
+ decimalPart := strings.TrimRight(parts[1], "0")
+ intString = parts[0] + decimalPart
+ expInt := -len(decimalPart)
+ exp += int64(expInt)
+ } else {
+ return Decimal{}, fmt.Errorf("can't convert %s to decimal: too many .s", value)
+ }
+
+ dValue := new(big.Int)
+ _, ok := dValue.SetString(intString, 10)
+ if !ok {
+ return Decimal{}, fmt.Errorf("can't convert %s to decimal", value)
+ }
+
+ if exp < math.MinInt32 || exp > math.MaxInt32 {
+ // NOTE(vadim): I doubt a string could realistically be this long
+ return Decimal{}, fmt.Errorf("can't convert %s to decimal: fractional part too long", originalInput)
+ }
+
+ return Decimal{
+ value: dValue,
+ exp: int32(exp),
+ }, nil
+}
+
+// RequireFromString returns a new Decimal from a string representation
+// or panics if NewFromString would have returned an error.
+//
+// Example:
+//
+// d := RequireFromString("-123.45")
+// d2 := RequireFromString(".0001")
+//
+func RequireFromString(value string) Decimal {
+ dec, err := NewFromString(value)
+ if err != nil {
+ panic(err)
+ }
+ return dec
+}
+
+// NewFromFloat converts a float64 to Decimal.
+//
+// The converted number will contain the number of significant digits that can be
+// represented in a float with reliable roundtrip.
+// This is typically 15 digits, but may be more in some cases.
+// See https://www.exploringbinary.com/decimal-precision-of-binary-floating-point-numbers/ for more information.
+//
+// For slightly faster conversion, use NewFromFloatWithExponent where you can specify the precision in absolute terms.
+//
+// NOTE: this will panic on NaN, +/-inf
+func NewFromFloat(value float64) Decimal {
+ if value == 0 {
+ return New(0, 0)
+ }
+ return newFromFloat(value, math.Float64bits(value), &float64info)
+}
+
+// NewFromFloat converts a float32 to Decimal.
+//
+// The converted number will contain the number of significant digits that can be
+// represented in a float with reliable roundtrip.
+// This is typically 6-8 digits depending on the input.
+// See https://www.exploringbinary.com/decimal-precision-of-binary-floating-point-numbers/ for more information.
+//
+// For slightly faster conversion, use NewFromFloatWithExponent where you can specify the precision in absolute terms.
+//
+// NOTE: this will panic on NaN, +/-inf
+func NewFromFloat32(value float32) Decimal {
+ if value == 0 {
+ return New(0, 0)
+ }
+ // XOR is workaround for https://github.com/golang/go/issues/26285
+ a := math.Float32bits(value) ^ 0x80808080
+ return newFromFloat(float64(value), uint64(a)^0x80808080, &float32info)
+}
+
+func newFromFloat(val float64, bits uint64, flt *floatInfo) Decimal {
+ if math.IsNaN(val) || math.IsInf(val, 0) {
+ panic(fmt.Sprintf("Cannot create a Decimal from %v", val))
+ }
+ exp := int(bits>>flt.mantbits) & (1<>(flt.expbits+flt.mantbits) != 0
+
+ roundShortest(&d, mant, exp, flt)
+ // If less than 19 digits, we can do calculation in an int64.
+ if d.nd < 19 {
+ tmp := int64(0)
+ m := int64(1)
+ for i := d.nd - 1; i >= 0; i-- {
+ tmp += m * int64(d.d[i]-'0')
+ m *= 10
+ }
+ if d.neg {
+ tmp *= -1
+ }
+ return Decimal{value: big.NewInt(tmp), exp: int32(d.dp) - int32(d.nd)}
+ }
+ dValue := new(big.Int)
+ dValue, ok := dValue.SetString(string(d.d[:d.nd]), 10)
+ if ok {
+ return Decimal{value: dValue, exp: int32(d.dp) - int32(d.nd)}
+ }
+
+ return NewFromFloatWithExponent(val, int32(d.dp)-int32(d.nd))
+}
+
+// NewFromFloatWithExponent converts a float64 to Decimal, with an arbitrary
+// number of fractional digits.
+//
+// Example:
+//
+// NewFromFloatWithExponent(123.456, -2).String() // output: "123.46"
+//
+func NewFromFloatWithExponent(value float64, exp int32) Decimal {
+ if math.IsNaN(value) || math.IsInf(value, 0) {
+ panic(fmt.Sprintf("Cannot create a Decimal from %v", value))
+ }
+
+ bits := math.Float64bits(value)
+ mant := bits & (1<<52 - 1)
+ exp2 := int32((bits >> 52) & (1<<11 - 1))
+ sign := bits >> 63
+
+ if exp2 == 0 {
+ // specials
+ if mant == 0 {
+ return Decimal{}
+ } else {
+ // subnormal
+ exp2++
+ }
+ } else {
+ // normal
+ mant |= 1 << 52
+ }
+
+ exp2 -= 1023 + 52
+
+ // normalizing base-2 values
+ for mant&1 == 0 {
+ mant = mant >> 1
+ exp2++
+ }
+
+ // maximum number of fractional base-10 digits to represent 2^N exactly cannot be more than -N if N<0
+ if exp < 0 && exp < exp2 {
+ if exp2 < 0 {
+ exp = exp2
+ } else {
+ exp = 0
+ }
+ }
+
+ // representing 10^M * 2^N as 5^M * 2^(M+N)
+ exp2 -= exp
+
+ temp := big.NewInt(1)
+ dMant := big.NewInt(int64(mant))
+
+ // applying 5^M
+ if exp > 0 {
+ temp = temp.SetInt64(int64(exp))
+ temp = temp.Exp(fiveInt, temp, nil)
+ } else if exp < 0 {
+ temp = temp.SetInt64(-int64(exp))
+ temp = temp.Exp(fiveInt, temp, nil)
+ dMant = dMant.Mul(dMant, temp)
+ temp = temp.SetUint64(1)
+ }
+
+ // applying 2^(M+N)
+ if exp2 > 0 {
+ dMant = dMant.Lsh(dMant, uint(exp2))
+ } else if exp2 < 0 {
+ temp = temp.Lsh(temp, uint(-exp2))
+ }
+
+ // rounding and downscaling
+ if exp > 0 || exp2 < 0 {
+ halfDown := new(big.Int).Rsh(temp, 1)
+ dMant = dMant.Add(dMant, halfDown)
+ dMant = dMant.Quo(dMant, temp)
+ }
+
+ if sign == 1 {
+ dMant = dMant.Neg(dMant)
+ }
+
+ return Decimal{
+ value: dMant,
+ exp: exp,
+ }
+}
+
+// rescale returns a rescaled version of the decimal. Returned
+// decimal may be less precise if the given exponent is bigger
+// than the initial exponent of the Decimal.
+// NOTE: this will truncate, NOT round
+//
+// Example:
+//
+// d := New(12345, -4)
+// d2 := d.rescale(-1)
+// d3 := d2.rescale(-4)
+// println(d1)
+// println(d2)
+// println(d3)
+//
+// Output:
+//
+// 1.2345
+// 1.2
+// 1.2000
+//
+func (d Decimal) rescale(exp int32) Decimal {
+ d.ensureInitialized()
+ // NOTE(vadim): must convert exps to float64 before - to prevent overflow
+ diff := math.Abs(float64(exp) - float64(d.exp))
+ value := new(big.Int).Set(d.value)
+
+ expScale := new(big.Int).Exp(tenInt, big.NewInt(int64(diff)), nil)
+ if exp > d.exp {
+ value = value.Quo(value, expScale)
+ } else if exp < d.exp {
+ value = value.Mul(value, expScale)
+ }
+
+ return Decimal{
+ value: value,
+ exp: exp,
+ }
+}
+
+// Abs returns the absolute value of the decimal.
+func (d Decimal) Abs() Decimal {
+ d.ensureInitialized()
+ d2Value := new(big.Int).Abs(d.value)
+ return Decimal{
+ value: d2Value,
+ exp: d.exp,
+ }
+}
+
+// Add returns d + d2.
+func (d Decimal) Add(d2 Decimal) Decimal {
+ baseScale := min(d.exp, d2.exp)
+ rd := d.rescale(baseScale)
+ rd2 := d2.rescale(baseScale)
+
+ d3Value := new(big.Int).Add(rd.value, rd2.value)
+ return Decimal{
+ value: d3Value,
+ exp: baseScale,
+ }
+}
+
+// Sub returns d - d2.
+func (d Decimal) Sub(d2 Decimal) Decimal {
+ baseScale := min(d.exp, d2.exp)
+ rd := d.rescale(baseScale)
+ rd2 := d2.rescale(baseScale)
+
+ d3Value := new(big.Int).Sub(rd.value, rd2.value)
+ return Decimal{
+ value: d3Value,
+ exp: baseScale,
+ }
+}
+
+// Neg returns -d.
+func (d Decimal) Neg() Decimal {
+ d.ensureInitialized()
+ val := new(big.Int).Neg(d.value)
+ return Decimal{
+ value: val,
+ exp: d.exp,
+ }
+}
+
+// Mul returns d * d2.
+func (d Decimal) Mul(d2 Decimal) Decimal {
+ d.ensureInitialized()
+ d2.ensureInitialized()
+
+ expInt64 := int64(d.exp) + int64(d2.exp)
+ if expInt64 > math.MaxInt32 || expInt64 < math.MinInt32 {
+ // NOTE(vadim): better to panic than give incorrect results, as
+ // Decimals are usually used for money
+ panic(fmt.Sprintf("exponent %v overflows an int32!", expInt64))
+ }
+
+ d3Value := new(big.Int).Mul(d.value, d2.value)
+ return Decimal{
+ value: d3Value,
+ exp: int32(expInt64),
+ }
+}
+
+// Shift shifts the decimal in base 10.
+// It shifts left when shift is positive and right if shift is negative.
+// In simpler terms, the given value for shift is added to the exponent
+// of the decimal.
+func (d Decimal) Shift(shift int32) Decimal {
+ d.ensureInitialized()
+ return Decimal{
+ value: new(big.Int).Set(d.value),
+ exp: d.exp + shift,
+ }
+}
+
+// Div returns d / d2. If it doesn't divide exactly, the result will have
+// DivisionPrecision digits after the decimal point.
+func (d Decimal) Div(d2 Decimal) Decimal {
+ return d.DivRound(d2, int32(DivisionPrecision))
+}
+
+// QuoRem does divsion with remainder
+// d.QuoRem(d2,precision) returns quotient q and remainder r such that
+// d = d2 * q + r, q an integer multiple of 10^(-precision)
+// 0 <= r < abs(d2) * 10 ^(-precision) if d>=0
+// 0 >= r > -abs(d2) * 10 ^(-precision) if d<0
+// Note that precision<0 is allowed as input.
+func (d Decimal) QuoRem(d2 Decimal, precision int32) (Decimal, Decimal) {
+ d.ensureInitialized()
+ d2.ensureInitialized()
+ if d2.value.Sign() == 0 {
+ panic("decimal division by 0")
+ }
+ scale := -precision
+ e := int64(d.exp - d2.exp - scale)
+ if e > math.MaxInt32 || e < math.MinInt32 {
+ panic("overflow in decimal QuoRem")
+ }
+ var aa, bb, expo big.Int
+ var scalerest int32
+ // d = a 10^ea
+ // d2 = b 10^eb
+ if e < 0 {
+ aa = *d.value
+ expo.SetInt64(-e)
+ bb.Exp(tenInt, &expo, nil)
+ bb.Mul(d2.value, &bb)
+ scalerest = d.exp
+ // now aa = a
+ // bb = b 10^(scale + eb - ea)
+ } else {
+ expo.SetInt64(e)
+ aa.Exp(tenInt, &expo, nil)
+ aa.Mul(d.value, &aa)
+ bb = *d2.value
+ scalerest = scale + d2.exp
+ // now aa = a ^ (ea - eb - scale)
+ // bb = b
+ }
+ var q, r big.Int
+ q.QuoRem(&aa, &bb, &r)
+ dq := Decimal{value: &q, exp: scale}
+ dr := Decimal{value: &r, exp: scalerest}
+ return dq, dr
+}
+
+// DivRound divides and rounds to a given precision
+// i.e. to an integer multiple of 10^(-precision)
+// for a positive quotient digit 5 is rounded up, away from 0
+// if the quotient is negative then digit 5 is rounded down, away from 0
+// Note that precision<0 is allowed as input.
+func (d Decimal) DivRound(d2 Decimal, precision int32) Decimal {
+ // QuoRem already checks initialization
+ q, r := d.QuoRem(d2, precision)
+ // the actual rounding decision is based on comparing r*10^precision and d2/2
+ // instead compare 2 r 10 ^precision and d2
+ var rv2 big.Int
+ rv2.Abs(r.value)
+ rv2.Lsh(&rv2, 1)
+ // now rv2 = abs(r.value) * 2
+ r2 := Decimal{value: &rv2, exp: r.exp + precision}
+ // r2 is now 2 * r * 10 ^ precision
+ var c = r2.Cmp(d2.Abs())
+
+ if c < 0 {
+ return q
+ }
+
+ if d.value.Sign()*d2.value.Sign() < 0 {
+ return q.Sub(New(1, -precision))
+ }
+
+ return q.Add(New(1, -precision))
+}
+
+// Mod returns d % d2.
+func (d Decimal) Mod(d2 Decimal) Decimal {
+ quo := d.Div(d2).Truncate(0)
+ return d.Sub(d2.Mul(quo))
+}
+
+// Pow returns d to the power d2
+func (d Decimal) Pow(d2 Decimal) Decimal {
+ var temp Decimal
+ if d2.IntPart() == 0 {
+ return NewFromFloat(1)
+ }
+ temp = d.Pow(d2.Div(NewFromFloat(2)))
+ if d2.IntPart()%2 == 0 {
+ return temp.Mul(temp)
+ }
+ if d2.IntPart() > 0 {
+ return temp.Mul(temp).Mul(d)
+ }
+ return temp.Mul(temp).Div(d)
+}
+
+// Cmp compares the numbers represented by d and d2 and returns:
+//
+// -1 if d < d2
+// 0 if d == d2
+// +1 if d > d2
+//
+func (d Decimal) Cmp(d2 Decimal) int {
+ d.ensureInitialized()
+ d2.ensureInitialized()
+
+ if d.exp == d2.exp {
+ return d.value.Cmp(d2.value)
+ }
+
+ baseExp := min(d.exp, d2.exp)
+ rd := d.rescale(baseExp)
+ rd2 := d2.rescale(baseExp)
+
+ return rd.value.Cmp(rd2.value)
+}
+
+// Equal returns whether the numbers represented by d and d2 are equal.
+func (d Decimal) Equal(d2 Decimal) bool {
+ return d.Cmp(d2) == 0
+}
+
+// Equals is deprecated, please use Equal method instead
+func (d Decimal) Equals(d2 Decimal) bool {
+ return d.Equal(d2)
+}
+
+// GreaterThan (GT) returns true when d is greater than d2.
+func (d Decimal) GreaterThan(d2 Decimal) bool {
+ return d.Cmp(d2) == 1
+}
+
+// GreaterThanOrEqual (GTE) returns true when d is greater than or equal to d2.
+func (d Decimal) GreaterThanOrEqual(d2 Decimal) bool {
+ cmp := d.Cmp(d2)
+ return cmp == 1 || cmp == 0
+}
+
+// LessThan (LT) returns true when d is less than d2.
+func (d Decimal) LessThan(d2 Decimal) bool {
+ return d.Cmp(d2) == -1
+}
+
+// LessThanOrEqual (LTE) returns true when d is less than or equal to d2.
+func (d Decimal) LessThanOrEqual(d2 Decimal) bool {
+ cmp := d.Cmp(d2)
+ return cmp == -1 || cmp == 0
+}
+
+// Sign returns:
+//
+// -1 if d < 0
+// 0 if d == 0
+// +1 if d > 0
+//
+func (d Decimal) Sign() int {
+ if d.value == nil {
+ return 0
+ }
+ return d.value.Sign()
+}
+
+// IsPositive return
+//
+// true if d > 0
+// false if d == 0
+// false if d < 0
+func (d Decimal) IsPositive() bool {
+ return d.Sign() == 1
+}
+
+// IsNegative return
+//
+// true if d < 0
+// false if d == 0
+// false if d > 0
+func (d Decimal) IsNegative() bool {
+ return d.Sign() == -1
+}
+
+// IsZero return
+//
+// true if d == 0
+// false if d > 0
+// false if d < 0
+func (d Decimal) IsZero() bool {
+ return d.Sign() == 0
+}
+
+// Exponent returns the exponent, or scale component of the decimal.
+func (d Decimal) Exponent() int32 {
+ return d.exp
+}
+
+// Coefficient returns the coefficient of the decimal. It is scaled by 10^Exponent()
+func (d Decimal) Coefficient() *big.Int {
+ // we copy the coefficient so that mutating the result does not mutate the
+ // Decimal.
+ return big.NewInt(0).Set(d.value)
+}
+
+// IntPart returns the integer component of the decimal.
+func (d Decimal) IntPart() int64 {
+ scaledD := d.rescale(0)
+ return scaledD.value.Int64()
+}
+
+// Rat returns a rational number representation of the decimal.
+func (d Decimal) Rat() *big.Rat {
+ d.ensureInitialized()
+ if d.exp <= 0 {
+ // NOTE(vadim): must negate after casting to prevent int32 overflow
+ denom := new(big.Int).Exp(tenInt, big.NewInt(-int64(d.exp)), nil)
+ return new(big.Rat).SetFrac(d.value, denom)
+ }
+
+ mul := new(big.Int).Exp(tenInt, big.NewInt(int64(d.exp)), nil)
+ num := new(big.Int).Mul(d.value, mul)
+ return new(big.Rat).SetFrac(num, oneInt)
+}
+
+// Float64 returns the nearest float64 value for d and a bool indicating
+// whether f represents d exactly.
+// For more details, see the documentation for big.Rat.Float64
+func (d Decimal) Float64() (f float64, exact bool) {
+ return d.Rat().Float64()
+}
+
+// String returns the string representation of the decimal
+// with the fixed point.
+//
+// Example:
+//
+// d := New(-12345, -3)
+// println(d.String())
+//
+// Output:
+//
+// -12.345
+//
+func (d Decimal) String() string {
+ return d.string(true)
+}
+
+// StringFixed returns a rounded fixed-point string with places digits after
+// the decimal point.
+//
+// Example:
+//
+// NewFromFloat(0).StringFixed(2) // output: "0.00"
+// NewFromFloat(0).StringFixed(0) // output: "0"
+// NewFromFloat(5.45).StringFixed(0) // output: "5"
+// NewFromFloat(5.45).StringFixed(1) // output: "5.5"
+// NewFromFloat(5.45).StringFixed(2) // output: "5.45"
+// NewFromFloat(5.45).StringFixed(3) // output: "5.450"
+// NewFromFloat(545).StringFixed(-1) // output: "550"
+//
+func (d Decimal) StringFixed(places int32) string {
+ rounded := d.Round(places)
+ return rounded.string(false)
+}
+
+// StringFixedBank returns a banker rounded fixed-point string with places digits
+// after the decimal point.
+//
+// Example:
+//
+// NewFromFloat(0).StringFixed(2) // output: "0.00"
+// NewFromFloat(0).StringFixed(0) // output: "0"
+// NewFromFloat(5.45).StringFixed(0) // output: "5"
+// NewFromFloat(5.45).StringFixed(1) // output: "5.4"
+// NewFromFloat(5.45).StringFixed(2) // output: "5.45"
+// NewFromFloat(5.45).StringFixed(3) // output: "5.450"
+// NewFromFloat(545).StringFixed(-1) // output: "550"
+//
+func (d Decimal) StringFixedBank(places int32) string {
+ rounded := d.RoundBank(places)
+ return rounded.string(false)
+}
+
+// StringFixedCash returns a Swedish/Cash rounded fixed-point string. For
+// more details see the documentation at function RoundCash.
+func (d Decimal) StringFixedCash(interval uint8) string {
+ rounded := d.RoundCash(interval)
+ return rounded.string(false)
+}
+
+// Round rounds the decimal to places decimal places.
+// If places < 0, it will round the integer part to the nearest 10^(-places).
+//
+// Example:
+//
+// NewFromFloat(5.45).Round(1).String() // output: "5.5"
+// NewFromFloat(545).Round(-1).String() // output: "550"
+//
+func (d Decimal) Round(places int32) Decimal {
+ // truncate to places + 1
+ ret := d.rescale(-places - 1)
+
+ // add sign(d) * 0.5
+ if ret.value.Sign() < 0 {
+ ret.value.Sub(ret.value, fiveInt)
+ } else {
+ ret.value.Add(ret.value, fiveInt)
+ }
+
+ // floor for positive numbers, ceil for negative numbers
+ _, m := ret.value.DivMod(ret.value, tenInt, new(big.Int))
+ ret.exp++
+ if ret.value.Sign() < 0 && m.Cmp(zeroInt) != 0 {
+ ret.value.Add(ret.value, oneInt)
+ }
+
+ return ret
+}
+
+// RoundBank rounds the decimal to places decimal places.
+// If the final digit to round is equidistant from the nearest two integers the
+// rounded value is taken as the even number
+//
+// If places < 0, it will round the integer part to the nearest 10^(-places).
+//
+// Examples:
+//
+// NewFromFloat(5.45).Round(1).String() // output: "5.4"
+// NewFromFloat(545).Round(-1).String() // output: "540"
+// NewFromFloat(5.46).Round(1).String() // output: "5.5"
+// NewFromFloat(546).Round(-1).String() // output: "550"
+// NewFromFloat(5.55).Round(1).String() // output: "5.6"
+// NewFromFloat(555).Round(-1).String() // output: "560"
+//
+func (d Decimal) RoundBank(places int32) Decimal {
+
+ round := d.Round(places)
+ remainder := d.Sub(round).Abs()
+
+ half := New(5, -places-1)
+ if remainder.Cmp(half) == 0 && round.value.Bit(0) != 0 {
+ if round.value.Sign() < 0 {
+ round.value.Add(round.value, oneInt)
+ } else {
+ round.value.Sub(round.value, oneInt)
+ }
+ }
+
+ return round
+}
+
+// RoundCash aka Cash/Penny/öre rounding rounds decimal to a specific
+// interval. The amount payable for a cash transaction is rounded to the nearest
+// multiple of the minimum currency unit available. The following intervals are
+// available: 5, 10, 15, 25, 50 and 100; any other number throws a panic.
+// 5: 5 cent rounding 3.43 => 3.45
+// 10: 10 cent rounding 3.45 => 3.50 (5 gets rounded up)
+// 15: 10 cent rounding 3.45 => 3.40 (5 gets rounded down)
+// 25: 25 cent rounding 3.41 => 3.50
+// 50: 50 cent rounding 3.75 => 4.00
+// 100: 100 cent rounding 3.50 => 4.00
+// For more details: https://en.wikipedia.org/wiki/Cash_rounding
+func (d Decimal) RoundCash(interval uint8) Decimal {
+ var iVal *big.Int
+ switch interval {
+ case 5:
+ iVal = twentyInt
+ case 10:
+ iVal = tenInt
+ case 15:
+ if d.exp < 0 {
+ // TODO: optimize and reduce allocations
+ orgExp := d.exp
+ dOne := New(10^-int64(orgExp), orgExp)
+ d2 := d
+ d2.exp = 0
+ if d2.Mod(fiveDec).Equal(Zero) {
+ d2.exp = orgExp
+ d2 = d2.Sub(dOne)
+ d = d2
+ }
+ }
+ iVal = tenInt
+ case 25:
+ iVal = fourInt
+ case 50:
+ iVal = twoInt
+ case 100:
+ iVal = oneInt
+ default:
+ panic(fmt.Sprintf("Decimal does not support this Cash rounding interval `%d`. Supported: 5, 10, 15, 25, 50, 100", interval))
+ }
+ dVal := Decimal{
+ value: iVal,
+ }
+ // TODO: optimize those calculations to reduce the high allocations (~29 allocs).
+ return d.Mul(dVal).Round(0).Div(dVal).Truncate(2)
+}
+
+// Floor returns the nearest integer value less than or equal to d.
+func (d Decimal) Floor() Decimal {
+ d.ensureInitialized()
+
+ if d.exp >= 0 {
+ return d
+ }
+
+ exp := big.NewInt(10)
+
+ // NOTE(vadim): must negate after casting to prevent int32 overflow
+ exp.Exp(exp, big.NewInt(-int64(d.exp)), nil)
+
+ z := new(big.Int).Div(d.value, exp)
+ return Decimal{value: z, exp: 0}
+}
+
+// Ceil returns the nearest integer value greater than or equal to d.
+func (d Decimal) Ceil() Decimal {
+ d.ensureInitialized()
+
+ if d.exp >= 0 {
+ return d
+ }
+
+ exp := big.NewInt(10)
+
+ // NOTE(vadim): must negate after casting to prevent int32 overflow
+ exp.Exp(exp, big.NewInt(-int64(d.exp)), nil)
+
+ z, m := new(big.Int).DivMod(d.value, exp, new(big.Int))
+ if m.Cmp(zeroInt) != 0 {
+ z.Add(z, oneInt)
+ }
+ return Decimal{value: z, exp: 0}
+}
+
+// Truncate truncates off digits from the number, without rounding.
+//
+// NOTE: precision is the last digit that will not be truncated (must be >= 0).
+//
+// Example:
+//
+// decimal.NewFromString("123.456").Truncate(2).String() // "123.45"
+//
+func (d Decimal) Truncate(precision int32) Decimal {
+ d.ensureInitialized()
+ if precision >= 0 && -precision > d.exp {
+ return d.rescale(-precision)
+ }
+ return d
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface.
+func (d *Decimal) UnmarshalJSON(decimalBytes []byte) error {
+ if string(decimalBytes) == "null" {
+ return nil
+ }
+
+ str, err := unquoteIfQuoted(decimalBytes)
+ if err != nil {
+ return fmt.Errorf("Error decoding string '%s': %s", decimalBytes, err)
+ }
+
+ decimal, err := NewFromString(str)
+ *d = decimal
+ if err != nil {
+ return fmt.Errorf("Error decoding string '%s': %s", str, err)
+ }
+ return nil
+}
+
+// MarshalJSON implements the json.Marshaler interface.
+func (d Decimal) MarshalJSON() ([]byte, error) {
+ var str string
+ if MarshalJSONWithoutQuotes {
+ str = d.String()
+ } else {
+ str = "\"" + d.String() + "\""
+ }
+ return []byte(str), nil
+}
+
+// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. As a string representation
+// is already used when encoding to text, this method stores that string as []byte
+func (d *Decimal) UnmarshalBinary(data []byte) error {
+ // Extract the exponent
+ d.exp = int32(binary.BigEndian.Uint32(data[:4]))
+
+ // Extract the value
+ d.value = new(big.Int)
+ return d.value.GobDecode(data[4:])
+}
+
+// MarshalBinary implements the encoding.BinaryMarshaler interface.
+func (d Decimal) MarshalBinary() (data []byte, err error) {
+ // Write the exponent first since it's a fixed size
+ v1 := make([]byte, 4)
+ binary.BigEndian.PutUint32(v1, uint32(d.exp))
+
+ // Add the value
+ var v2 []byte
+ if v2, err = d.value.GobEncode(); err != nil {
+ return
+ }
+
+ // Return the byte array
+ data = append(v1, v2...)
+ return
+}
+
+// Scan implements the sql.Scanner interface for database deserialization.
+func (d *Decimal) Scan(value interface{}) error {
+ // first try to see if the data is stored in database as a Numeric datatype
+ switch v := value.(type) {
+
+ case float32:
+ *d = NewFromFloat(float64(v))
+ return nil
+
+ case float64:
+ // numeric in sqlite3 sends us float64
+ *d = NewFromFloat(v)
+ return nil
+
+ case int64:
+ // at least in sqlite3 when the value is 0 in db, the data is sent
+ // to us as an int64 instead of a float64 ...
+ *d = New(v, 0)
+ return nil
+
+ default:
+ // default is trying to interpret value stored as string
+ str, err := unquoteIfQuoted(v)
+ if err != nil {
+ return err
+ }
+ *d, err = NewFromString(str)
+ return err
+ }
+}
+
+// Value implements the driver.Valuer interface for database serialization.
+func (d Decimal) Value() (driver.Value, error) {
+ return d.String(), nil
+}
+
+// UnmarshalText implements the encoding.TextUnmarshaler interface for XML
+// deserialization.
+func (d *Decimal) UnmarshalText(text []byte) error {
+ str := string(text)
+
+ dec, err := NewFromString(str)
+ *d = dec
+ if err != nil {
+ return fmt.Errorf("Error decoding string '%s': %s", str, err)
+ }
+
+ return nil
+}
+
+// MarshalText implements the encoding.TextMarshaler interface for XML
+// serialization.
+func (d Decimal) MarshalText() (text []byte, err error) {
+ return []byte(d.String()), nil
+}
+
+// GobEncode implements the gob.GobEncoder interface for gob serialization.
+func (d Decimal) GobEncode() ([]byte, error) {
+ return d.MarshalBinary()
+}
+
+// GobDecode implements the gob.GobDecoder interface for gob serialization.
+func (d *Decimal) GobDecode(data []byte) error {
+ return d.UnmarshalBinary(data)
+}
+
+// StringScaled first scales the decimal then calls .String() on it.
+// NOTE: buggy, unintuitive, and DEPRECATED! Use StringFixed instead.
+func (d Decimal) StringScaled(exp int32) string {
+ return d.rescale(exp).String()
+}
+
+func (d Decimal) string(trimTrailingZeros bool) string {
+ if d.exp >= 0 {
+ return d.rescale(0).value.String()
+ }
+
+ abs := new(big.Int).Abs(d.value)
+ str := abs.String()
+
+ var intPart, fractionalPart string
+
+ // NOTE(vadim): this cast to int will cause bugs if d.exp == INT_MIN
+ // and you are on a 32-bit machine. Won't fix this super-edge case.
+ dExpInt := int(d.exp)
+ if len(str) > -dExpInt {
+ intPart = str[:len(str)+dExpInt]
+ fractionalPart = str[len(str)+dExpInt:]
+ } else {
+ intPart = "0"
+
+ num0s := -dExpInt - len(str)
+ fractionalPart = strings.Repeat("0", num0s) + str
+ }
+
+ if trimTrailingZeros {
+ i := len(fractionalPart) - 1
+ for ; i >= 0; i-- {
+ if fractionalPart[i] != '0' {
+ break
+ }
+ }
+ fractionalPart = fractionalPart[:i+1]
+ }
+
+ number := intPart
+ if len(fractionalPart) > 0 {
+ number += "." + fractionalPart
+ }
+
+ if d.value.Sign() < 0 {
+ return "-" + number
+ }
+
+ return number
+}
+
+func (d *Decimal) ensureInitialized() {
+ if d.value == nil {
+ d.value = new(big.Int)
+ }
+}
+
+// Min returns the smallest Decimal that was passed in the arguments.
+//
+// To call this function with an array, you must do:
+//
+// Min(arr[0], arr[1:]...)
+//
+// This makes it harder to accidentally call Min with 0 arguments.
+func Min(first Decimal, rest ...Decimal) Decimal {
+ ans := first
+ for _, item := range rest {
+ if item.Cmp(ans) < 0 {
+ ans = item
+ }
+ }
+ return ans
+}
+
+// Max returns the largest Decimal that was passed in the arguments.
+//
+// To call this function with an array, you must do:
+//
+// Max(arr[0], arr[1:]...)
+//
+// This makes it harder to accidentally call Max with 0 arguments.
+func Max(first Decimal, rest ...Decimal) Decimal {
+ ans := first
+ for _, item := range rest {
+ if item.Cmp(ans) > 0 {
+ ans = item
+ }
+ }
+ return ans
+}
+
+// Sum returns the combined total of the provided first and rest Decimals
+func Sum(first Decimal, rest ...Decimal) Decimal {
+ total := first
+ for _, item := range rest {
+ total = total.Add(item)
+ }
+
+ return total
+}
+
+// Avg returns the average value of the provided first and rest Decimals
+func Avg(first Decimal, rest ...Decimal) Decimal {
+ count := New(int64(len(rest)+1), 0)
+ sum := Sum(first, rest...)
+ return sum.Div(count)
+}
+
+func min(x, y int32) int32 {
+ if x >= y {
+ return y
+ }
+ return x
+}
+
+func unquoteIfQuoted(value interface{}) (string, error) {
+ var bytes []byte
+
+ switch v := value.(type) {
+ case string:
+ bytes = []byte(v)
+ case []byte:
+ bytes = v
+ default:
+ return "", fmt.Errorf("Could not convert value '%+v' to byte array of type '%T'",
+ value, value)
+ }
+
+ // If the amount is quoted, strip the quotes
+ if len(bytes) > 2 && bytes[0] == '"' && bytes[len(bytes)-1] == '"' {
+ bytes = bytes[1 : len(bytes)-1]
+ }
+ return string(bytes), nil
+}
+
+// NullDecimal represents a nullable decimal with compatibility for
+// scanning null values from the database.
+type NullDecimal struct {
+ Decimal Decimal
+ Valid bool
+}
+
+// Scan implements the sql.Scanner interface for database deserialization.
+func (d *NullDecimal) Scan(value interface{}) error {
+ if value == nil {
+ d.Valid = false
+ return nil
+ }
+ d.Valid = true
+ return d.Decimal.Scan(value)
+}
+
+// Value implements the driver.Valuer interface for database serialization.
+func (d NullDecimal) Value() (driver.Value, error) {
+ if !d.Valid {
+ return nil, nil
+ }
+ return d.Decimal.Value()
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface.
+func (d *NullDecimal) UnmarshalJSON(decimalBytes []byte) error {
+ if string(decimalBytes) == "null" {
+ d.Valid = false
+ return nil
+ }
+ d.Valid = true
+ return d.Decimal.UnmarshalJSON(decimalBytes)
+}
+
+// MarshalJSON implements the json.Marshaler interface.
+func (d NullDecimal) MarshalJSON() ([]byte, error) {
+ if !d.Valid {
+ return []byte("null"), nil
+ }
+ return d.Decimal.MarshalJSON()
+}
+
+// Trig functions
+
+// Atan returns the arctangent, in radians, of x.
+func (x Decimal) Atan() Decimal {
+ if x.Equal(NewFromFloat(0.0)) {
+ return x
+ }
+ if x.GreaterThan(NewFromFloat(0.0)) {
+ return x.satan()
+ }
+ return x.Neg().satan().Neg()
+}
+
+func (d Decimal) xatan() Decimal {
+ P0 := NewFromFloat(-8.750608600031904122785e-01)
+ P1 := NewFromFloat(-1.615753718733365076637e+01)
+ P2 := NewFromFloat(-7.500855792314704667340e+01)
+ P3 := NewFromFloat(-1.228866684490136173410e+02)
+ P4 := NewFromFloat(-6.485021904942025371773e+01)
+ Q0 := NewFromFloat(2.485846490142306297962e+01)
+ Q1 := NewFromFloat(1.650270098316988542046e+02)
+ Q2 := NewFromFloat(4.328810604912902668951e+02)
+ Q3 := NewFromFloat(4.853903996359136964868e+02)
+ Q4 := NewFromFloat(1.945506571482613964425e+02)
+ z := d.Mul(d)
+ b1 := P0.Mul(z).Add(P1).Mul(z).Add(P2).Mul(z).Add(P3).Mul(z).Add(P4).Mul(z)
+ b2 := z.Add(Q0).Mul(z).Add(Q1).Mul(z).Add(Q2).Mul(z).Add(Q3).Mul(z).Add(Q4)
+ z = b1.Div(b2)
+ z = d.Mul(z).Add(d)
+ return z
+}
+
+// satan reduces its argument (known to be positive)
+// to the range [0, 0.66] and calls xatan.
+func (d Decimal) satan() Decimal {
+ Morebits := NewFromFloat(6.123233995736765886130e-17) // pi/2 = PIO2 + Morebits
+ Tan3pio8 := NewFromFloat(2.41421356237309504880) // tan(3*pi/8)
+ pi := NewFromFloat(3.14159265358979323846264338327950288419716939937510582097494459)
+
+ if d.LessThanOrEqual(NewFromFloat(0.66)) {
+ return d.xatan()
+ }
+ if d.GreaterThan(Tan3pio8) {
+ return pi.Div(NewFromFloat(2.0)).Sub(NewFromFloat(1.0).Div(d).xatan()).Add(Morebits)
+ }
+ return pi.Div(NewFromFloat(4.0)).Add((d.Sub(NewFromFloat(1.0)).Div(d.Add(NewFromFloat(1.0)))).xatan()).Add(NewFromFloat(0.5).Mul(Morebits))
+}
+
+// sin coefficients
+ var _sin = [...]Decimal{
+ NewFromFloat(1.58962301576546568060E-10), // 0x3de5d8fd1fd19ccd
+ NewFromFloat(-2.50507477628578072866E-8), // 0xbe5ae5e5a9291f5d
+ NewFromFloat(2.75573136213857245213E-6), // 0x3ec71de3567d48a1
+ NewFromFloat(-1.98412698295895385996E-4), // 0xbf2a01a019bfdf03
+ NewFromFloat(8.33333333332211858878E-3), // 0x3f8111111110f7d0
+ NewFromFloat(-1.66666666666666307295E-1), // 0xbfc5555555555548
+ }
+
+// Sin returns the sine of the radian argument x.
+ func (d Decimal) Sin() Decimal {
+ PI4A := NewFromFloat(7.85398125648498535156E-1) // 0x3fe921fb40000000, Pi/4 split into three parts
+ PI4B := NewFromFloat(3.77489470793079817668E-8) // 0x3e64442d00000000,
+ PI4C := NewFromFloat(2.69515142907905952645E-15) // 0x3ce8469898cc5170,
+ M4PI := NewFromFloat(1.273239544735162542821171882678754627704620361328125) // 4/pi
+
+ if d.Equal(NewFromFloat(0.0)) {
+ return d
+ }
+ // make argument positive but save the sign
+ sign := false
+ if d.LessThan(NewFromFloat(0.0)) {
+ d = d.Neg()
+ sign = true
+ }
+
+ j := d.Mul(M4PI).IntPart() // integer part of x/(Pi/4), as integer for tests on the phase angle
+ y := NewFromFloat(float64(j)) // integer part of x/(Pi/4), as float
+
+ // map zeros to origin
+ if j&1 == 1 {
+ j++
+ y = y.Add(NewFromFloat(1.0))
+ }
+ j &= 7 // octant modulo 2Pi radians (360 degrees)
+ // reflect in x axis
+ if j > 3 {
+ sign = !sign
+ j -= 4
+ }
+ z := d.Sub(y.Mul(PI4A)).Sub(y.Mul(PI4B)).Sub(y.Mul(PI4C)) // Extended precision modular arithmetic
+ zz := z.Mul(z)
+
+ if j == 1 || j == 2 {
+ w := zz.Mul(zz).Mul(_cos[0].Mul(zz).Add(_cos[1]).Mul(zz).Add(_cos[2]).Mul(zz).Add(_cos[3]).Mul(zz).Add(_cos[4]).Mul(zz).Add(_cos[5]))
+ y = NewFromFloat(1.0).Sub(NewFromFloat(0.5).Mul(zz)).Add(w)
+ } else {
+ y = z.Add(z.Mul(zz).Mul(_sin[0].Mul(zz).Add(_sin[1]).Mul(zz).Add(_sin[2]).Mul(zz).Add(_sin[3]).Mul(zz).Add(_sin[4]).Mul(zz).Add(_sin[5])))
+ }
+ if sign {
+ y = y.Neg()
+ }
+ return y
+ }
+
+ // cos coefficients
+ var _cos = [...]Decimal{
+ NewFromFloat(-1.13585365213876817300E-11), // 0xbda8fa49a0861a9b
+ NewFromFloat(2.08757008419747316778E-9), // 0x3e21ee9d7b4e3f05
+ NewFromFloat(-2.75573141792967388112E-7), // 0xbe927e4f7eac4bc6
+ NewFromFloat(2.48015872888517045348E-5), // 0x3efa01a019c844f5
+ NewFromFloat(-1.38888888888730564116E-3), // 0xbf56c16c16c14f91
+ NewFromFloat(4.16666666666665929218E-2), // 0x3fa555555555554b
+ }
+
+ // Cos returns the cosine of the radian argument x.
+ func (d Decimal) Cos() Decimal {
+
+ PI4A := NewFromFloat(7.85398125648498535156E-1) // 0x3fe921fb40000000, Pi/4 split into three parts
+ PI4B := NewFromFloat(3.77489470793079817668E-8) // 0x3e64442d00000000,
+ PI4C := NewFromFloat(2.69515142907905952645E-15) // 0x3ce8469898cc5170,
+ M4PI := NewFromFloat(1.273239544735162542821171882678754627704620361328125) // 4/pi
+
+ // make argument positive
+ sign := false
+ if d.LessThan(NewFromFloat(0.0)) {
+ d = d.Neg()
+ }
+
+ j := d.Mul(M4PI).IntPart() // integer part of x/(Pi/4), as integer for tests on the phase angle
+ y := NewFromFloat(float64(j)) // integer part of x/(Pi/4), as float
+
+ // map zeros to origin
+ if j&1 == 1 {
+ j++
+ y = y.Add(NewFromFloat(1.0))
+ }
+ j &= 7 // octant modulo 2Pi radians (360 degrees)
+ // reflect in x axis
+ if j > 3 {
+ sign = !sign
+ j -= 4
+ }
+ if j > 1 {
+ sign = !sign
+ }
+
+ z := d.Sub(y.Mul(PI4A)).Sub(y.Mul(PI4B)).Sub(y.Mul(PI4C)) // Extended precision modular arithmetic
+ zz := z.Mul(z)
+
+ if j == 1 || j == 2 {
+ y = z.Add(z.Mul(zz).Mul(_sin[0].Mul(zz).Add(_sin[1]).Mul(zz).Add(_sin[2]).Mul(zz).Add(_sin[3]).Mul(zz).Add(_sin[4]).Mul(zz).Add(_sin[5])))
+ } else {
+ w := zz.Mul(zz).Mul(_cos[0].Mul(zz).Add(_cos[1]).Mul(zz).Add(_cos[2]).Mul(zz).Add(_cos[3]).Mul(zz).Add(_cos[4]).Mul(zz).Add(_cos[5]))
+ y = NewFromFloat(1.0).Sub(NewFromFloat(0.5).Mul(zz)).Add(w)
+ }
+ if sign {
+ y = y.Neg()
+ }
+ return y
+ }
+
+ var _tanP = [...]Decimal{
+ NewFromFloat(-1.30936939181383777646E+4), // 0xc0c992d8d24f3f38
+ NewFromFloat(1.15351664838587416140E+6), // 0x413199eca5fc9ddd
+ NewFromFloat(-1.79565251976484877988E+7), // 0xc1711fead3299176
+ }
+ var _tanQ = [...]Decimal{
+ NewFromFloat(1.00000000000000000000E+0),
+ NewFromFloat(1.36812963470692954678E+4), //0x40cab8a5eeb36572
+ NewFromFloat(-1.32089234440210967447E+6), //0xc13427bc582abc96
+ NewFromFloat(2.50083801823357915839E+7), //0x4177d98fc2ead8ef
+ NewFromFloat(-5.38695755929454629881E+7), //0xc189afe03cbe5a31
+ }
+
+ // Tan returns the tangent of the radian argument x.
+ func (d Decimal) Tan() Decimal {
+
+ PI4A := NewFromFloat(7.85398125648498535156E-1) // 0x3fe921fb40000000, Pi/4 split into three parts
+ PI4B := NewFromFloat(3.77489470793079817668E-8) // 0x3e64442d00000000,
+ PI4C := NewFromFloat(2.69515142907905952645E-15) // 0x3ce8469898cc5170,
+ M4PI := NewFromFloat(1.273239544735162542821171882678754627704620361328125) // 4/pi
+
+ if d.Equal(NewFromFloat(0.0)) {
+ return d
+ }
+
+ // make argument positive but save the sign
+ sign := false
+ if d.LessThan(NewFromFloat(0.0)) {
+ d = d.Neg()
+ sign = true
+ }
+
+ j := d.Mul(M4PI).IntPart() // integer part of x/(Pi/4), as integer for tests on the phase angle
+ y := NewFromFloat(float64(j)) // integer part of x/(Pi/4), as float
+
+ // map zeros to origin
+ if j&1 == 1 {
+ j++
+ y = y.Add(NewFromFloat(1.0))
+ }
+
+ z := d.Sub(y.Mul(PI4A)).Sub(y.Mul(PI4B)).Sub(y.Mul(PI4C)) // Extended precision modular arithmetic
+ zz := z.Mul(z)
+
+ if zz.GreaterThan(NewFromFloat(1e-14)) {
+ w := zz.Mul(_tanP[0].Mul(zz).Add(_tanP[1]).Mul(zz).Add(_tanP[2]))
+ x := zz.Add(_tanQ[1]).Mul(zz).Add(_tanQ[2]).Mul(zz).Add(_tanQ[3]).Mul(zz).Add(_tanQ[4])
+ y = z.Add(z.Mul(w.Div(x)))
+ } else {
+ y = z
+ }
+ if j&2 == 2 {
+ y = NewFromFloat(-1.0).Div(y)
+ }
+ if sign {
+ y = y.Neg()
+ }
+ return y
+ }
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/shopspring/decimal/rounding.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/shopspring/decimal/rounding.go
new file mode 100644
index 0000000..fdd74ea
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/shopspring/decimal/rounding.go
@@ -0,0 +1,118 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Multiprecision decimal numbers.
+// For floating-point formatting only; not general purpose.
+// Only operations are assign and (binary) left/right shift.
+// Can do binary floating point in multiprecision decimal precisely
+// because 2 divides 10; cannot do decimal floating point
+// in multiprecision binary precisely.
+package decimal
+
+type floatInfo struct {
+ mantbits uint
+ expbits uint
+ bias int
+}
+
+var float32info = floatInfo{23, 8, -127}
+var float64info = floatInfo{52, 11, -1023}
+
+// roundShortest rounds d (= mant * 2^exp) to the shortest number of digits
+// that will let the original floating point value be precisely reconstructed.
+func roundShortest(d *decimal, mant uint64, exp int, flt *floatInfo) {
+ // If mantissa is zero, the number is zero; stop now.
+ if mant == 0 {
+ d.nd = 0
+ return
+ }
+
+ // Compute upper and lower such that any decimal number
+ // between upper and lower (possibly inclusive)
+ // will round to the original floating point number.
+
+ // We may see at once that the number is already shortest.
+ //
+ // Suppose d is not denormal, so that 2^exp <= d < 10^dp.
+ // The closest shorter number is at least 10^(dp-nd) away.
+ // The lower/upper bounds computed below are at distance
+ // at most 2^(exp-mantbits).
+ //
+ // So the number is already shortest if 10^(dp-nd) > 2^(exp-mantbits),
+ // or equivalently log2(10)*(dp-nd) > exp-mantbits.
+ // It is true if 332/100*(dp-nd) >= exp-mantbits (log2(10) > 3.32).
+ minexp := flt.bias + 1 // minimum possible exponent
+ if exp > minexp && 332*(d.dp-d.nd) >= 100*(exp-int(flt.mantbits)) {
+ // The number is already shortest.
+ return
+ }
+
+ // d = mant << (exp - mantbits)
+ // Next highest floating point number is mant+1 << exp-mantbits.
+ // Our upper bound is halfway between, mant*2+1 << exp-mantbits-1.
+ upper := new(decimal)
+ upper.Assign(mant*2 + 1)
+ upper.Shift(exp - int(flt.mantbits) - 1)
+
+ // d = mant << (exp - mantbits)
+ // Next lowest floating point number is mant-1 << exp-mantbits,
+ // unless mant-1 drops the significant bit and exp is not the minimum exp,
+ // in which case the next lowest is mant*2-1 << exp-mantbits-1.
+ // Either way, call it mantlo << explo-mantbits.
+ // Our lower bound is halfway between, mantlo*2+1 << explo-mantbits-1.
+ var mantlo uint64
+ var explo int
+ if mant > 1< 0 {
+ h.fd.Close()
+
+ for i := h.backupCount - 1; i > 0; i-- {
+ sfn := fmt.Sprintf("%s.%d", h.fileName, i)
+ dfn := fmt.Sprintf("%s.%d", h.fileName, i+1)
+
+ os.Rename(sfn, dfn)
+ }
+
+ dfn := fmt.Sprintf("%s.1", h.fileName)
+ os.Rename(h.fileName, dfn)
+
+ h.fd, _ = os.OpenFile(h.fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
+ h.curBytes = 0
+ f, err := h.fd.Stat()
+ if err != nil {
+ return
+ }
+ h.curBytes = int(f.Size())
+ }
+}
+
+// TimeRotatingFileHandler writes log to a file,
+// it will backup current and open a new one, with a period time you sepecified.
+//
+// refer: http://docs.python.org/2/library/logging.handlers.html.
+// same like python TimedRotatingFileHandler.
+type TimeRotatingFileHandler struct {
+ fd *os.File
+
+ baseName string
+ interval int64
+ suffix string
+ rolloverAt int64
+}
+
+// TimeRotating way
+const (
+ WhenSecond = iota
+ WhenMinute
+ WhenHour
+ WhenDay
+)
+
+// NewTimeRotatingFileHandler creates a TimeRotatingFileHandler
+func NewTimeRotatingFileHandler(baseName string, when int8, interval int) (*TimeRotatingFileHandler, error) {
+ dir := path.Dir(baseName)
+ os.MkdirAll(dir, 0777)
+
+ h := new(TimeRotatingFileHandler)
+
+ h.baseName = baseName
+
+ switch when {
+ case WhenSecond:
+ h.interval = 1
+ h.suffix = "2006-01-02_15-04-05"
+ case WhenMinute:
+ h.interval = 60
+ h.suffix = "2006-01-02_15-04"
+ case WhenHour:
+ h.interval = 3600
+ h.suffix = "2006-01-02_15"
+ case WhenDay:
+ h.interval = 3600 * 24
+ h.suffix = "2006-01-02"
+ default:
+ return nil, fmt.Errorf("invalid when_rotate: %d", when)
+ }
+
+ h.interval = h.interval * int64(interval)
+
+ var err error
+ h.fd, err = os.OpenFile(h.baseName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
+ if err != nil {
+ return nil, err
+ }
+
+ fInfo, _ := h.fd.Stat()
+ h.rolloverAt = fInfo.ModTime().Unix() + h.interval
+
+ return h, nil
+}
+
+func (h *TimeRotatingFileHandler) doRollover() {
+ //refer http://hg.python.org/cpython/file/2.7/Lib/logging/handlers.py
+ now := time.Now()
+
+ if h.rolloverAt <= now.Unix() {
+ fName := h.baseName + now.Format(h.suffix)
+ h.fd.Close()
+ e := os.Rename(h.baseName, fName)
+ if e != nil {
+ panic(e)
+ }
+
+ h.fd, _ = os.OpenFile(h.baseName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
+
+ h.rolloverAt = time.Now().Unix() + h.interval
+ }
+}
+
+// Write implements Handler interface
+func (h *TimeRotatingFileHandler) Write(b []byte) (n int, err error) {
+ h.doRollover()
+ return h.fd.Write(b)
+}
+
+// Close implements Handler interface
+func (h *TimeRotatingFileHandler) Close() error {
+ return h.fd.Close()
+}
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go-log/log/handler.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go-log/log/handler.go
new file mode 100644
index 0000000..5460f06
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go-log/log/handler.go
@@ -0,0 +1,54 @@
+package log
+
+import (
+ "io"
+)
+
+//Handler writes logs to somewhere
+type Handler interface {
+ Write(p []byte) (n int, err error)
+ Close() error
+}
+
+// StreamHandler writes logs to a specified io Writer, maybe stdout, stderr, etc...
+type StreamHandler struct {
+ w io.Writer
+}
+
+// NewStreamHandler creates a StreamHandler
+func NewStreamHandler(w io.Writer) (*StreamHandler, error) {
+ h := new(StreamHandler)
+
+ h.w = w
+
+ return h, nil
+}
+
+// Write implements Handler interface
+func (h *StreamHandler) Write(b []byte) (n int, err error) {
+ return h.w.Write(b)
+}
+
+// Close implements Handler interface
+func (h *StreamHandler) Close() error {
+ return nil
+}
+
+// NullHandler does nothing, it discards anything.
+type NullHandler struct {
+}
+
+// NewNullHandler creates a NullHandler
+func NewNullHandler() (*NullHandler, error) {
+ return new(NullHandler), nil
+}
+
+// // Write implements Handler interface
+func (h *NullHandler) Write(b []byte) (n int, err error) {
+ return len(b), nil
+}
+
+// Close implements Handler interface
+func (h *NullHandler) Close() {
+
+}
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go-log/log/log.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go-log/log/log.go
new file mode 100644
index 0000000..956186d
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go-log/log/log.go
@@ -0,0 +1,137 @@
+package log
+
+import (
+ "fmt"
+ "os"
+)
+
+var logger = NewDefault(newStdHandler())
+
+// SetDefaultLogger changes the global logger
+func SetDefaultLogger(l *Logger) {
+ logger = l
+}
+
+// SetLevel changes the logger level
+func SetLevel(level Level) {
+ logger.SetLevel(level)
+}
+
+// SetLevelByName changes the logger level by name
+func SetLevelByName(name string) {
+ logger.SetLevelByName(name)
+}
+
+// Fatal records the log with fatal level and exits
+func Fatal(args ...interface{}) {
+ logger.Output(2, LevelFatal, fmt.Sprint(args...))
+ os.Exit(1)
+}
+
+// Fatalf records the log with fatal level and exits
+func Fatalf(format string, args ...interface{}) {
+ logger.Output(2, LevelFatal, fmt.Sprintf(format, args...))
+ os.Exit(1)
+}
+
+// Fatalln records the log with fatal level and exits
+func Fatalln(args ...interface{}) {
+ logger.Output(2, LevelFatal, fmt.Sprintln(args...))
+ os.Exit(1)
+}
+
+// Panic records the log with fatal level and panics
+func Panic(args ...interface{}) {
+ msg := fmt.Sprint(args...)
+ logger.Output(2, LevelError, msg)
+ panic(msg)
+}
+
+// Panicf records the log with fatal level and panics
+func Panicf(format string, args ...interface{}) {
+ msg := fmt.Sprintf(format, args...)
+ logger.Output(2, LevelError, msg)
+ panic(msg)
+}
+
+// Panicln records the log with fatal level and panics
+func Panicln(args ...interface{}) {
+ msg := fmt.Sprintln(args...)
+ logger.Output(2, LevelError, msg)
+ panic(msg)
+}
+
+// Print records the log with trace level
+func Print(args ...interface{}) {
+ logger.Output(2, LevelTrace, fmt.Sprint(args...))
+}
+
+// Printf records the log with trace level
+func Printf(format string, args ...interface{}) {
+ logger.Output(2, LevelTrace, fmt.Sprintf(format, args...))
+}
+
+// Println records the log with trace level
+func Println(args ...interface{}) {
+ logger.Output(2, LevelTrace, fmt.Sprintln(args...))
+}
+
+// Debug records the log with debug level
+func Debug(args ...interface{}) {
+ logger.Output(2, LevelDebug, fmt.Sprint(args...))
+}
+
+// Debugf records the log with debug level
+func Debugf(format string, args ...interface{}) {
+ logger.Output(2, LevelDebug, fmt.Sprintf(format, args...))
+}
+
+// Debugln records the log with debug level
+func Debugln(args ...interface{}) {
+ logger.Output(2, LevelDebug, fmt.Sprintln(args...))
+}
+
+// Error records the log with error level
+func Error(args ...interface{}) {
+ logger.Output(2, LevelError, fmt.Sprint(args...))
+}
+
+// Errorf records the log with error level
+func Errorf(format string, args ...interface{}) {
+ logger.Output(2, LevelError, fmt.Sprintf(format, args...))
+}
+
+// Errorln records the log with error level
+func Errorln(args ...interface{}) {
+ logger.Output(2, LevelError, fmt.Sprintln(args...))
+}
+
+// Info records the log with info level
+func Info(args ...interface{}) {
+ logger.Output(2, LevelInfo, fmt.Sprint(args...))
+}
+
+// Infof records the log with info level
+func Infof(format string, args ...interface{}) {
+ logger.Output(2, LevelInfo, fmt.Sprintf(format, args...))
+}
+
+// Infoln records the log with info level
+func Infoln(args ...interface{}) {
+ logger.Output(2, LevelInfo, fmt.Sprintln(args...))
+}
+
+// Warn records the log with warn level
+func Warn(args ...interface{}) {
+ logger.Output(2, LevelWarn, fmt.Sprint(args...))
+}
+
+// Warnf records the log with warn level
+func Warnf(format string, args ...interface{}) {
+ logger.Output(2, LevelWarn, fmt.Sprintf(format, args...))
+}
+
+// Warnln records the log with warn level
+func Warnln(args ...interface{}) {
+ logger.Output(2, LevelWarn, fmt.Sprintln(args...))
+}
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go-log/log/logger.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go-log/log/logger.go
new file mode 100644
index 0000000..b2f7ed2
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go-log/log/logger.go
@@ -0,0 +1,340 @@
+package log
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/siddontang/go-log/loggers"
+)
+
+const (
+ timeFormat = "2006/01/02 15:04:05"
+ maxBufPoolSize = 16
+)
+
+// Logger flag
+const (
+ Ltime = 1 << iota // time format "2006/01/02 15:04:05"
+ Lfile // file.go:123
+ Llevel // [Trace|Debug|Info...]
+)
+
+// Level type
+type Level int
+
+// Log level, from low to high, more high means more serious
+const (
+ LevelTrace Level = iota
+ LevelDebug
+ LevelInfo
+ LevelWarn
+ LevelError
+ LevelFatal
+)
+
+// String returns level String
+func (l Level) String() string {
+ switch l {
+ case LevelTrace:
+ return "trace"
+ case LevelDebug:
+ return "debug"
+ case LevelInfo:
+ return "info"
+ case LevelWarn:
+ return "warn"
+ case LevelError:
+ return "error"
+ case LevelFatal:
+ return "fatal"
+ }
+ // return default info
+ return "info"
+}
+
+// Logger is the logger to record log
+type Logger struct {
+ // TODO: support logger.Contextual
+ loggers.Advanced
+
+ sync.Mutex
+
+ level Level
+ flag int
+
+ handler Handler
+
+ quit chan struct{}
+ msg chan []byte
+
+ bufs [][]byte
+}
+
+// New creates a logger with specified handler and flag
+func New(handler Handler, flag int) *Logger {
+ var l = new(Logger)
+
+ l.level = LevelInfo
+ l.handler = handler
+
+ l.flag = flag
+
+ l.quit = make(chan struct{})
+
+ l.msg = make(chan []byte, 1024)
+
+ l.bufs = make([][]byte, 0, 16)
+
+ go l.run()
+
+ return l
+}
+
+// NewDefault creates default logger with specified handler and flag: Ltime|Lfile|Llevel
+func NewDefault(handler Handler) *Logger {
+ return New(handler, Ltime|Lfile|Llevel)
+}
+
+func newStdHandler() *StreamHandler {
+ h, _ := NewStreamHandler(os.Stdout)
+ return h
+}
+
+func (l *Logger) run() {
+ for {
+ select {
+ case msg := <-l.msg:
+ l.handler.Write(msg)
+ l.putBuf(msg)
+ case <-l.quit:
+ l.handler.Close()
+ }
+ }
+}
+
+func (l *Logger) popBuf() []byte {
+ l.Lock()
+ var buf []byte
+ if len(l.bufs) == 0 {
+ buf = make([]byte, 0, 1024)
+ } else {
+ buf = l.bufs[len(l.bufs)-1]
+ l.bufs = l.bufs[0 : len(l.bufs)-1]
+ }
+ l.Unlock()
+
+ return buf
+}
+
+func (l *Logger) putBuf(buf []byte) {
+ l.Lock()
+ if len(l.bufs) < maxBufPoolSize {
+ buf = buf[0:0]
+ l.bufs = append(l.bufs, buf)
+ }
+ l.Unlock()
+}
+
+// Close closes the logger
+func (l *Logger) Close() {
+ if l.quit == nil {
+ return
+ }
+
+ close(l.quit)
+ l.quit = nil
+}
+
+// SetLevel sets log level, any log level less than it will not log
+func (l *Logger) SetLevel(level Level) {
+ l.level = level
+}
+
+// SetLevelByName sets log level by name
+func (l *Logger) SetLevelByName(name string) {
+ level := LevelInfo
+ switch strings.ToLower(name) {
+ case "trace":
+ level = LevelTrace
+ case "debug":
+ level = LevelDebug
+ case "warn", "warning":
+ level = LevelWarn
+ case "error":
+ level = LevelError
+ case "fatal":
+ level = LevelFatal
+ default:
+ level = LevelInfo
+ }
+
+ l.SetLevel(level)
+}
+
+// Output records the log with special callstack depth and log level.
+func (l *Logger) Output(callDepth int, level Level, msg string) {
+ if l.level > level {
+ return
+ }
+
+ buf := l.popBuf()
+
+ if l.flag&Ltime > 0 {
+ now := time.Now().Format(timeFormat)
+ buf = append(buf, '[')
+ buf = append(buf, now...)
+ buf = append(buf, "] "...)
+ }
+
+ if l.flag&Llevel > 0 {
+ buf = append(buf, '[')
+ buf = append(buf, level.String()...)
+ buf = append(buf, "] "...)
+ }
+
+ if l.flag&Lfile > 0 {
+ _, file, line, ok := runtime.Caller(callDepth)
+ if !ok {
+ file = "???"
+ line = 0
+ } else {
+ for i := len(file) - 1; i > 0; i-- {
+ if file[i] == '/' {
+ file = file[i+1:]
+ break
+ }
+ }
+ }
+
+ buf = append(buf, file...)
+ buf = append(buf, ':')
+
+ buf = strconv.AppendInt(buf, int64(line), 10)
+ buf = append(buf, ' ')
+ }
+
+ buf = append(buf, msg...)
+ if len(msg) == 0 || msg[len(msg)-1] != '\n' {
+ buf = append(buf, '\n')
+ }
+ l.msg <- buf
+}
+
+// Fatal records the log with fatal level and exits
+func (l *Logger) Fatal(args ...interface{}) {
+ l.Output(2, LevelFatal, fmt.Sprint(args...))
+ os.Exit(1)
+}
+
+// Fatalf records the log with fatal level and exits
+func (l *Logger) Fatalf(format string, args ...interface{}) {
+ l.Output(2, LevelFatal, fmt.Sprintf(format, args...))
+ os.Exit(1)
+}
+
+// Fatalln records the log with fatal level and exits
+func (l *Logger) Fatalln(args ...interface{}) {
+ l.Output(2, LevelFatal, fmt.Sprintln(args...))
+ os.Exit(1)
+}
+
+// Panic records the log with fatal level and panics
+func (l *Logger) Panic(args ...interface{}) {
+ msg := fmt.Sprint(args...)
+ l.Output(2, LevelError, msg)
+ panic(msg)
+}
+
+// Panicf records the log with fatal level and panics
+func (l *Logger) Panicf(format string, args ...interface{}) {
+ msg := fmt.Sprintf(format, args...)
+ l.Output(2, LevelError, msg)
+ panic(msg)
+}
+
+// Panicln records the log with fatal level and panics
+func (l *Logger) Panicln(args ...interface{}) {
+ msg := fmt.Sprintln(args...)
+ l.Output(2, LevelError, msg)
+ panic(msg)
+}
+
+// Print records the log with trace level
+func (l *Logger) Print(args ...interface{}) {
+ l.Output(2, LevelTrace, fmt.Sprint(args...))
+}
+
+// Printf records the log with trace level
+func (l *Logger) Printf(format string, args ...interface{}) {
+ l.Output(2, LevelTrace, fmt.Sprintf(format, args...))
+}
+
+// Println records the log with trace level
+func (l *Logger) Println(args ...interface{}) {
+ l.Output(2, LevelTrace, fmt.Sprintln(args...))
+}
+
+// Debug records the log with debug level
+func (l *Logger) Debug(args ...interface{}) {
+ l.Output(2, LevelDebug, fmt.Sprint(args...))
+}
+
+// Debugf records the log with debug level
+func (l *Logger) Debugf(format string, args ...interface{}) {
+ l.Output(2, LevelDebug, fmt.Sprintf(format, args...))
+}
+
+// Debugln records the log with debug level
+func (l *Logger) Debugln(args ...interface{}) {
+ l.Output(2, LevelDebug, fmt.Sprintln(args...))
+}
+
+// Error records the log with error level
+func (l *Logger) Error(args ...interface{}) {
+ l.Output(2, LevelError, fmt.Sprint(args...))
+}
+
+// Errorf records the log with error level
+func (l *Logger) Errorf(format string, args ...interface{}) {
+ l.Output(2, LevelError, fmt.Sprintf(format, args...))
+}
+
+// Errorln records the log with error level
+func (l *Logger) Errorln(args ...interface{}) {
+ l.Output(2, LevelError, fmt.Sprintln(args...))
+}
+
+// Info records the log with info level
+func (l *Logger) Info(args ...interface{}) {
+ l.Output(2, LevelInfo, fmt.Sprint(args...))
+}
+
+// Infof records the log with info level
+func (l *Logger) Infof(format string, args ...interface{}) {
+ l.Output(2, LevelInfo, fmt.Sprintf(format, args...))
+}
+
+// Infoln records the log with info level
+func (l *Logger) Infoln(args ...interface{}) {
+ l.Output(2, LevelInfo, fmt.Sprintln(args...))
+}
+
+// Warn records the log with warn level
+func (l *Logger) Warn(args ...interface{}) {
+ l.Output(2, LevelWarn, fmt.Sprint(args...))
+}
+
+// Warnf records the log with warn level
+func (l *Logger) Warnf(format string, args ...interface{}) {
+ l.Output(2, LevelWarn, fmt.Sprintf(format, args...))
+}
+
+// Warnln records the log with warn level
+func (l *Logger) Warnln(args ...interface{}) {
+ l.Output(2, LevelWarn, fmt.Sprintln(args...))
+}
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go-log/loggers/loggers.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go-log/loggers/loggers.go
new file mode 100644
index 0000000..2723b24
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go-log/loggers/loggers.go
@@ -0,0 +1,68 @@
+// MIT License
+
+// Copyright (c) 2017 Birkir A. Barkarson
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+package loggers
+
+// Standard is the interface used by Go's standard library's log package.
+type Standard interface {
+ Fatal(args ...interface{})
+ Fatalf(format string, args ...interface{})
+ Fatalln(args ...interface{})
+
+ Panic(args ...interface{})
+ Panicf(format string, args ...interface{})
+ Panicln(args ...interface{})
+
+ Print(args ...interface{})
+ Printf(format string, args ...interface{})
+ Println(args ...interface{})
+}
+
+// Advanced is an interface with commonly used log level methods.
+type Advanced interface {
+ Standard
+
+ Debug(args ...interface{})
+ Debugf(format string, args ...interface{})
+ Debugln(args ...interface{})
+
+ Error(args ...interface{})
+ Errorf(format string, args ...interface{})
+ Errorln(args ...interface{})
+
+ Info(args ...interface{})
+ Infof(format string, args ...interface{})
+ Infoln(args ...interface{})
+
+ Warn(args ...interface{})
+ Warnf(format string, args ...interface{})
+ Warnln(args ...interface{})
+}
+
+// Contextual is an interface that allows context addition to a log statement before
+// calling the final print (message/level) method.
+type Contextual interface {
+ Advanced
+
+ WithField(key string, value interface{}) Advanced
+ WithFields(fields ...interface{}) Advanced
+}
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/siddontang/go/LICENSE b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/LICENSE
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/siddontang/go/LICENSE
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/LICENSE
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/bson/LICENSE b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/bson/LICENSE
new file mode 100644
index 0000000..8903260
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/bson/LICENSE
@@ -0,0 +1,25 @@
+BSON library for Go
+
+Copyright (c) 2010-2012 - Gustavo Niemeyer
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/filelock/LICENSE b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/filelock/LICENSE
new file mode 100644
index 0000000..fec05ce
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/filelock/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2011 The LevelDB-Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/siddontang/go/hack/hack.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/hack/hack.go
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/siddontang/go/hack/hack.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/hack/hack.go
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/golang.org/x/net/LICENSE b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/snappy/LICENSE
similarity index 95%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/golang.org/x/net/LICENSE
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/snappy/LICENSE
index 6a66aea..6050c10 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/golang.org/x/net/LICENSE
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/snappy/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2009 The Go Authors. All rights reserved.
+Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/siddontang/go/sync2/atomic.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/sync2/atomic.go
similarity index 99%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/siddontang/go/sync2/atomic.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/sync2/atomic.go
index 424a974..382fc20 100644
--- a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/siddontang/go/sync2/atomic.go
+++ b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/sync2/atomic.go
@@ -121,7 +121,7 @@ func (s *AtomicString) Get() string {
return str
}
-func (s *AtomicString) CompareAndSwap(oldval, newval string) (swqpped bool) {
+func (s *AtomicString) CompareAndSwap(oldval, newval string) (swapped bool) {
s.mu.Lock()
defer s.mu.Unlock()
if s.str == oldval {
diff --git a/vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/siddontang/go/sync2/semaphore.go b/vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/sync2/semaphore.go
similarity index 100%
rename from vendor/github.com/siddontang/go-mysql/_vendor/vendor/github.com/siddontang/go/sync2/semaphore.go
rename to vendor/github.com/siddontang/go-mysql/vendor/github.com/siddontang/go/sync2/semaphore.go
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/google.golang.org/appengine/LICENSE b/vendor/github.com/siddontang/go-mysql/vendor/google.golang.org/appengine/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/google.golang.org/appengine/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/google.golang.org/appengine/cloudsql/cloudsql.go b/vendor/github.com/siddontang/go-mysql/vendor/google.golang.org/appengine/cloudsql/cloudsql.go
new file mode 100644
index 0000000..7b27e6b
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/google.golang.org/appengine/cloudsql/cloudsql.go
@@ -0,0 +1,62 @@
+// Copyright 2013 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+/*
+Package cloudsql exposes access to Google Cloud SQL databases.
+
+This package does not work in App Engine "flexible environment".
+
+This package is intended for MySQL drivers to make App Engine-specific
+connections. Applications should use this package through database/sql:
+Select a pure Go MySQL driver that supports this package, and use sql.Open
+with protocol "cloudsql" and an address of the Cloud SQL instance.
+
+A Go MySQL driver that has been tested to work well with Cloud SQL
+is the go-sql-driver:
+ import "database/sql"
+ import _ "github.com/go-sql-driver/mysql"
+
+ db, err := sql.Open("mysql", "user@cloudsql(project-id:instance-name)/dbname")
+
+
+Another driver that works well with Cloud SQL is the mymysql driver:
+ import "database/sql"
+ import _ "github.com/ziutek/mymysql/godrv"
+
+ db, err := sql.Open("mymysql", "cloudsql:instance-name*dbname/user/password")
+
+
+Using either of these drivers, you can perform a standard SQL query.
+This example assumes there is a table named 'users' with
+columns 'first_name' and 'last_name':
+
+ rows, err := db.Query("SELECT first_name, last_name FROM users")
+ if err != nil {
+ log.Errorf(ctx, "db.Query: %v", err)
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var firstName string
+ var lastName string
+ if err := rows.Scan(&firstName, &lastName); err != nil {
+ log.Errorf(ctx, "rows.Scan: %v", err)
+ continue
+ }
+ log.Infof(ctx, "First: %v - Last: %v", firstName, lastName)
+ }
+ if err := rows.Err(); err != nil {
+ log.Errorf(ctx, "Row error: %v", err)
+ }
+*/
+package cloudsql
+
+import (
+ "net"
+)
+
+// Dial connects to the named Cloud SQL instance.
+func Dial(instance string) (net.Conn, error) {
+ return connect(instance)
+}
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/google.golang.org/appengine/cloudsql/cloudsql_classic.go b/vendor/github.com/siddontang/go-mysql/vendor/google.golang.org/appengine/cloudsql/cloudsql_classic.go
new file mode 100644
index 0000000..af62dba
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/google.golang.org/appengine/cloudsql/cloudsql_classic.go
@@ -0,0 +1,17 @@
+// Copyright 2013 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+// +build appengine
+
+package cloudsql
+
+import (
+ "net"
+
+ "appengine/cloudsql"
+)
+
+func connect(instance string) (net.Conn, error) {
+ return cloudsql.Dial(instance)
+}
diff --git a/vendor/github.com/siddontang/go-mysql/vendor/google.golang.org/appengine/cloudsql/cloudsql_vm.go b/vendor/github.com/siddontang/go-mysql/vendor/google.golang.org/appengine/cloudsql/cloudsql_vm.go
new file mode 100644
index 0000000..90fa7b3
--- /dev/null
+++ b/vendor/github.com/siddontang/go-mysql/vendor/google.golang.org/appengine/cloudsql/cloudsql_vm.go
@@ -0,0 +1,16 @@
+// Copyright 2013 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+// +build !appengine
+
+package cloudsql
+
+import (
+ "errors"
+ "net"
+)
+
+func connect(instance string) (net.Conn, error) {
+ return nil, errors.New(`cloudsql: not supported in App Engine "flexible environment"`)
+}