“wwangchuanjin 5 years ago
parent
commit
01a8b1c542

+ 92 - 0
src/github.com/go-sql-driver/mysql/CHANGELOG.md

@@ -0,0 +1,92 @@
+## HEAD
+
+Changes:
+
+ - Go 1.1 is no longer supported
+ - Use decimals field from MySQL to format time types (#249)
+ - Buffer optimizations (#269)
+ - TLS ServerName defaults to the host (#283)
+
+Bugfixes:
+
+ - Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249)
+ - Fixed handling of queries without columns and rows (#255)
+ - Fixed a panic when SetKeepAlive() failed (#298)
+
+New Features:
+ - Support for returning table alias on Columns() (#289)
+ - Placeholder interpolation, can be actived with the DSN parameter `interpolateParams=true` (#309, #318)
+
+
+## Version 1.2 (2014-06-03)
+
+Changes:
+
+ - We switched back to a "rolling release". `go get` installs the current master branch again
+ - Version v1 of the driver will not be maintained anymore. Go 1.0 is no longer supported by this driver
+ - Exported errors to allow easy checking from application code
+ - Enabled TCP Keepalives on TCP connections
+ - Optimized INFILE handling (better buffer size calculation, lazy init, ...)
+ - The DSN parser also checks for a missing separating slash
+ - Faster binary date / datetime to string formatting
+ - Also exported the MySQLWarning type
+ - mysqlConn.Close returns the first error encountered instead of ignoring all errors
+ - writePacket() automatically writes the packet size to the header
+ - readPacket() uses an iterative approach instead of the recursive approach to merge splitted packets
+
+New Features:
+
+ - `RegisterDial` allows the usage of a custom dial function to establish the network connection
+ - Setting the connection collation is possible with the `collation` DSN parameter. This parameter should be preferred over the `charset` parameter
+ - Logging of critical errors is configurable with `SetLogger`
+ - Google CloudSQL support
+
+Bugfixes:
+
+ - Allow more than 32 parameters in prepared statements
+ - Various old_password fixes
+ - Fixed TestConcurrent test to pass Go's race detection
+ - Fixed appendLengthEncodedInteger for large numbers
+ - Renamed readLengthEnodedString to readLengthEncodedString and skipLengthEnodedString to skipLengthEncodedString (fixed typo)
+
+
+## Version 1.1 (2013-11-02)
+
+Changes:
+
+  - Go-MySQL-Driver now requires Go 1.1
+  - Connections now use the collation `utf8_general_ci` by default. Adding `&charset=UTF8` to the DSN should not be necessary anymore
+  - Made closing rows and connections error tolerant. This allows for example deferring rows.Close() without checking for errors
+  - `[]byte(nil)` is now treated as a NULL value. Before, it was treated like an empty string / `[]byte("")`
+  - DSN parameter values must now be url.QueryEscape'ed. This allows text values to contain special characters, such as '&'.
+  - Use the IO buffer also for writing. This results in zero allocations (by the driver) for most queries
+  - Optimized the buffer for reading
+  - stmt.Query now caches column metadata
+  - New Logo
+  - Changed the copyright header to include all contributors
+  - Improved the LOAD INFILE documentation
+  - The driver struct is now exported to make the driver directly accessible
+  - Refactored the driver tests
+  - Added more benchmarks and moved all to a separate file
+  - Other small refactoring
+
+New Features:
+
+  - Added *old_passwords* support: Required in some cases, but must be enabled by adding `allowOldPasswords=true` to the DSN since it is insecure
+  - Added a `clientFoundRows` parameter: Return the number of matching rows instead of the number of rows changed on UPDATEs
+  - Added TLS/SSL support: Use a TLS/SSL encrypted connection to the server. Custom TLS configs can be registered and used
+
+Bugfixes:
+
+  - Fixed MySQL 4.1 support: MySQL 4.1 sends packets with lengths which differ from the specification
+  - Convert to DB timezone when inserting `time.Time`
+  - Splitted packets (more than 16MB) are now merged correctly
+  - Fixed false positive `io.EOF` errors when the data was fully read
+  - Avoid panics on reuse of closed connections
+  - Fixed empty string producing false nil values
+  - Fixed sign byte for positive TIME fields
+
+
+## Version 1.0 (2013-05-14)
+
+Initial Release

+ 40 - 0
src/github.com/go-sql-driver/mysql/CONTRIBUTING.md

@@ -0,0 +1,40 @@
+# Contributing Guidelines
+
+## Reporting Issues
+
+Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed).
+
+Please provide the following minimum information:
+* Your Go-MySQL-Driver version (or git SHA)
+* Your Go version (run `go version` in your console)
+* A detailed issue description
+* Error Log if present
+* If possible, a short example
+
+
+## Contributing Code
+
+By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file.
+Don't forget to add yourself to the AUTHORS file.
+
+### Pull Requests Checklist
+
+Please check the following points before submitting your pull request:
+- [x] Code compiles correctly
+- [x] Created tests, if possible
+- [x] All tests pass
+- [x] Extended the README / documentation, if necessary
+- [x] Added yourself to the AUTHORS file
+
+### Code Review
+
+Everyone is invited to review and comment on pull requests.
+If it looks fine to you, comment with "LGTM" (Looks good to me).
+
+If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes.
+
+Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM".
+
+## Development Ideas
+
+If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/go-sql-driver/mysql/wiki/Development-Ideas) Wiki page.

+ 374 - 0
src/github.com/go-sql-driver/mysql/README.md

@@ -0,0 +1,374 @@
+# Go-MySQL-Driver
+
+A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) package
+
+![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin")
+
+**Latest stable Release:** [Version 1.2 (June 03, 2014)](https://github.com/go-sql-driver/mysql/releases)
+
+[![Build Status](https://travis-ci.org/go-sql-driver/mysql.png?branch=master)](https://travis-ci.org/go-sql-driver/mysql)
+
+---------------------------------------
+  * [Features](#features)
+  * [Requirements](#requirements)
+  * [Installation](#installation)
+  * [Usage](#usage)
+    * [DSN (Data Source Name)](#dsn-data-source-name)
+      * [Password](#password)
+      * [Protocol](#protocol)
+      * [Address](#address)
+      * [Parameters](#parameters)
+      * [Examples](#examples)
+    * [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support)
+    * [time.Time support](#timetime-support)
+    * [Unicode support](#unicode-support)
+  * [Testing / Development](#testing--development)
+  * [License](#license)
+
+---------------------------------------
+
+## Features
+  * Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance")
+  * Native Go implementation. No C-bindings, just pure Go
+  * Connections over TCP/IPv4, TCP/IPv6 or Unix domain sockets
+  * Automatic handling of broken connections
+  * Automatic Connection Pooling *(by database/sql package)*
+  * Supports queries larger than 16MB
+  * Full [`sql.RawBytes`](http://golang.org/pkg/database/sql/#RawBytes) support.
+  * Intelligent `LONG DATA` handling in prepared statements
+  * Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support
+  * Optional `time.Time` parsing
+  * Optional placeholder interpolation
+
+## Requirements
+  * Go 1.2 or higher
+  * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
+
+---------------------------------------
+
+## Installation
+Simple install the package to your [$GOPATH](http://code.google.com/p/go-wiki/wiki/GOPATH "GOPATH") with the [go tool](http://golang.org/cmd/go/ "go command") from shell:
+```bash
+$ go get github.com/go-sql-driver/mysql
+```
+Make sure [Git is installed](http://git-scm.com/downloads) on your machine and in your system's `PATH`.
+
+## Usage
+_Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](http://golang.org/pkg/database/sql) API then.
+
+Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name)  as `dataSourceName`:
+```go
+import "database/sql"
+import _ "github.com/go-sql-driver/mysql"
+
+db, err := sql.Open("mysql", "user:password@/dbname")
+```
+
+[Examples are available in our Wiki](https://github.com/go-sql-driver/mysql/wiki/Examples "Go-MySQL-Driver Examples").
+
+
+### DSN (Data Source Name)
+
+The Data Source Name has a common format, like e.g. [PEAR DB](http://pear.php.net/manual/en/package.database.db.intro-dsn.php) uses it, but without type-prefix (optional parts marked by squared brackets):
+```
+[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
+```
+
+A DSN in its fullest form:
+```
+username:password@protocol(address)/dbname?param=value
+```
+
+Except for the databasename, all values are optional. So the minimal DSN is:
+```
+/dbname
+```
+
+If you do not want to preselect a database, leave `dbname` empty:
+```
+/
+```
+This has the same effect as an empty DSN string:
+```
+
+```
+
+#### Password
+Passwords can consist of any character. Escaping is **not** necessary.
+
+#### Protocol
+See [net.Dial](http://golang.org/pkg/net/#Dial) for more information which networks are available.
+In general you should use an Unix domain socket if available and TCP otherwise for best performance.
+
+#### Address
+For TCP and UDP networks, addresses have the form `host:port`.
+If `host` is a literal IPv6 address, it must be enclosed in square brackets.
+The functions [net.JoinHostPort](http://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](http://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
+
+For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`.
+
+#### Parameters
+*Parameters are case-sensitive!*
+
+Notice that any of `true`, `TRUE`, `True` or `1` is accepted to stand for a true boolean value. Not surprisingly, false can be specified as any of: `false`, `FALSE`, `False` or `0`.
+
+##### `allowAllFiles`
+
+```
+Type:           bool
+Valid Values:   true, false
+Default:        false
+```
+
+`allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files.
+[*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
+
+##### `allowOldPasswords`
+
+```
+Type:           bool
+Valid Values:   true, false
+Default:        false
+```
+`allowOldPasswords=true` allows the usage of the insecure old password method. This should be avoided, but is necessary in some cases. See also [the old_passwords wiki page](https://github.com/go-sql-driver/mysql/wiki/old_passwords).
+
+##### `charset`
+
+```
+Type:           string
+Valid Values:   <name>
+Default:        none
+```
+
+Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
+
+Usage of the `charset` parameter is discouraged because it issues additional queries to the server.
+Unless you need the fallback behavior, please use `collation` instead.
+
+##### `collation`
+
+```
+Type:           string
+Valid Values:   <name>
+Default:        utf8_general_ci
+```
+
+Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail.
+
+A list of valid charsets for a server is retrievable with `SHOW COLLATION`.
+
+##### `clientFoundRows`
+
+```
+Type:           bool
+Valid Values:   true, false
+Default:        false
+```
+
+`clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed.
+
+##### `columnsWithAlias`
+
+```
+Type:           bool
+Valid Values:   true, false
+Default:        false
+```
+
+When `columnsWithAlias` is true, calls to `sql.Rows.Columns()` will return the table alias and the column name separated by a dot. For example:
+
+```
+SELECT u.id FROM users as u
+```
+
+will return `u.id` instead of just `id` if `columnsWithAlias=true`.
+
+##### `interpolateParams`
+
+```
+Type:           bool
+Valid Values:   true, false
+Default:        false
+```
+
+If `interpolateParams` is true, placeholders (`?`) in calls to `db.Query()` and `db.Exec()` are interpolated into a single query string with given parameters. This reduces the number of roundtrips, since the driver has to prepare a statement, execute it with given parameters and close the statement again with `interpolateParams=false`.
+
+*This can not be used together with the multibyte encodings BIG5, CP932, GB2312, GBK or SJIS. These are blacklisted as they may [introduce a SQL injection vulnerability](http://stackoverflow.com/a/12118602/3430118)!*
+
+##### `loc`
+
+```
+Type:           string
+Valid Values:   <escaped name>
+Default:        UTC
+```
+
+Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details.
+
+Please keep in mind, that param values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`.
+
+
+##### `parseTime`
+
+```
+Type:           bool
+Valid Values:   true, false
+Default:        false
+```
+
+`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
+
+
+##### `strict`
+
+```
+Type:           bool
+Valid Values:   true, false
+Default:        false
+```
+
+`strict=true` enables the strict mode in which MySQL warnings are treated as errors.
+
+By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_notes) to ignore notes. See the [examples](#examples) for an DSN example.
+
+
+##### `timeout`
+
+```
+Type:           decimal number
+Default:        OS default
+```
+
+*Driver* side connection timeout. The value must be a string of decimal numbers, each with optional fraction and a unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout).
+
+
+##### `tls`
+
+```
+Type:           bool / string
+Valid Values:   true, false, skip-verify, <name>
+Default:        false
+```
+
+`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](http://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
+
+
+##### System Variables
+
+All other parameters are interpreted as system variables:
+  * `autocommit`: `"SET autocommit=<value>"`
+  * `time_zone`: `"SET time_zone=<value>"`
+  * [`tx_isolation`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `"SET tx_isolation=<value>"`
+  * `param`: `"SET <param>=<value>"`
+
+*The values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!*
+
+#### Examples
+```
+user@unix(/path/to/socket)/dbname
+```
+
+```
+root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local
+```
+
+```
+user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true
+```
+
+Use the [strict mode](#strict) but ignore notes:
+```
+user:password@/dbname?strict=true&sql_notes=false
+```
+
+TCP via IPv6:
+```
+user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s&collation=utf8mb4_unicode_ci
+```
+
+TCP on a remote host, e.g. Amazon RDS:
+```
+id:password@tcp(your-amazonaws-uri.com:3306)/dbname
+```
+
+Google Cloud SQL on App Engine:
+```
+user@cloudsql(project-id:instance-name)/dbname
+```
+
+TCP using default port (3306) on localhost:
+```
+user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped
+```
+
+Use the default protocol (tcp) and host (localhost:3306):
+```
+user:password@/dbname
+```
+
+No Database preselected:
+```
+user:password@/
+```
+
+### `LOAD DATA LOCAL INFILE` support
+For this feature you need direct access to the package. Therefore you must change the import path (no `_`):
+```go
+import "github.com/go-sql-driver/mysql"
+```
+
+Files must be whitelisted by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the Whitelist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)).
+
+To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then.
+
+See the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
+
+
+### `time.Time` support
+The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your programm.
+
+However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](http://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
+
+**Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
+
+Alternatively you can use the [`NullTime`](http://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`.
+
+
+### Unicode support
+Since version 1.1 Go-MySQL-Driver automatically uses the collation `utf8_general_ci` by default.
+
+Other collations / charsets can be set using the [`collation`](#collation) DSN parameter.
+
+Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAMES utf8`) to the DSN to enable proper UTF-8 support. This is not necessary anymore. The [`collation`](#collation) parameter should be preferred to set another collation / charset than the default.
+
+See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
+
+
+## Testing / Development
+To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
+
+Go-MySQL-Driver is not feature-complete yet. Your help is very appreciated.
+If you want to contribute, you can work on an [open issue](https://github.com/go-sql-driver/mysql/issues?state=open) or review a [pull request](https://github.com/go-sql-driver/mysql/pulls).
+
+See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/CONTRIBUTING.md) for details.
+
+---------------------------------------
+
+## License
+Go-MySQL-Driver is licensed under the [Mozilla Public License Version 2.0](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
+
+Mozilla summarizes the license scope as follows:
+> MPL: The copyleft applies to any files containing MPLed code.
+
+
+That means:
+  * You can **use** the **unchanged** source code both in private and commercially
+  * When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0)
+  * You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**
+
+Please read the [MPL 2.0 FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html) if you have further questions regarding the license.
+
+You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
+
+![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow")
+

+ 19 - 0
src/github.com/go-sql-driver/mysql/appengine.go

@@ -0,0 +1,19 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2013 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/.
+
+// +build appengine
+
+package mysql
+
+import (
+	"appengine/cloudsql"
+)
+
+func init() {
+	RegisterDial("cloudsql", cloudsql.Dial)
+}

+ 136 - 0
src/github.com/go-sql-driver/mysql/buffer.go

@@ -0,0 +1,136 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2013 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 "io"
+
+const defaultBufSize = 4096
+
+// A buffer which is used for both reading and writing.
+// This is possible since communication on each connection is synchronous.
+// In other words, we can't write and read simultaneously on the same connection.
+// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
+// Also highly optimized for this particular use case.
+type buffer struct {
+	buf    []byte
+	rd     io.Reader
+	idx    int
+	length int
+}
+
+func newBuffer(rd io.Reader) buffer {
+	var b [defaultBufSize]byte
+	return buffer{
+		buf: b[:],
+		rd:  rd,
+	}
+}
+
+// fill reads into the buffer until at least _need_ bytes are in it
+func (b *buffer) fill(need int) error {
+	n := b.length
+
+	// move existing data to the beginning
+	if n > 0 && b.idx > 0 {
+		copy(b.buf[0:n], b.buf[b.idx:])
+	}
+
+	// grow buffer if necessary
+	// TODO: let the buffer shrink again at some point
+	//       Maybe keep the org buf slice and swap back?
+	if need > len(b.buf) {
+		// Round up to the next multiple of the default size
+		newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
+		copy(newBuf, b.buf)
+		b.buf = newBuf
+	}
+
+	b.idx = 0
+
+	for {
+		nn, err := b.rd.Read(b.buf[n:])
+		n += nn
+
+		switch err {
+		case nil:
+			if n < need {
+				continue
+			}
+			b.length = n
+			return nil
+
+		case io.EOF:
+			if n >= need {
+				b.length = n
+				return nil
+			}
+			return io.ErrUnexpectedEOF
+
+		default:
+			return err
+		}
+	}
+}
+
+// returns next N bytes from buffer.
+// The returned slice is only guaranteed to be valid until the next read
+func (b *buffer) readNext(need int) ([]byte, error) {
+	if b.length < need {
+		// refill
+		if err := b.fill(need); err != nil {
+			return nil, err
+		}
+	}
+
+	offset := b.idx
+	b.idx += need
+	b.length -= need
+	return b.buf[offset:b.idx], nil
+}
+
+// returns a buffer with the requested size.
+// If possible, a slice from the existing buffer is returned.
+// Otherwise a bigger buffer is made.
+// Only one buffer (total) can be used at a time.
+func (b *buffer) takeBuffer(length int) []byte {
+	if b.length > 0 {
+		return nil
+	}
+
+	// test (cheap) general case first
+	if length <= defaultBufSize || length <= cap(b.buf) {
+		return b.buf[:length]
+	}
+
+	if length < maxPacketSize {
+		b.buf = make([]byte, length)
+		return b.buf
+	}
+	return make([]byte, length)
+}
+
+// shortcut which can be used if the requested buffer is guaranteed to be
+// 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]
+	}
+	return nil
+}
+
+// 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
+	}
+	return nil
+}

+ 250 - 0
src/github.com/go-sql-driver/mysql/collations.go

@@ -0,0 +1,250 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2014 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
+
+const defaultCollation byte = 33 // utf8_general_ci
+
+// A list of available collations mapped to the internal ID.
+// To update this map use the following MySQL query:
+//     SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS
+var collations = map[string]byte{
+	"big5_chinese_ci":          1,
+	"latin2_czech_cs":          2,
+	"dec8_swedish_ci":          3,
+	"cp850_general_ci":         4,
+	"latin1_german1_ci":        5,
+	"hp8_english_ci":           6,
+	"koi8r_general_ci":         7,
+	"latin1_swedish_ci":        8,
+	"latin2_general_ci":        9,
+	"swe7_swedish_ci":          10,
+	"ascii_general_ci":         11,
+	"ujis_japanese_ci":         12,
+	"sjis_japanese_ci":         13,
+	"cp1251_bulgarian_ci":      14,
+	"latin1_danish_ci":         15,
+	"hebrew_general_ci":        16,
+	"tis620_thai_ci":           18,
+	"euckr_korean_ci":          19,
+	"latin7_estonian_cs":       20,
+	"latin2_hungarian_ci":      21,
+	"koi8u_general_ci":         22,
+	"cp1251_ukrainian_ci":      23,
+	"gb2312_chinese_ci":        24,
+	"greek_general_ci":         25,
+	"cp1250_general_ci":        26,
+	"latin2_croatian_ci":       27,
+	"gbk_chinese_ci":           28,
+	"cp1257_lithuanian_ci":     29,
+	"latin5_turkish_ci":        30,
+	"latin1_german2_ci":        31,
+	"armscii8_general_ci":      32,
+	"utf8_general_ci":          33,
+	"cp1250_czech_cs":          34,
+	"ucs2_general_ci":          35,
+	"cp866_general_ci":         36,
+	"keybcs2_general_ci":       37,
+	"macce_general_ci":         38,
+	"macroman_general_ci":      39,
+	"cp852_general_ci":         40,
+	"latin7_general_ci":        41,
+	"latin7_general_cs":        42,
+	"macce_bin":                43,
+	"cp1250_croatian_ci":       44,
+	"utf8mb4_general_ci":       45,
+	"utf8mb4_bin":              46,
+	"latin1_bin":               47,
+	"latin1_general_ci":        48,
+	"latin1_general_cs":        49,
+	"cp1251_bin":               50,
+	"cp1251_general_ci":        51,
+	"cp1251_general_cs":        52,
+	"macroman_bin":             53,
+	"utf16_general_ci":         54,
+	"utf16_bin":                55,
+	"utf16le_general_ci":       56,
+	"cp1256_general_ci":        57,
+	"cp1257_bin":               58,
+	"cp1257_general_ci":        59,
+	"utf32_general_ci":         60,
+	"utf32_bin":                61,
+	"utf16le_bin":              62,
+	"binary":                   63,
+	"armscii8_bin":             64,
+	"ascii_bin":                65,
+	"cp1250_bin":               66,
+	"cp1256_bin":               67,
+	"cp866_bin":                68,
+	"dec8_bin":                 69,
+	"greek_bin":                70,
+	"hebrew_bin":               71,
+	"hp8_bin":                  72,
+	"keybcs2_bin":              73,
+	"koi8r_bin":                74,
+	"koi8u_bin":                75,
+	"latin2_bin":               77,
+	"latin5_bin":               78,
+	"latin7_bin":               79,
+	"cp850_bin":                80,
+	"cp852_bin":                81,
+	"swe7_bin":                 82,
+	"utf8_bin":                 83,
+	"big5_bin":                 84,
+	"euckr_bin":                85,
+	"gb2312_bin":               86,
+	"gbk_bin":                  87,
+	"sjis_bin":                 88,
+	"tis620_bin":               89,
+	"ucs2_bin":                 90,
+	"ujis_bin":                 91,
+	"geostd8_general_ci":       92,
+	"geostd8_bin":              93,
+	"latin1_spanish_ci":        94,
+	"cp932_japanese_ci":        95,
+	"cp932_bin":                96,
+	"eucjpms_japanese_ci":      97,
+	"eucjpms_bin":              98,
+	"cp1250_polish_ci":         99,
+	"utf16_unicode_ci":         101,
+	"utf16_icelandic_ci":       102,
+	"utf16_latvian_ci":         103,
+	"utf16_romanian_ci":        104,
+	"utf16_slovenian_ci":       105,
+	"utf16_polish_ci":          106,
+	"utf16_estonian_ci":        107,
+	"utf16_spanish_ci":         108,
+	"utf16_swedish_ci":         109,
+	"utf16_turkish_ci":         110,
+	"utf16_czech_ci":           111,
+	"utf16_danish_ci":          112,
+	"utf16_lithuanian_ci":      113,
+	"utf16_slovak_ci":          114,
+	"utf16_spanish2_ci":        115,
+	"utf16_roman_ci":           116,
+	"utf16_persian_ci":         117,
+	"utf16_esperanto_ci":       118,
+	"utf16_hungarian_ci":       119,
+	"utf16_sinhala_ci":         120,
+	"utf16_german2_ci":         121,
+	"utf16_croatian_ci":        122,
+	"utf16_unicode_520_ci":     123,
+	"utf16_vietnamese_ci":      124,
+	"ucs2_unicode_ci":          128,
+	"ucs2_icelandic_ci":        129,
+	"ucs2_latvian_ci":          130,
+	"ucs2_romanian_ci":         131,
+	"ucs2_slovenian_ci":        132,
+	"ucs2_polish_ci":           133,
+	"ucs2_estonian_ci":         134,
+	"ucs2_spanish_ci":          135,
+	"ucs2_swedish_ci":          136,
+	"ucs2_turkish_ci":          137,
+	"ucs2_czech_ci":            138,
+	"ucs2_danish_ci":           139,
+	"ucs2_lithuanian_ci":       140,
+	"ucs2_slovak_ci":           141,
+	"ucs2_spanish2_ci":         142,
+	"ucs2_roman_ci":            143,
+	"ucs2_persian_ci":          144,
+	"ucs2_esperanto_ci":        145,
+	"ucs2_hungarian_ci":        146,
+	"ucs2_sinhala_ci":          147,
+	"ucs2_german2_ci":          148,
+	"ucs2_croatian_ci":         149,
+	"ucs2_unicode_520_ci":      150,
+	"ucs2_vietnamese_ci":       151,
+	"ucs2_general_mysql500_ci": 159,
+	"utf32_unicode_ci":         160,
+	"utf32_icelandic_ci":       161,
+	"utf32_latvian_ci":         162,
+	"utf32_romanian_ci":        163,
+	"utf32_slovenian_ci":       164,
+	"utf32_polish_ci":          165,
+	"utf32_estonian_ci":        166,
+	"utf32_spanish_ci":         167,
+	"utf32_swedish_ci":         168,
+	"utf32_turkish_ci":         169,
+	"utf32_czech_ci":           170,
+	"utf32_danish_ci":          171,
+	"utf32_lithuanian_ci":      172,
+	"utf32_slovak_ci":          173,
+	"utf32_spanish2_ci":        174,
+	"utf32_roman_ci":           175,
+	"utf32_persian_ci":         176,
+	"utf32_esperanto_ci":       177,
+	"utf32_hungarian_ci":       178,
+	"utf32_sinhala_ci":         179,
+	"utf32_german2_ci":         180,
+	"utf32_croatian_ci":        181,
+	"utf32_unicode_520_ci":     182,
+	"utf32_vietnamese_ci":      183,
+	"utf8_unicode_ci":          192,
+	"utf8_icelandic_ci":        193,
+	"utf8_latvian_ci":          194,
+	"utf8_romanian_ci":         195,
+	"utf8_slovenian_ci":        196,
+	"utf8_polish_ci":           197,
+	"utf8_estonian_ci":         198,
+	"utf8_spanish_ci":          199,
+	"utf8_swedish_ci":          200,
+	"utf8_turkish_ci":          201,
+	"utf8_czech_ci":            202,
+	"utf8_danish_ci":           203,
+	"utf8_lithuanian_ci":       204,
+	"utf8_slovak_ci":           205,
+	"utf8_spanish2_ci":         206,
+	"utf8_roman_ci":            207,
+	"utf8_persian_ci":          208,
+	"utf8_esperanto_ci":        209,
+	"utf8_hungarian_ci":        210,
+	"utf8_sinhala_ci":          211,
+	"utf8_german2_ci":          212,
+	"utf8_croatian_ci":         213,
+	"utf8_unicode_520_ci":      214,
+	"utf8_vietnamese_ci":       215,
+	"utf8_general_mysql500_ci": 223,
+	"utf8mb4_unicode_ci":       224,
+	"utf8mb4_icelandic_ci":     225,
+	"utf8mb4_latvian_ci":       226,
+	"utf8mb4_romanian_ci":      227,
+	"utf8mb4_slovenian_ci":     228,
+	"utf8mb4_polish_ci":        229,
+	"utf8mb4_estonian_ci":      230,
+	"utf8mb4_spanish_ci":       231,
+	"utf8mb4_swedish_ci":       232,
+	"utf8mb4_turkish_ci":       233,
+	"utf8mb4_czech_ci":         234,
+	"utf8mb4_danish_ci":        235,
+	"utf8mb4_lithuanian_ci":    236,
+	"utf8mb4_slovak_ci":        237,
+	"utf8mb4_spanish2_ci":      238,
+	"utf8mb4_roman_ci":         239,
+	"utf8mb4_persian_ci":       240,
+	"utf8mb4_esperanto_ci":     241,
+	"utf8mb4_hungarian_ci":     242,
+	"utf8mb4_sinhala_ci":       243,
+	"utf8mb4_german2_ci":       244,
+	"utf8mb4_croatian_ci":      245,
+	"utf8mb4_unicode_520_ci":   246,
+	"utf8mb4_vietnamese_ci":    247,
+}
+
+// A blacklist of collations which is unsafe to interpolate parameters.
+// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
+var unsafeCollations = map[byte]bool{
+	1:  true, // big5_chinese_ci
+	13: true, // sjis_japanese_ci
+	28: true, // gbk_chinese_ci
+	84: true, // big5_bin
+	86: true, // gb2312_bin
+	87: true, // gbk_bin
+	88: true, // sjis_bin
+	95: true, // cp932_japanese_ci
+	96: true, // cp932_bin
+}

+ 402 - 0
src/github.com/go-sql-driver/mysql/connection.go

@@ -0,0 +1,402 @@
+// 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 (
+	"crypto/tls"
+	"database/sql/driver"
+	"errors"
+	"net"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type mysqlConn struct {
+	buf              buffer
+	netConn          net.Conn
+	affectedRows     uint64
+	insertId         uint64
+	cfg              *config
+	maxPacketAllowed int
+	maxWriteSize     int
+	flags            clientFlag
+	status           statusFlag
+	sequence         uint8
+	parseTime        bool
+	strict           bool
+}
+
+type config struct {
+	user              string
+	passwd            string
+	net               string
+	addr              string
+	dbname            string
+	params            map[string]string
+	loc               *time.Location
+	tls               *tls.Config
+	timeout           time.Duration
+	collation         uint8
+	allowAllFiles     bool
+	allowOldPasswords bool
+	clientFoundRows   bool
+	columnsWithAlias  bool
+	interpolateParams bool
+}
+
+// Handles parameters set in DSN after the connection is established
+func (mc *mysqlConn) handleParams() (err error) {
+	for param, val := range mc.cfg.params {
+		switch param {
+		// Charset
+		case "charset":
+			charsets := strings.Split(val, ",")
+			for i := range charsets {
+				// ignore errors here - a charset may not exist
+				err = mc.exec("SET NAMES " + charsets[i])
+				if err == nil {
+					break
+				}
+			}
+			if err != nil {
+				return
+			}
+
+		// time.Time parsing
+		case "parseTime":
+			var isBool bool
+			mc.parseTime, isBool = readBool(val)
+			if !isBool {
+				return errors.New("Invalid Bool value: " + val)
+			}
+
+		// Strict mode
+		case "strict":
+			var isBool bool
+			mc.strict, isBool = readBool(val)
+			if !isBool {
+				return errors.New("Invalid Bool value: " + val)
+			}
+
+		// Compression
+		case "compress":
+			err = errors.New("Compression not implemented yet")
+			return
+
+		// System Vars
+		default:
+			err = mc.exec("SET " + param + "=" + val + "")
+			if err != nil {
+				return
+			}
+		}
+	}
+
+	return
+}
+
+func (mc *mysqlConn) Begin() (driver.Tx, error) {
+	if mc.netConn == nil {
+		errLog.Print(ErrInvalidConn)
+		return nil, driver.ErrBadConn
+	}
+	err := mc.exec("START TRANSACTION")
+	if err == nil {
+		return &mysqlTx{mc}, err
+	}
+
+	return nil, err
+}
+
+func (mc *mysqlConn) Close() (err error) {
+	// Makes Close idempotent
+	if mc.netConn != nil {
+		err = mc.writeCommandPacket(comQuit)
+		if err == nil {
+			err = mc.netConn.Close()
+		} else {
+			mc.netConn.Close()
+		}
+		mc.netConn = nil
+	}
+
+	mc.cfg = nil
+	mc.buf.rd = nil
+
+	return
+}
+
+func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
+	if mc.netConn == nil {
+		errLog.Print(ErrInvalidConn)
+		return nil, driver.ErrBadConn
+	}
+	// Send command
+	err := mc.writeCommandPacketStr(comStmtPrepare, query)
+	if err != nil {
+		return nil, err
+	}
+
+	stmt := &mysqlStmt{
+		mc: mc,
+	}
+
+	// Read Result
+	columnCount, err := stmt.readPrepareResultPacket()
+	if err == nil {
+		if stmt.paramCount > 0 {
+			if err = mc.readUntilEOF(); err != nil {
+				return nil, err
+			}
+		}
+
+		if columnCount > 0 {
+			err = mc.readUntilEOF()
+		}
+	}
+
+	return stmt, err
+}
+
+func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) {
+	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
+	}
+	buf = buf[:0]
+	argPos := 0
+
+	for i := 0; i < len(query); i++ {
+		q := strings.IndexByte(query[i:], '?')
+		if q == -1 {
+			buf = append(buf, query[i:]...)
+			break
+		}
+		buf = append(buf, query[i:i+q]...)
+		i += q
+
+		arg := args[argPos]
+		argPos++
+
+		if arg == nil {
+			buf = append(buf, "NULL"...)
+			continue
+		}
+
+		switch v := arg.(type) {
+		case int64:
+			buf = strconv.AppendInt(buf, v, 10)
+		case float64:
+			buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
+		case bool:
+			if v {
+				buf = append(buf, '1')
+			} else {
+				buf = append(buf, '0')
+			}
+		case time.Time:
+			if v.IsZero() {
+				buf = append(buf, "'0000-00-00'"...)
+			} else {
+				v := v.In(mc.cfg.loc)
+				v = v.Add(time.Nanosecond * 500) // To round under microsecond
+				year := v.Year()
+				year100 := year / 100
+				year1 := year % 100
+				month := v.Month()
+				day := v.Day()
+				hour := v.Hour()
+				minute := v.Minute()
+				second := v.Second()
+				micro := v.Nanosecond() / 1000
+
+				buf = append(buf, []byte{
+					'\'',
+					digits10[year100], digits01[year100],
+					digits10[year1], digits01[year1],
+					'-',
+					digits10[month], digits01[month],
+					'-',
+					digits10[day], digits01[day],
+					' ',
+					digits10[hour], digits01[hour],
+					':',
+					digits10[minute], digits01[minute],
+					':',
+					digits10[second], digits01[second],
+				}...)
+
+				if micro != 0 {
+					micro10000 := micro / 10000
+					micro100 := micro / 100 % 100
+					micro1 := micro % 100
+					buf = append(buf, []byte{
+						'.',
+						digits10[micro10000], digits01[micro10000],
+						digits10[micro100], digits01[micro100],
+						digits10[micro1], digits01[micro1],
+					}...)
+				}
+				buf = append(buf, '\'')
+			}
+		case []byte:
+			if v == nil {
+				buf = append(buf, "NULL"...)
+			} else {
+				buf = append(buf, '\'')
+				if mc.status&statusNoBackslashEscapes == 0 {
+					buf = escapeBytesBackslash(buf, v)
+				} else {
+					buf = escapeBytesQuotes(buf, v)
+				}
+				buf = append(buf, '\'')
+			}
+		case string:
+			buf = append(buf, '\'')
+			if mc.status&statusNoBackslashEscapes == 0 {
+				buf = escapeStringBackslash(buf, v)
+			} else {
+				buf = escapeStringQuotes(buf, v)
+			}
+			buf = append(buf, '\'')
+		default:
+			return "", driver.ErrSkip
+		}
+
+		if len(buf)+4 > mc.maxPacketAllowed {
+			return "", driver.ErrSkip
+		}
+	}
+	if argPos != len(args) {
+		return "", driver.ErrSkip
+	}
+	return string(buf), nil
+}
+
+func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
+	if mc.netConn == nil {
+		errLog.Print(ErrInvalidConn)
+		return nil, driver.ErrBadConn
+	}
+	if len(args) != 0 {
+		if !mc.cfg.interpolateParams {
+			return nil, driver.ErrSkip
+		}
+		// try to interpolate the parameters to save extra roundtrips for preparing and closing a statement
+		prepared, err := mc.interpolateParams(query, args)
+		if err != nil {
+			return nil, err
+		}
+		query = prepared
+		args = nil
+	}
+	mc.affectedRows = 0
+	mc.insertId = 0
+
+	err := mc.exec(query)
+	if err == nil {
+		return &mysqlResult{
+			affectedRows: int64(mc.affectedRows),
+			insertId:     int64(mc.insertId),
+		}, err
+	}
+	return nil, 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
+	}
+
+	// Read Result
+	resLen, err := mc.readResultSetHeaderPacket()
+	if err == nil && resLen > 0 {
+		if err = mc.readUntilEOF(); err != nil {
+			return err
+		}
+
+		err = mc.readUntilEOF()
+	}
+
+	return err
+}
+
+func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
+	if mc.netConn == nil {
+		errLog.Print(ErrInvalidConn)
+		return nil, driver.ErrBadConn
+	}
+	if len(args) != 0 {
+		if !mc.cfg.interpolateParams {
+			return nil, driver.ErrSkip
+		}
+		// try client-side prepare to reduce roundtrip
+		prepared, err := mc.interpolateParams(query, args)
+		if err != nil {
+			return nil, err
+		}
+		query = prepared
+		args = nil
+	}
+	// Send command
+	err := mc.writeCommandPacketStr(comQuery, query)
+	if err == nil {
+		// Read Result
+		var resLen int
+		resLen, err = mc.readResultSetHeaderPacket()
+		if err == nil {
+			rows := new(textRows)
+			rows.mc = mc
+
+			if resLen == 0 {
+				// no columns, no more data
+				return emptyRows{}, nil
+			}
+			// Columns
+			rows.columns, err = mc.readColumns(resLen)
+			return rows, err
+		}
+	}
+	return nil, err
+}
+
+// Gets the value of the given MySQL System Variable
+// The returned byte slice is only valid until the next read
+func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
+	// Send command
+	if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil {
+		return nil, err
+	}
+
+	// Read Result
+	resLen, err := mc.readResultSetHeaderPacket()
+	if err == nil {
+		rows := new(textRows)
+		rows.mc = mc
+
+		if resLen > 0 {
+			// Columns
+			if err := mc.readUntilEOF(); err != nil {
+				return nil, err
+			}
+		}
+
+		dest := make([]driver.Value, resLen)
+		if err = rows.readRow(dest); err == nil {
+			return dest[0].([]byte), mc.readUntilEOF()
+		}
+	}
+	return nil, err
+}

+ 154 - 0
src/github.com/go-sql-driver/mysql/const.go

@@ -0,0 +1,154 @@
+// 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
+
+const (
+	minProtocolVersion byte = 10
+	maxPacketSize           = 1<<24 - 1
+	timeFormat              = "2006-01-02 15:04:05.999999"
+)
+
+// MySQL constants documentation:
+// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
+
+const (
+	iOK          byte = 0x00
+	iLocalInFile byte = 0xfb
+	iEOF         byte = 0xfe
+	iERR         byte = 0xff
+)
+
+type clientFlag uint32
+
+const (
+	clientLongPassword clientFlag = 1 << iota
+	clientFoundRows
+	clientLongFlag
+	clientConnectWithDB
+	clientNoSchema
+	clientCompress
+	clientODBC
+	clientLocalFiles
+	clientIgnoreSpace
+	clientProtocol41
+	clientInteractive
+	clientSSL
+	clientIgnoreSIGPIPE
+	clientTransactions
+	clientReserved
+	clientSecureConn
+	clientMultiStatements
+	clientMultiResults
+)
+
+const (
+	comQuit byte = iota + 1
+	comInitDB
+	comQuery
+	comFieldList
+	comCreateDB
+	comDropDB
+	comRefresh
+	comShutdown
+	comStatistics
+	comProcessInfo
+	comConnect
+	comProcessKill
+	comDebug
+	comPing
+	comTime
+	comDelayedInsert
+	comChangeUser
+	comBinlogDump
+	comTableDump
+	comConnectOut
+	comRegisterSlave
+	comStmtPrepare
+	comStmtExecute
+	comStmtSendLongData
+	comStmtClose
+	comStmtReset
+	comSetOption
+	comStmtFetch
+)
+
+const (
+	fieldTypeDecimal byte = iota
+	fieldTypeTiny
+	fieldTypeShort
+	fieldTypeLong
+	fieldTypeFloat
+	fieldTypeDouble
+	fieldTypeNULL
+	fieldTypeTimestamp
+	fieldTypeLongLong
+	fieldTypeInt24
+	fieldTypeDate
+	fieldTypeTime
+	fieldTypeDateTime
+	fieldTypeYear
+	fieldTypeNewDate
+	fieldTypeVarChar
+	fieldTypeBit
+)
+const (
+	fieldTypeNewDecimal byte = iota + 0xf6
+	fieldTypeEnum
+	fieldTypeSet
+	fieldTypeTinyBLOB
+	fieldTypeMediumBLOB
+	fieldTypeLongBLOB
+	fieldTypeBLOB
+	fieldTypeVarString
+	fieldTypeString
+	fieldTypeGeometry
+)
+
+type fieldFlag uint16
+
+const (
+	flagNotNULL fieldFlag = 1 << iota
+	flagPriKey
+	flagUniqueKey
+	flagMultipleKey
+	flagBLOB
+	flagUnsigned
+	flagZeroFill
+	flagBinary
+	flagEnum
+	flagAutoIncrement
+	flagTimestamp
+	flagSet
+	flagUnknown1
+	flagUnknown2
+	flagUnknown3
+	flagUnknown4
+)
+
+// http://dev.mysql.com/doc/internals/en/status-flags.html
+
+type statusFlag uint16
+
+const (
+	statusInTrans statusFlag = 1 << iota
+	statusInAutocommit
+	statusReserved // Not in documentation
+	statusMoreResultsExists
+	statusNoGoodIndexUsed
+	statusNoIndexUsed
+	statusCursorExists
+	statusLastRowSent
+	statusDbDropped
+	statusNoBackslashEscapes
+	statusMetadataChanged
+	statusQueryWasSlow
+	statusPsOutParams
+	statusInTransReadonly
+	statusSessionStateChanged
+)

+ 140 - 0
src/github.com/go-sql-driver/mysql/driver.go

@@ -0,0 +1,140 @@
+// 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/.
+
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// The driver should be used via the database/sql package:
+//
+//  import "database/sql"
+//  import _ "github.com/go-sql-driver/mysql"
+//
+//  db, err := sql.Open("mysql", "user:password@/dbname")
+//
+// See https://github.com/go-sql-driver/mysql#usage for details
+package mysql
+
+import (
+	"database/sql"
+	"database/sql/driver"
+	"net"
+)
+
+// This struct is exported to make the driver directly accessible.
+// In general the driver is used via the database/sql package.
+type MySQLDriver struct{}
+
+// DialFunc is a function which can be used to establish the network connection.
+// Custom dial functions must be registered with RegisterDial
+type DialFunc func(addr string) (net.Conn, error)
+
+var 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) {
+	if dials == nil {
+		dials = make(map[string]DialFunc)
+	}
+	dials[net] = dial
+}
+
+// Open new Connection.
+// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
+// the DSN string is formated
+func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
+	var err error
+
+	// New mysqlConn
+	mc := &mysqlConn{
+		maxPacketAllowed: maxPacketSize,
+		maxWriteSize:     maxPacketSize - 1,
+	}
+	mc.cfg, err = parseDSN(dsn)
+	if err != nil {
+		return nil, err
+	}
+
+	// Connect to Server
+	if dial, ok := dials[mc.cfg.net]; ok {
+		mc.netConn, err = dial(mc.cfg.addr)
+	} else {
+		nd := net.Dialer{Timeout: mc.cfg.timeout}
+		mc.netConn, err = nd.Dial(mc.cfg.net, mc.cfg.addr)
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	// Enable TCP Keepalives on TCP connections
+	if tc, ok := mc.netConn.(*net.TCPConn); ok {
+		if err := tc.SetKeepAlive(true); err != nil {
+			// Don't send COM_QUIT before handshake.
+			mc.netConn.Close()
+			mc.netConn = nil
+			return nil, err
+		}
+	}
+
+	mc.buf = newBuffer(mc.netConn)
+
+	// Reading Handshake Initialization Packet
+	cipher, err := mc.readInitPacket()
+	if err != nil {
+		mc.Close()
+		return nil, err
+	}
+
+	// Send Client Authentication Packet
+	if err = mc.writeAuthPacket(cipher); err != nil {
+		mc.Close()
+		return nil, err
+	}
+
+	// Read Result Packet
+	err = mc.readResultOK()
+	if err != nil {
+		// Retry with old authentication method, if allowed
+		if mc.cfg != nil && mc.cfg.allowOldPasswords && err == ErrOldPassword {
+			if err = mc.writeOldAuthPacket(cipher); err != nil {
+				mc.Close()
+				return nil, err
+			}
+			if err = mc.readResultOK(); err != nil {
+				mc.Close()
+				return nil, err
+			}
+		} else {
+			mc.Close()
+			return nil, err
+		}
+
+	}
+
+	// Get max allowed packet size
+	maxap, err := mc.getSystemVar("max_allowed_packet")
+	if err != nil {
+		mc.Close()
+		return nil, err
+	}
+	mc.maxPacketAllowed = stringToInt(maxap) - 1
+	if mc.maxPacketAllowed < maxPacketSize {
+		mc.maxWriteSize = mc.maxPacketAllowed
+	}
+
+	// Handle DSN Params
+	err = mc.handleParams()
+	if err != nil {
+		mc.Close()
+		return nil, err
+	}
+
+	return mc, nil
+}
+
+func init() {
+	sql.Register("mysql", &MySQLDriver{})
+}

+ 129 - 0
src/github.com/go-sql-driver/mysql/errors.go

@@ -0,0 +1,129 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2013 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"
+	"errors"
+	"fmt"
+	"io"
+	"log"
+	"os"
+)
+
+// Various errors the driver might return. Can change between driver versions.
+var (
+	ErrInvalidConn = errors.New("Invalid Connection")
+	ErrMalformPkt  = errors.New("Malformed Packet")
+	ErrNoTLS       = errors.New("TLS encryption requested but server does not support TLS")
+	ErrOldPassword = errors.New("This server only supports the insecure 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")
+	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. You can change this value on the server by adjusting the 'max_allowed_packet' variable.")
+	ErrBusyBuffer  = errors.New("Busy buffer")
+)
+
+var errLog Logger = log.New(os.Stderr, "[MySQL] ", log.Ldate|log.Ltime|log.Lshortfile)
+
+// Logger is used to log critical error messages.
+type Logger interface {
+	Print(v ...interface{})
+}
+
+// SetLogger is used to set the logger for critical errors.
+// The initial logger is os.Stderr.
+func SetLogger(logger Logger) error {
+	if logger == nil {
+		return errors.New("logger is nil")
+	}
+	errLog = logger
+	return nil
+}
+
+// MySQLError is an error type which represents a single MySQL error
+type MySQLError struct {
+	Number  uint16
+	Message string
+}
+
+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
+		}
+	}
+}

+ 162 - 0
src/github.com/go-sql-driver/mysql/infile.go

@@ -0,0 +1,162 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2013 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 (
+	"fmt"
+	"io"
+	"os"
+	"strings"
+)
+
+var (
+	fileRegister   map[string]bool
+	readerRegister map[string]func() io.Reader
+)
+
+// RegisterLocalFile adds the given file to the file whitelist,
+// so that it can be used by "LOAD DATA LOCAL INFILE <filepath>".
+// Alternatively you can allow the use of all local files with
+// the DSN parameter 'allowAllFiles=true'
+//
+//  filePath := "/home/gopher/data.csv"
+//  mysql.RegisterLocalFile(filePath)
+//  err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo")
+//  if err != nil {
+//  ...
+//
+func RegisterLocalFile(filePath string) {
+	// lazy map init
+	if fileRegister == nil {
+		fileRegister = make(map[string]bool)
+	}
+
+	fileRegister[strings.Trim(filePath, `"`)] = true
+}
+
+// DeregisterLocalFile removes the given filepath from the whitelist.
+func DeregisterLocalFile(filePath string) {
+	delete(fileRegister, strings.Trim(filePath, `"`))
+}
+
+// RegisterReaderHandler registers a handler function which is used
+// to receive a io.Reader.
+// The Reader can be used by "LOAD DATA LOCAL INFILE Reader::<name>".
+// If the handler returns a io.ReadCloser Close() is called when the
+// request is finished.
+//
+//  mysql.RegisterReaderHandler("data", func() io.Reader {
+//  	var csvReader io.Reader // Some Reader that returns CSV data
+//  	... // Open Reader here
+//  	return csvReader
+//  })
+//  err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo")
+//  if err != nil {
+//  ...
+//
+func RegisterReaderHandler(name string, handler func() io.Reader) {
+	// lazy map init
+	if readerRegister == nil {
+		readerRegister = make(map[string]func() io.Reader)
+	}
+
+	readerRegister[name] = handler
+}
+
+// DeregisterReaderHandler removes the ReaderHandler function with
+// the given name from the registry.
+func DeregisterReaderHandler(name string) {
+	delete(readerRegister, name)
+}
+
+func deferredClose(err *error, closer io.Closer) {
+	closeErr := closer.Close()
+	if *err == nil {
+		*err = closeErr
+	}
+}
+
+func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
+	var rdr io.Reader
+	var data []byte
+
+	if strings.HasPrefix(name, "Reader::") { // io.Reader
+		name = name[8:]
+		if handler, inMap := readerRegister[name]; inMap {
+			rdr = handler()
+			if rdr != nil {
+				data = make([]byte, 4+mc.maxWriteSize)
+
+				if cl, ok := rdr.(io.Closer); ok {
+					defer deferredClose(&err, cl)
+				}
+			} else {
+				err = fmt.Errorf("Reader '%s' is <nil>", name)
+			}
+		} else {
+			err = fmt.Errorf("Reader '%s' is not registered", name)
+		}
+	} else { // File
+		name = strings.Trim(name, `"`)
+		if mc.cfg.allowAllFiles || fileRegister[name] {
+			var file *os.File
+			var fi os.FileInfo
+
+			if file, err = os.Open(name); err == nil {
+				defer deferredClose(&err, file)
+
+				// get file size
+				if fi, err = file.Stat(); err == nil {
+					rdr = file
+					if fileSize := int(fi.Size()); fileSize <= mc.maxWriteSize {
+						data = make([]byte, 4+fileSize)
+					} else if fileSize <= mc.maxPacketAllowed {
+						data = make([]byte, 4+mc.maxWriteSize)
+					} else {
+						err = fmt.Errorf("Local File '%s' too large: Size: %d, Max: %d", name, fileSize, mc.maxPacketAllowed)
+					}
+				}
+			}
+		} else {
+			err = fmt.Errorf("Local File '%s' is not registered. Use the DSN parameter 'allowAllFiles=true' to allow all files", name)
+		}
+	}
+
+	// send content packets
+	if err == nil {
+		var n int
+		for err == nil {
+			n, err = rdr.Read(data[4:])
+			if n > 0 {
+				if ioErr := mc.writePacket(data[:4+n]); ioErr != nil {
+					return ioErr
+				}
+			}
+		}
+		if err == io.EOF {
+			err = nil
+		}
+	}
+
+	// send empty packet (termination)
+	if data == nil {
+		data = make([]byte, 4)
+	}
+	if ioErr := mc.writePacket(data[:4]); ioErr != nil {
+		return ioErr
+	}
+
+	// read OK packet
+	if err == nil {
+		return mc.readResultOK()
+	} else {
+		mc.readPacket()
+	}
+	return err
+}

+ 1138 - 0
src/github.com/go-sql-driver/mysql/packets.go

@@ -0,0 +1,1138 @@
+// 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 (
+	"bytes"
+	"crypto/tls"
+	"database/sql/driver"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"math"
+	"time"
+)
+
+// Packets documentation:
+// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
+
+// Read packet to buffer 'data'
+func (mc *mysqlConn) readPacket() ([]byte, error) {
+	var payload []byte
+	for {
+		// Read packet header
+		data, err := mc.buf.readNext(4)
+		if err != nil {
+			errLog.Print(err)
+			mc.Close()
+			return nil, driver.ErrBadConn
+		}
+
+		// 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]
+		if data[3] != mc.sequence {
+			if data[3] > mc.sequence {
+				return nil, ErrPktSyncMul
+			} else {
+				return nil, ErrPktSync
+			}
+		}
+		mc.sequence++
+
+		// Read packet body [pktLen bytes]
+		data, err = mc.buf.readNext(pktLen)
+		if err != nil {
+			errLog.Print(err)
+			mc.Close()
+			return nil, driver.ErrBadConn
+		}
+
+		isLastPacket := (pktLen < maxPacketSize)
+
+		// Zero allocations for non-splitting packets
+		if isLastPacket && payload == nil {
+			return data, nil
+		}
+
+		payload = append(payload, data...)
+
+		if isLastPacket {
+			return payload, nil
+		}
+	}
+}
+
+// Write packet buffer 'data'
+func (mc *mysqlConn) writePacket(data []byte) error {
+	pktLen := len(data) - 4
+
+	if pktLen > mc.maxPacketAllowed {
+		return ErrPktTooLarge
+	}
+
+	for {
+		var size int
+		if pktLen >= maxPacketSize {
+			data[0] = 0xff
+			data[1] = 0xff
+			data[2] = 0xff
+			size = maxPacketSize
+		} else {
+			data[0] = byte(pktLen)
+			data[1] = byte(pktLen >> 8)
+			data[2] = byte(pktLen >> 16)
+			size = pktLen
+		}
+		data[3] = mc.sequence
+
+		// Write packet
+		n, err := mc.netConn.Write(data[:4+size])
+		if err == nil && n == 4+size {
+			mc.sequence++
+			if size != maxPacketSize {
+				return nil
+			}
+			pktLen -= size
+			data = data[size:]
+			continue
+		}
+
+		// Handle error
+		if err == nil { // n != len(data)
+			errLog.Print(ErrMalformPkt)
+		} else {
+			errLog.Print(err)
+		}
+		return driver.ErrBadConn
+	}
+}
+
+/******************************************************************************
+*                           Initialisation 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()
+	if err != nil {
+		return nil, err
+	}
+
+	if data[0] == iERR {
+		return nil, mc.handleErrorPacket(data)
+	}
+
+	// protocol version [1 byte]
+	if data[0] < minProtocolVersion {
+		return nil, fmt.Errorf(
+			"Unsupported MySQL Protocol Version %d. Protocol Version %d or higher is required",
+			data[0],
+			minProtocolVersion,
+		)
+	}
+
+	// server version [null terminated string]
+	// connection id [4 bytes]
+	pos := 1 + bytes.IndexByte(data[1:], 0x00) + 1 + 4
+
+	// first part of the password cipher [8 bytes]
+	cipher := data[pos : pos+8]
+
+	// (filler) always 0x00 [1 byte]
+	pos += 8 + 1
+
+	// 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
+	}
+	if mc.flags&clientSSL == 0 && mc.cfg.tls != nil {
+		return nil, ErrNoTLS
+	}
+	pos += 2
+
+	if len(data) > pos {
+		// character set [1 byte]
+		// status flags [2 bytes]
+		// capability flags (upper 2 bytes) [2 bytes]
+		// length of auth-plugin-data [1 byte]
+		// reserved (all [00]) [10 bytes]
+		pos += 1 + 2 + 2 + 1 + 10
+
+		// second part of the password cipher [mininum 13 bytes],
+		// where len=MAX(13, length of auth-plugin-data - 8)
+		//
+		// The web documentation is ambiguous about the length. However,
+		// according to mysql-5.7/sql/auth/sql_authentication.cc line 538,
+		// the 13th byte is "\0 byte, terminating the second part of
+		// a scramble". So the second part of the password cipher is
+		// a NULL terminated string that's at least 13 bytes with the
+		// last byte being NULL.
+		//
+		// 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]...)
+
+		// 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
+		return cipher, nil
+	}
+
+	// make a memory safe copy of the cipher slice
+	var b [8]byte
+	copy(b[:], cipher)
+	return b[:], 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 {
+	// Adjust client flags based on server support
+	clientFlags := clientProtocol41 |
+		clientSecureConn |
+		clientLongPassword |
+		clientTransactions |
+		clientLocalFiles |
+		mc.flags&clientLongFlag
+
+	if mc.cfg.clientFoundRows {
+		clientFlags |= clientFoundRows
+	}
+
+	// To enable TLS / SSL
+	if mc.cfg.tls != nil {
+		clientFlags |= clientSSL
+	}
+
+	// User Password
+	scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.passwd))
+
+	pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.user) + 1 + 1 + len(scrambleBuff)
+
+	// To specify a db name
+	if n := len(mc.cfg.dbname); n > 0 {
+		clientFlags |= clientConnectWithDB
+		pktLen += n + 1
+	}
+
+	// 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
+		errLog.Print(ErrBusyBuffer)
+		return driver.ErrBadConn
+	}
+
+	// ClientFlags [32 bit]
+	data[4] = byte(clientFlags)
+	data[5] = byte(clientFlags >> 8)
+	data[6] = byte(clientFlags >> 16)
+	data[7] = byte(clientFlags >> 24)
+
+	// MaxPacketSize [32 bit] (none)
+	data[8] = 0x00
+	data[9] = 0x00
+	data[10] = 0x00
+	data[11] = 0x00
+
+	// Charset [1 byte]
+	data[12] = mc.cfg.collation
+
+	// SSL Connection Request Packet
+	// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest
+	if mc.cfg.tls != nil {
+		// Send TLS / SSL request packet
+		if err := mc.writePacket(data[:(4+4+1+23)+4]); err != nil {
+			return err
+		}
+
+		// Switch to TLS
+		tlsConn := tls.Client(mc.netConn, mc.cfg.tls)
+		if err := tlsConn.Handshake(); err != nil {
+			return err
+		}
+		mc.netConn = tlsConn
+		mc.buf.rd = tlsConn
+	}
+
+	// Filler [23 bytes] (all 0x00)
+	pos := 13 + 23
+
+	// User [null terminated string]
+	if len(mc.cfg.user) > 0 {
+		pos += copy(data[pos:], mc.cfg.user)
+	}
+	data[pos] = 0x00
+	pos++
+
+	// ScrambleBuffer [length encoded integer]
+	data[pos] = byte(len(scrambleBuff))
+	pos += 1 + copy(data[pos+1:], scrambleBuff)
+
+	// Databasename [null terminated string]
+	if len(mc.cfg.dbname) > 0 {
+		pos += copy(data[pos:], mc.cfg.dbname)
+		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 lenght and add a tailing 0
+	pktLen := len(scrambleBuff) + 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 scrambled password [null terminated string]
+	copy(data[4:], scrambleBuff)
+	data[4+pktLen-1] = 0x00
+
+	return mc.writePacket(data)
+}
+
+/******************************************************************************
+*                             Command Packets                                 *
+******************************************************************************/
+
+func (mc *mysqlConn) writeCommandPacket(command byte) error {
+	// Reset Packet Sequence
+	mc.sequence = 0
+
+	data := mc.buf.takeSmallBuffer(4 + 1)
+	if data == nil {
+		// can not take the buffer. Something must be wrong with the connection
+		errLog.Print(ErrBusyBuffer)
+		return driver.ErrBadConn
+	}
+
+	// Add command byte
+	data[4] = command
+
+	// Send CMD packet
+	return mc.writePacket(data)
+}
+
+func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
+	// Reset Packet Sequence
+	mc.sequence = 0
+
+	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
+		errLog.Print(ErrBusyBuffer)
+		return driver.ErrBadConn
+	}
+
+	// Add command byte
+	data[4] = command
+
+	// Add arg
+	copy(data[5:], arg)
+
+	// Send CMD packet
+	return mc.writePacket(data)
+}
+
+func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
+	// Reset Packet Sequence
+	mc.sequence = 0
+
+	data := mc.buf.takeSmallBuffer(4 + 1 + 4)
+	if data == nil {
+		// can not take the buffer. Something must be wrong with the connection
+		errLog.Print(ErrBusyBuffer)
+		return driver.ErrBadConn
+	}
+
+	// Add command byte
+	data[4] = command
+
+	// Add arg [32 bit]
+	data[5] = byte(arg)
+	data[6] = byte(arg >> 8)
+	data[7] = byte(arg >> 16)
+	data[8] = byte(arg >> 24)
+
+	// Send CMD packet
+	return mc.writePacket(data)
+}
+
+/******************************************************************************
+*                              Result Packets                                 *
+******************************************************************************/
+
+// 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:
+			// someone is using old_passwords
+			return ErrOldPassword
+
+		default: // Error otherwise
+			return mc.handleErrorPacket(data)
+		}
+	}
+	return err
+}
+
+// Result Set Header Packet
+// http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-ProtocolText::Resultset
+func (mc *mysqlConn) readResultSetHeaderPacket() (int, error) {
+	data, err := mc.readPacket()
+	if err == nil {
+		switch data[0] {
+
+		case iOK:
+			return 0, mc.handleOkPacket(data)
+
+		case iERR:
+			return 0, mc.handleErrorPacket(data)
+
+		case iLocalInFile:
+			return 0, mc.handleInFileRequest(string(data[1:]))
+		}
+
+		// column count
+		num, _, n := readLengthEncodedInteger(data)
+		if n-len(data) == 0 {
+			return int(num), nil
+		}
+
+		return 0, ErrMalformPkt
+	}
+	return 0, err
+}
+
+// Error Packet
+// http://dev.mysql.com/doc/internals/en/generic-response-packets.html#packet-ERR_Packet
+func (mc *mysqlConn) handleErrorPacket(data []byte) error {
+	if data[0] != iERR {
+		return ErrMalformPkt
+	}
+
+	// 0xff [1 byte]
+
+	// Error Number [16 bit uint]
+	errno := binary.LittleEndian.Uint16(data[1:3])
+
+	pos := 3
+
+	// SQL State [optional: # + 5bytes string]
+	if data[3] == 0x23 {
+		//sqlstate := string(data[4 : 4+5])
+		pos = 9
+	}
+
+	// Error Message [string]
+	return &MySQLError{
+		Number:  errno,
+		Message: string(data[pos:]),
+	}
+}
+
+// Ok Packet
+// http://dev.mysql.com/doc/internals/en/generic-response-packets.html#packet-OK_Packet
+func (mc *mysqlConn) handleOkPacket(data []byte) error {
+	var n, m int
+
+	// 0x00 [1 byte]
+
+	// Affected rows [Length Coded Binary]
+	mc.affectedRows, _, n = readLengthEncodedInteger(data[1:])
+
+	// Insert id [Length Coded Binary]
+	mc.insertId, _, m = readLengthEncodedInteger(data[1+n:])
+
+	// server_status [2 bytes]
+	mc.status = statusFlag(data[1+n+m]) | statusFlag(data[1+n+m+1])<<8
+
+	// warning count [2 bytes]
+	if !mc.strict {
+		return nil
+	} else {
+		pos := 1 + n + m + 2
+		if binary.LittleEndian.Uint16(data[pos:pos+2]) > 0 {
+			return mc.getWarnings()
+		}
+		return nil
+	}
+}
+
+// Read Packets as Field Packets until EOF-Packet or an Error appears
+// http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnDefinition41
+func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
+	columns := make([]mysqlField, count)
+
+	for i := 0; ; i++ {
+		data, err := mc.readPacket()
+		if err != nil {
+			return nil, err
+		}
+
+		// EOF Packet
+		if data[0] == iEOF && (len(data) == 5 || len(data) == 1) {
+			if i == count {
+				return columns, nil
+			}
+			return nil, fmt.Errorf("ColumnsCount mismatch n:%d len:%d", count, len(columns))
+		}
+
+		// Catalog
+		pos, err := skipLengthEncodedString(data)
+		if err != nil {
+			return nil, err
+		}
+
+		// Database [len coded string]
+		n, err := skipLengthEncodedString(data[pos:])
+		if err != nil {
+			return nil, err
+		}
+		pos += n
+
+		// Table [len coded string]
+		if mc.cfg.columnsWithAlias {
+			tableName, _, n, err := readLengthEncodedString(data[pos:])
+			if err != nil {
+				return nil, err
+			}
+			pos += n
+			columns[i].tableName = string(tableName)
+		} else {
+			n, err = skipLengthEncodedString(data[pos:])
+			if err != nil {
+				return nil, err
+			}
+			pos += n
+		}
+
+		// Original table [len coded string]
+		n, err = skipLengthEncodedString(data[pos:])
+		if err != nil {
+			return nil, err
+		}
+		pos += n
+
+		// Name [len coded string]
+		name, _, n, err := readLengthEncodedString(data[pos:])
+		if err != nil {
+			return nil, err
+		}
+		columns[i].name = string(name)
+		pos += n
+
+		// Original name [len coded string]
+		n, err = skipLengthEncodedString(data[pos:])
+		if err != nil {
+			return nil, err
+		}
+
+		// Filler [uint8]
+		// Charset [charset, collation uint8]
+		// Length [uint32]
+		pos += n + 1 + 2 + 4
+
+		// Field type [uint8]
+		columns[i].fieldType = data[pos]
+		pos++
+
+		// Flags [uint16]
+		columns[i].flags = fieldFlag(binary.LittleEndian.Uint16(data[pos : pos+2]))
+		pos += 2
+
+		// Decimals [uint8]
+		columns[i].decimals = data[pos]
+		//pos++
+
+		// Default value [len coded binary]
+		//if pos < len(data) {
+		//	defaultVal, _, err = bytesToLengthCodedBinary(data[pos:])
+		//}
+	}
+}
+
+// Read Packets as Field Packets until EOF-Packet or an Error appears
+// http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-ProtocolText::ResultsetRow
+func (rows *textRows) readRow(dest []driver.Value) error {
+	mc := rows.mc
+
+	data, err := mc.readPacket()
+	if err != nil {
+		return err
+	}
+
+	// EOF Packet
+	if data[0] == iEOF && len(data) == 5 {
+		rows.mc = nil
+		return io.EOF
+	}
+	if data[0] == iERR {
+		rows.mc = nil
+		return mc.handleErrorPacket(data)
+	}
+
+	// RowSet Packet
+	var n int
+	var isNull bool
+	pos := 0
+
+	for i := range dest {
+		// Read bytes and convert to string
+		dest[i], isNull, n, err = readLengthEncodedString(data[pos:])
+		pos += n
+		if err == nil {
+			if !isNull {
+				if !mc.parseTime {
+					continue
+				} else {
+					switch rows.columns[i].fieldType {
+					case fieldTypeTimestamp, fieldTypeDateTime,
+						fieldTypeDate, fieldTypeNewDate:
+						dest[i], err = parseDateTime(
+							string(dest[i].([]byte)),
+							mc.cfg.loc,
+						)
+						if err == nil {
+							continue
+						}
+					default:
+						continue
+					}
+				}
+
+			} else {
+				dest[i] = nil
+				continue
+			}
+		}
+		return err // err != nil
+	}
+
+	return nil
+}
+
+// Reads Packets until EOF-Packet or an Error appears. Returns count of Packets read
+func (mc *mysqlConn) readUntilEOF() error {
+	for {
+		data, err := mc.readPacket()
+
+		// No Err and no EOF Packet
+		if err == nil && data[0] != iEOF {
+			continue
+		}
+		return err // Err or EOF
+	}
+}
+
+/******************************************************************************
+*                           Prepared Statements                               *
+******************************************************************************/
+
+// Prepare Result Packets
+// http://dev.mysql.com/doc/internals/en/com-stmt-prepare-response.html
+func (stmt *mysqlStmt) readPrepareResultPacket() (uint16, error) {
+	data, err := stmt.mc.readPacket()
+	if err == nil {
+		// packet indicator [1 byte]
+		if data[0] != iOK {
+			return 0, stmt.mc.handleErrorPacket(data)
+		}
+
+		// statement id [4 bytes]
+		stmt.id = binary.LittleEndian.Uint32(data[1:5])
+
+		// Column count [16 bit uint]
+		columnCount := binary.LittleEndian.Uint16(data[5:7])
+
+		// Param count [16 bit uint]
+		stmt.paramCount = int(binary.LittleEndian.Uint16(data[7:9]))
+
+		// Reserved [8 bit]
+
+		// Warning count [16 bit uint]
+		if !stmt.mc.strict {
+			return columnCount, nil
+		} else {
+			// 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
+}
+
+// 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
+	pktLen := maxLen
+
+	// After the header (bytes 0-3) follows before the data:
+	// 1 byte command
+	// 4 bytes stmtID
+	// 2 bytes paramID
+	const dataOffset = 1 + 4 + 2
+
+	// Can not 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))
+
+	copy(data[4+dataOffset:], arg)
+
+	for argLen := len(arg); argLen > 0; argLen -= pktLen - dataOffset {
+		if dataOffset+argLen < maxLen {
+			pktLen = dataOffset + argLen
+		}
+
+		stmt.mc.sequence = 0
+		// Add command byte [1 byte]
+		data[4] = comStmtSendLongData
+
+		// Add stmtID [32 bit]
+		data[5] = byte(stmt.id)
+		data[6] = byte(stmt.id >> 8)
+		data[7] = byte(stmt.id >> 16)
+		data[8] = byte(stmt.id >> 24)
+
+		// Add paramID [16 bit]
+		data[9] = byte(paramID)
+		data[10] = byte(paramID >> 8)
+
+		// Send CMD packet
+		err := stmt.mc.writePacket(data[:4+pktLen])
+		if err == nil {
+			data = data[pktLen-dataOffset:]
+			continue
+		}
+		return err
+
+	}
+
+	// Reset Packet Sequence
+	stmt.mc.sequence = 0
+	return nil
+}
+
+// Execute Prepared Statement
+// http://dev.mysql.com/doc/internals/en/com-stmt-execute.html
+func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
+	if len(args) != stmt.paramCount {
+		return fmt.Errorf(
+			"Arguments count mismatch (Got: %d Has: %d)",
+			len(args),
+			stmt.paramCount,
+		)
+	}
+
+	const minPktLen = 4 + 1 + 4 + 1 + 4
+	mc := stmt.mc
+
+	// Reset packet-sequence
+	mc.sequence = 0
+
+	var data []byte
+
+	if len(args) == 0 {
+		data = mc.buf.takeBuffer(minPktLen)
+	} else {
+		data = mc.buf.takeCompleteBuffer()
+	}
+	if data == nil {
+		// can not take the buffer. Something must be wrong with the connection
+		errLog.Print(ErrBusyBuffer)
+		return driver.ErrBadConn
+	}
+
+	// command [1 byte]
+	data[4] = comStmtExecute
+
+	// statement_id [4 bytes]
+	data[5] = byte(stmt.id)
+	data[6] = byte(stmt.id >> 8)
+	data[7] = byte(stmt.id >> 16)
+	data[8] = byte(stmt.id >> 24)
+
+	// flags (0: CURSOR_TYPE_NO_CURSOR) [1 byte]
+	data[9] = 0x00
+
+	// iteration_count (uint32(1)) [4 bytes]
+	data[10] = 0x01
+	data[11] = 0x00
+	data[12] = 0x00
+	data[13] = 0x00
+
+	if len(args) > 0 {
+		pos := minPktLen
+
+		var nullMask []byte
+		if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= len(data) {
+			// buffer has to be extended but we don't know by how much so
+			// we depend on append after all data with known sizes fit.
+			// We stop at that because we deal with a lot of columns here
+			// which makes the required allocation size hard to guess.
+			tmp := make([]byte, pos+maskLen+typesLen)
+			copy(tmp[:pos], data[:pos])
+			data = tmp
+			nullMask = data[pos : pos+maskLen]
+			pos += maskLen
+		} else {
+			nullMask = data[pos : pos+maskLen]
+			for i := 0; i < maskLen; i++ {
+				nullMask[i] = 0
+			}
+			pos += maskLen
+		}
+
+		// newParameterBoundFlag 1 [1 byte]
+		data[pos] = 0x01
+		pos++
+
+		// type of each parameter [len(args)*2 bytes]
+		paramTypes := data[pos:]
+		pos += len(args) * 2
+
+		// value of each parameter [n bytes]
+		paramValues := data[pos:pos]
+		valuesCap := cap(paramValues)
+
+		for i, arg := range args {
+			// build NULL-bitmap
+			if arg == nil {
+				nullMask[i/8] |= 1 << (uint(i) & 7)
+				paramTypes[i+i] = fieldTypeNULL
+				paramTypes[i+i+1] = 0x00
+				continue
+			}
+
+			// cache types and values
+			switch v := arg.(type) {
+			case int64:
+				paramTypes[i+i] = fieldTypeLongLong
+				paramTypes[i+i+1] = 0x00
+
+				if cap(paramValues)-len(paramValues)-8 >= 0 {
+					paramValues = paramValues[:len(paramValues)+8]
+					binary.LittleEndian.PutUint64(
+						paramValues[len(paramValues)-8:],
+						uint64(v),
+					)
+				} else {
+					paramValues = append(paramValues,
+						uint64ToBytes(uint64(v))...,
+					)
+				}
+
+			case float64:
+				paramTypes[i+i] = fieldTypeDouble
+				paramTypes[i+i+1] = 0x00
+
+				if cap(paramValues)-len(paramValues)-8 >= 0 {
+					paramValues = paramValues[:len(paramValues)+8]
+					binary.LittleEndian.PutUint64(
+						paramValues[len(paramValues)-8:],
+						math.Float64bits(v),
+					)
+				} else {
+					paramValues = append(paramValues,
+						uint64ToBytes(math.Float64bits(v))...,
+					)
+				}
+
+			case bool:
+				paramTypes[i+i] = fieldTypeTiny
+				paramTypes[i+i+1] = 0x00
+
+				if v {
+					paramValues = append(paramValues, 0x01)
+				} else {
+					paramValues = append(paramValues, 0x00)
+				}
+
+			case []byte:
+				// Common case (non-nil value) first
+				if v != nil {
+					paramTypes[i+i] = fieldTypeString
+					paramTypes[i+i+1] = 0x00
+
+					if len(v) < mc.maxPacketAllowed-pos-len(paramValues)-(len(args)-(i+1))*64 {
+						paramValues = appendLengthEncodedInteger(paramValues,
+							uint64(len(v)),
+						)
+						paramValues = append(paramValues, v...)
+					} else {
+						if err := stmt.writeCommandLongData(i, v); err != nil {
+							return err
+						}
+					}
+					continue
+				}
+
+				// Handle []byte(nil) as a NULL value
+				nullMask[i/8] |= 1 << (uint(i) & 7)
+				paramTypes[i+i] = fieldTypeNULL
+				paramTypes[i+i+1] = 0x00
+
+			case string:
+				paramTypes[i+i] = fieldTypeString
+				paramTypes[i+i+1] = 0x00
+
+				if len(v) < mc.maxPacketAllowed-pos-len(paramValues)-(len(args)-(i+1))*64 {
+					paramValues = appendLengthEncodedInteger(paramValues,
+						uint64(len(v)),
+					)
+					paramValues = append(paramValues, v...)
+				} else {
+					if err := stmt.writeCommandLongData(i, []byte(v)); err != nil {
+						return err
+					}
+				}
+
+			case time.Time:
+				paramTypes[i+i] = fieldTypeString
+				paramTypes[i+i+1] = 0x00
+
+				var val []byte
+				if v.IsZero() {
+					val = []byte("0000-00-00")
+				} else {
+					val = []byte(v.In(mc.cfg.loc).Format(timeFormat))
+				}
+
+				paramValues = appendLengthEncodedInteger(paramValues,
+					uint64(len(val)),
+				)
+				paramValues = append(paramValues, val...)
+
+			default:
+				return fmt.Errorf("Can't convert type: %T", arg)
+			}
+		}
+
+		// Check if param values exceeded the available buffer
+		// In that case we must build the data packet with the new values buffer
+		if valuesCap != cap(paramValues) {
+			data = append(data[:pos], paramValues...)
+			mc.buf.buf = data
+		}
+
+		pos += len(paramValues)
+		data = data[:pos]
+	}
+
+	return mc.writePacket(data)
+}
+
+// http://dev.mysql.com/doc/internals/en/binary-protocol-resultset-row.html
+func (rows *binaryRows) readRow(dest []driver.Value) error {
+	data, err := rows.mc.readPacket()
+	if err != nil {
+		return err
+	}
+
+	// packet indicator [1 byte]
+	if data[0] != iOK {
+		rows.mc = nil
+		// EOF Packet
+		if data[0] == iEOF && len(data) == 5 {
+			return io.EOF
+		}
+
+		// Error otherwise
+		return rows.mc.handleErrorPacket(data)
+	}
+
+	// NULL-bitmap,  [(column-count + 7 + 2) / 8 bytes]
+	pos := 1 + (len(dest)+7+2)>>3
+	nullMask := data[1:pos]
+
+	for i := range dest {
+		// Field is NULL
+		// (byte >> bit-pos) % 2 == 1
+		if ((nullMask[(i+2)>>3] >> uint((i+2)&7)) & 1) == 1 {
+			dest[i] = nil
+			continue
+		}
+
+		// Convert to byte-coded string
+		switch rows.columns[i].fieldType {
+		case fieldTypeNULL:
+			dest[i] = nil
+			continue
+
+		// Numeric Types
+		case fieldTypeTiny:
+			if rows.columns[i].flags&flagUnsigned != 0 {
+				dest[i] = int64(data[pos])
+			} else {
+				dest[i] = int64(int8(data[pos]))
+			}
+			pos++
+			continue
+
+		case fieldTypeShort, fieldTypeYear:
+			if rows.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])))
+			}
+			pos += 2
+			continue
+
+		case fieldTypeInt24, fieldTypeLong:
+			if rows.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])))
+			}
+			pos += 4
+			continue
+
+		case fieldTypeLongLong:
+			if rows.columns[i].flags&flagUnsigned != 0 {
+				val := binary.LittleEndian.Uint64(data[pos : pos+8])
+				if val > math.MaxInt64 {
+					dest[i] = uint64ToString(val)
+				} else {
+					dest[i] = int64(val)
+				}
+			} else {
+				dest[i] = int64(binary.LittleEndian.Uint64(data[pos : pos+8]))
+			}
+			pos += 8
+			continue
+
+		case fieldTypeFloat:
+			dest[i] = float64(math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4])))
+			pos += 4
+			continue
+
+		case fieldTypeDouble:
+			dest[i] = math.Float64frombits(binary.LittleEndian.Uint64(data[pos : pos+8]))
+			pos += 8
+			continue
+
+		// Length coded Binary Strings
+		case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar,
+			fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB,
+			fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB,
+			fieldTypeVarString, fieldTypeString, fieldTypeGeometry:
+			var isNull bool
+			var n int
+			dest[i], isNull, n, err = readLengthEncodedString(data[pos:])
+			pos += n
+			if err == nil {
+				if !isNull {
+					continue
+				} else {
+					dest[i] = nil
+					continue
+				}
+			}
+			return err
+
+		case
+			fieldTypeDate, fieldTypeNewDate, // Date YYYY-MM-DD
+			fieldTypeTime,                         // Time [-][H]HH:MM:SS[.fractal]
+			fieldTypeTimestamp, fieldTypeDateTime: // Timestamp YYYY-MM-DD HH:MM:SS[.fractal]
+
+			num, isNull, n := readLengthEncodedInteger(data[pos:])
+			pos += n
+
+			switch {
+			case isNull:
+				dest[i] = nil
+				continue
+			case rows.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 {
+				case 0x00, 0x1f:
+					dstlen = 8
+				case 1, 2, 3, 4, 5, 6:
+					dstlen = 8 + 1 + decimals
+				default:
+					return fmt.Errorf(
+						"MySQL protocol error, illegal decimals value %d",
+						rows.columns[i].decimals,
+					)
+				}
+				dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true)
+			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 {
+					dstlen = 10
+				} else {
+					switch decimals := rows.columns[i].decimals; decimals {
+					case 0x00, 0x1f:
+						dstlen = 19
+					case 1, 2, 3, 4, 5, 6:
+						dstlen = 19 + 1 + decimals
+					default:
+						return fmt.Errorf(
+							"MySQL protocol error, illegal decimals value %d",
+							rows.columns[i].decimals,
+						)
+					}
+				}
+				dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, false)
+			}
+
+			if err == nil {
+				pos += int(num)
+				continue
+			} else {
+				return err
+			}
+
+		// Please report if this happens!
+		default:
+			return fmt.Errorf("Unknown FieldType %d", rows.columns[i].fieldType)
+		}
+	}
+
+	return nil
+}

+ 22 - 0
src/github.com/go-sql-driver/mysql/result.go

@@ -0,0 +1,22 @@
+// 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
+
+type mysqlResult struct {
+	affectedRows int64
+	insertId     int64
+}
+
+func (res *mysqlResult) LastInsertId() (int64, error) {
+	return res.insertId, nil
+}
+
+func (res *mysqlResult) RowsAffected() (int64, error) {
+	return res.affectedRows, nil
+}

+ 102 - 0
src/github.com/go-sql-driver/mysql/rows.go

@@ -0,0 +1,102 @@
+// 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.cfg.columnsWithAlias {
+		for i := range columns {
+			columns[i] = rows.columns[i].tableName + "." + 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()
+	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
+}

+ 112 - 0
src/github.com/go-sql-driver/mysql/statement.go

@@ -0,0 +1,112 @@
+// 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"
+)
+
+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) 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)
+	rows.mc = mc
+
+	if resLen > 0 {
+		// 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
+}

+ 31 - 0
src/github.com/go-sql-driver/mysql/transaction.go

@@ -0,0 +1,31 @@
+// 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
+
+type mysqlTx struct {
+	mc *mysqlConn
+}
+
+func (tx *mysqlTx) Commit() (err error) {
+	if tx.mc == nil || tx.mc.netConn == nil {
+		return ErrInvalidConn
+	}
+	err = tx.mc.exec("COMMIT")
+	tx.mc = nil
+	return
+}
+
+func (tx *mysqlTx) Rollback() (err error) {
+	if tx.mc == nil || tx.mc.netConn == nil {
+		return ErrInvalidConn
+	}
+	err = tx.mc.exec("ROLLBACK")
+	tx.mc = nil
+	return
+}

+ 963 - 0
src/github.com/go-sql-driver/mysql/utils.go

@@ -0,0 +1,963 @@
+// 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 (
+	"crypto/sha1"
+	"crypto/tls"
+	"database/sql/driver"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"io"
+	"net"
+	"net/url"
+	"strings"
+	"time"
+)
+
+var (
+	tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
+
+	errInvalidDSNUnescaped       = errors.New("Invalid DSN: Did you forget to escape a param value?")
+	errInvalidDSNAddr            = errors.New("Invalid DSN: Network Address not terminated (missing closing brace)")
+	errInvalidDSNNoSlash         = errors.New("Invalid DSN: Missing the slash separating the database name")
+	errInvalidDSNUnsafeCollation = errors.New("Invalid DSN: interpolateParams can be used with ascii, latin1, utf8 and utf8mb4 charset")
+)
+
+func init() {
+	tlsConfigRegister = make(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.
+//
+//  rootCertPool := x509.NewCertPool()
+//  pem, err := ioutil.ReadFile("/path/ca-cert.pem")
+//  if err != nil {
+//      log.Fatal(err)
+//  }
+//  if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
+//      log.Fatal("Failed to append PEM.")
+//  }
+//  clientCert := make([]tls.Certificate, 0, 1)
+//  certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem")
+//  if err != nil {
+//      log.Fatal(err)
+//  }
+//  clientCert = append(clientCert, certs)
+//  mysql.RegisterTLSConfig("custom", &tls.Config{
+//      RootCAs: rootCertPool,
+//      Certificates: clientCert,
+//  })
+//  db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
+//
+func RegisterTLSConfig(key string, config *tls.Config) error {
+	if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" {
+		return fmt.Errorf("Key '%s' is reserved", key)
+	}
+
+	tlsConfigRegister[key] = config
+	return nil
+}
+
+// DeregisterTLSConfig removes the tls.Config associated with key.
+func DeregisterTLSConfig(key string) {
+	delete(tlsConfigRegister, key)
+}
+
+// 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,
+	}
+
+	// TODO: use strings.IndexByte when we can depend on Go 1.2
+
+	// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
+	// Find the last '/' (since the password or the net addr might contain a '/')
+	foundSlash := false
+	for i := len(dsn) - 1; i >= 0; i-- {
+		if dsn[i] == '/' {
+			foundSlash = true
+			var j, k int
+
+			// left part is empty if i <= 0
+			if i > 0 {
+				// [username[:password]@][protocol[(address)]]
+				// Find the last '@' in dsn[:i]
+				for j = i; j >= 0; j-- {
+					if dsn[j] == '@' {
+						// username[:password]
+						// Find the first ':' in dsn[:j]
+						for k = 0; k < j; k++ {
+							if dsn[k] == ':' {
+								cfg.passwd = dsn[k+1 : j]
+								break
+							}
+						}
+						cfg.user = dsn[:k]
+
+						break
+					}
+				}
+
+				// [protocol[(address)]]
+				// Find the first '(' in dsn[j+1:i]
+				for k = j + 1; k < i; k++ {
+					if dsn[k] == '(' {
+						// dsn[i-1] must be == ')' if an address is specified
+						if dsn[i-1] != ')' {
+							if strings.ContainsRune(dsn[k+1:i], ')') {
+								return nil, errInvalidDSNUnescaped
+							}
+							return nil, errInvalidDSNAddr
+						}
+						cfg.addr = dsn[k+1 : i-1]
+						break
+					}
+				}
+				cfg.net = dsn[j+1 : k]
+			}
+
+			// dbname[?param1=value1&...&paramN=valueN]
+			// Find the first '?' in dsn[i+1:]
+			for j = i + 1; j < len(dsn); j++ {
+				if dsn[j] == '?' {
+					if err = parseDSNParams(cfg, dsn[j+1:]); err != nil {
+						return
+					}
+					break
+				}
+			}
+			cfg.dbname = dsn[i+1 : j]
+
+			break
+		}
+	}
+
+	if !foundSlash && len(dsn) > 0 {
+		return nil, errInvalidDSNNoSlash
+	}
+
+	if cfg.interpolateParams && unsafeCollations[cfg.collation] {
+		return nil, 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 nil, errors.New("Default addr for network '" + cfg.net + "' unknown")
+		}
+
+	}
+
+	return
+}
+
+// parseDSNParams parses the DSN "query string"
+// Values must be url.QueryEscape'ed
+func parseDSNParams(cfg *config, params string) (err error) {
+	for _, v := range strings.Split(params, "&") {
+		param := strings.SplitN(v, "=", 2)
+		if len(param) != 2 {
+			continue
+		}
+
+		// cfg params
+		switch value := param[1]; param[0] {
+
+		// Enable client side placeholder substitution
+		case "interpolateParams":
+			var isBool bool
+			cfg.interpolateParams, isBool = readBool(value)
+			if !isBool {
+				return fmt.Errorf("Invalid Bool value: %s", value)
+			}
+
+		// Disable INFILE whitelist / enable all files
+		case "allowAllFiles":
+			var isBool bool
+			cfg.allowAllFiles, isBool = readBool(value)
+			if !isBool {
+				return fmt.Errorf("Invalid Bool value: %s", value)
+			}
+
+		// Use old authentication mode (pre MySQL 4.1)
+		case "allowOldPasswords":
+			var isBool bool
+			cfg.allowOldPasswords, isBool = readBool(value)
+			if !isBool {
+				return fmt.Errorf("Invalid Bool value: %s", value)
+			}
+
+		// Switch "rowsAffected" mode
+		case "clientFoundRows":
+			var isBool bool
+			cfg.clientFoundRows, isBool = readBool(value)
+			if !isBool {
+				return fmt.Errorf("Invalid Bool value: %s", value)
+			}
+
+		// Collation
+		case "collation":
+			collation, ok := collations[value]
+			if !ok {
+				// Note possibility for false negatives:
+				// could be triggered  although the collation is valid if the
+				// collations map does not contain entries the server supports.
+				err = errors.New("unknown collation")
+				return
+			}
+			cfg.collation = collation
+			break
+
+		case "columnsWithAlias":
+			var isBool bool
+			cfg.columnsWithAlias, isBool = readBool(value)
+			if !isBool {
+				return fmt.Errorf("Invalid Bool value: %s", value)
+			}
+
+		// Time Location
+		case "loc":
+			if value, err = url.QueryUnescape(value); err != nil {
+				return
+			}
+			cfg.loc, err = time.LoadLocation(value)
+			if err != nil {
+				return
+			}
+
+		// Dial Timeout
+		case "timeout":
+			cfg.timeout, err = time.ParseDuration(value)
+			if err != nil {
+				return
+			}
+
+		// TLS-Encryption
+		case "tls":
+			boolValue, isBool := readBool(value)
+			if isBool {
+				if boolValue {
+					cfg.tls = &tls.Config{}
+				}
+			} else {
+				if strings.ToLower(value) == "skip-verify" {
+					cfg.tls = &tls.Config{InsecureSkipVerify: true}
+				} else if tlsConfig, ok := tlsConfigRegister[value]; ok {
+					if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
+						host, _, err := net.SplitHostPort(cfg.addr)
+						if err == nil {
+							tlsConfig.ServerName = host
+						}
+					}
+
+					cfg.tls = tlsConfig
+				} else {
+					return fmt.Errorf("Invalid value / unknown config name: %s", value)
+				}
+			}
+
+		default:
+			// lazy init
+			if cfg.params == nil {
+				cfg.params = make(map[string]string)
+			}
+
+			if cfg.params[param[0]], err = url.QueryUnescape(value); err != nil {
+				return
+			}
+		}
+	}
+
+	return
+}
+
+// Returns the bool value of the input.
+// The 2nd return value indicates if the input was a valid bool value
+func readBool(input string) (value bool, valid bool) {
+	switch input {
+	case "1", "true", "TRUE", "True":
+		return true, true
+	case "0", "false", "FALSE", "False":
+		return false, true
+	}
+
+	// Not a valid bool value
+	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                                *
+******************************************************************************/
+
+// NullTime represents a time.Time that may be NULL.
+// NullTime implements the Scanner interface so
+// it can be used as a scan destination:
+//
+//  var nt NullTime
+//  err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
+//  ...
+//  if nt.Valid {
+//     // use nt.Time
+//  } else {
+//     // NULL value
+//  }
+//
+// This NullTime implementation is not driver-specific
+type NullTime struct {
+	Time  time.Time
+	Valid bool // Valid is true if Time is not NULL
+}
+
+// Scan implements the Scanner interface.
+// The value type must be time.Time or string / []byte (formatted time-string),
+// otherwise Scan fails.
+func (nt *NullTime) Scan(value interface{}) (err error) {
+	if value == nil {
+		nt.Time, nt.Valid = time.Time{}, false
+		return
+	}
+
+	switch v := value.(type) {
+	case time.Time:
+		nt.Time, nt.Valid = v, true
+		return
+	case []byte:
+		nt.Time, err = parseDateTime(string(v), time.UTC)
+		nt.Valid = (err == nil)
+		return
+	case string:
+		nt.Time, err = parseDateTime(v, time.UTC)
+		nt.Valid = (err == nil)
+		return
+	}
+
+	nt.Valid = false
+	return fmt.Errorf("Can't convert %T to time.Time", value)
+}
+
+// Value implements the driver Valuer interface.
+func (nt NullTime) Value() (driver.Value, error) {
+	if !nt.Valid {
+		return nil, nil
+	}
+	return nt.Time, nil
+}
+
+func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
+	base := "0000-00-00 00:00:00.0000000"
+	switch len(str) {
+	case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
+		if str == base[:len(str)] {
+			return
+		}
+		t, err = time.Parse(timeFormat[:len(str)], str)
+	default:
+		err = fmt.Errorf("Invalid Time-String: %s", str)
+		return
+	}
+
+	// Adjust location
+	if err == nil && loc != time.UTC {
+		y, mo, d := t.Date()
+		h, mi, s := t.Clock()
+		t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil
+	}
+
+	return
+}
+
+func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) {
+	switch num {
+	case 0:
+		return time.Time{}, nil
+	case 4:
+		return time.Date(
+			int(binary.LittleEndian.Uint16(data[:2])), // year
+			time.Month(data[2]),                       // month
+			int(data[3]),                              // day
+			0, 0, 0, 0,
+			loc,
+		), nil
+	case 7:
+		return time.Date(
+			int(binary.LittleEndian.Uint16(data[:2])), // year
+			time.Month(data[2]),                       // month
+			int(data[3]),                              // day
+			int(data[4]),                              // hour
+			int(data[5]),                              // minutes
+			int(data[6]),                              // seconds
+			0,
+			loc,
+		), nil
+	case 11:
+		return time.Date(
+			int(binary.LittleEndian.Uint16(data[:2])), // year
+			time.Month(data[2]),                       // month
+			int(data[3]),                              // day
+			int(data[4]),                              // hour
+			int(data[5]),                              // minutes
+			int(data[6]),                              // seconds
+			int(binary.LittleEndian.Uint32(data[7:11]))*1000, // nanoseconds
+			loc,
+		), nil
+	}
+	return nil, fmt.Errorf("Invalid DATETIME-packet length %d", num)
+}
+
+// zeroDateTime is used in formatBinaryDateTime to avoid an allocation
+// if the DATE or DATETIME has the zero value.
+// It must never be changed.
+// The current behavior depends on database/sql copying the result.
+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) {
+	// 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)
+		}
+		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:]
+	}
+	// p1 is 2-digit hour, src is after hour
+	p2, p3 = src[0], src[1]
+	dst = append(dst,
+		digits10[p1], digits01[p1], ':',
+		digits10[p2], digits01[p2], ':',
+		digits10[p3], digits01[p3],
+	)
+	if length <= byte(len(dst)) {
+		return dst, nil
+	}
+	src = src[2:]
+	if len(src) == 0 {
+		return append(dst, zeroDateTime[19:zOffs+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 {
+	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
+	}
+}
+
+/******************************************************************************
+*                       Convert from and to bytes                             *
+******************************************************************************/
+
+func uint64ToBytes(n uint64) []byte {
+	return []byte{
+		byte(n),
+		byte(n >> 8),
+		byte(n >> 16),
+		byte(n >> 24),
+		byte(n >> 32),
+		byte(n >> 40),
+		byte(n >> 48),
+		byte(n >> 56),
+	}
+}
+
+func uint64ToString(n uint64) []byte {
+	var a [20]byte
+	i := 20
+
+	// U+0030 = 0
+	// ...
+	// U+0039 = 9
+
+	var q uint64
+	for n >= 10 {
+		i--
+		q = n / 10
+		a[i] = uint8(n-q*10) + 0x30
+		n = q
+	}
+
+	i--
+	a[i] = uint8(n) + 0x30
+
+	return a[i:]
+}
+
+// treats string value as unsigned integer representation
+func stringToInt(b []byte) int {
+	val := 0
+	for i := range b {
+		val *= 10
+		val += int(b[i] - 0x30)
+	}
+	return val
+}
+
+// returns the string read as a bytes slice, wheter the value is NULL,
+// the number of bytes read and an error, in case the string is longer than
+// the input slice
+func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
+	// Get length
+	num, isNull, n := readLengthEncodedInteger(b)
+	if num < 1 {
+		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 nil, false, n, io.EOF
+}
+
+// returns the number of bytes skipped and an error, in case the string is
+// longer than the input slice
+func skipLengthEncodedString(b []byte) (int, error) {
+	// Get length
+	num, _, n := readLengthEncodedInteger(b)
+	if num < 1 {
+		return n, nil
+	}
+
+	n += int(num)
+
+	// Check data length
+	if len(b) >= n {
+		return n, nil
+	}
+	return n, io.EOF
+}
+
+// returns the number read, whether the value is NULL and the number of bytes read
+func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
+	switch b[0] {
+
+	// 251: NULL
+	case 0xfb:
+		return 0, true, 1
+
+	// 252: value of following 2
+	case 0xfc:
+		return uint64(b[1]) | uint64(b[2])<<8, false, 3
+
+	// 253: value of following 3
+	case 0xfd:
+		return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4
+
+	// 254: value of following 8
+	case 0xfe:
+		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,
+			false, 9
+	}
+
+	// 0-250: value of first byte
+	return uint64(b[0]), false, 1
+}
+
+// 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))
+}
+
+// reserveBuffer checks cap(buf) and expand buffer to len(buf) + appendSize.
+// If cap(buf) is not enough, reallocate new buffer.
+func reserveBuffer(buf []byte, appendSize int) []byte {
+	newSize := len(buf) + appendSize
+	if cap(buf) < newSize {
+		// Grow buffer exponentially
+		newBuf := make([]byte, len(buf)*2+appendSize)
+		copy(newBuf, buf)
+		buf = newBuf
+	}
+	return buf[:newSize]
+}
+
+// escapeBytesBackslash escapes []byte with backslashes (\)
+// This escapes the contents of a string (provided as []byte) by adding backslashes before special
+// characters, and turning others into specific escape sequences, such as
+// turning newlines into \n and null bytes into \0.
+// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L823-L932
+func escapeBytesBackslash(buf, v []byte) []byte {
+	pos := len(buf)
+	buf = reserveBuffer(buf, len(v)*2)
+
+	for _, c := range v {
+		switch c {
+		case '\x00':
+			buf[pos] = '\\'
+			buf[pos+1] = '0'
+			pos += 2
+		case '\n':
+			buf[pos] = '\\'
+			buf[pos+1] = 'n'
+			pos += 2
+		case '\r':
+			buf[pos] = '\\'
+			buf[pos+1] = 'r'
+			pos += 2
+		case '\x1a':
+			buf[pos] = '\\'
+			buf[pos+1] = 'Z'
+			pos += 2
+		case '\'':
+			buf[pos] = '\\'
+			buf[pos+1] = '\''
+			pos += 2
+		case '"':
+			buf[pos] = '\\'
+			buf[pos+1] = '"'
+			pos += 2
+		case '\\':
+			buf[pos] = '\\'
+			buf[pos+1] = '\\'
+			pos += 2
+		default:
+			buf[pos] = c
+			pos += 1
+		}
+	}
+
+	return buf[:pos]
+}
+
+// escapeStringBackslash is similar to escapeBytesBackslash but for string.
+func escapeStringBackslash(buf []byte, v string) []byte {
+	pos := len(buf)
+	buf = reserveBuffer(buf, len(v)*2)
+
+	for i := 0; i < len(v); i++ {
+		c := v[i]
+		switch c {
+		case '\x00':
+			buf[pos] = '\\'
+			buf[pos+1] = '0'
+			pos += 2
+		case '\n':
+			buf[pos] = '\\'
+			buf[pos+1] = 'n'
+			pos += 2
+		case '\r':
+			buf[pos] = '\\'
+			buf[pos+1] = 'r'
+			pos += 2
+		case '\x1a':
+			buf[pos] = '\\'
+			buf[pos+1] = 'Z'
+			pos += 2
+		case '\'':
+			buf[pos] = '\\'
+			buf[pos+1] = '\''
+			pos += 2
+		case '"':
+			buf[pos] = '\\'
+			buf[pos+1] = '"'
+			pos += 2
+		case '\\':
+			buf[pos] = '\\'
+			buf[pos+1] = '\\'
+			pos += 2
+		default:
+			buf[pos] = c
+			pos += 1
+		}
+	}
+
+	return buf[:pos]
+}
+
+// escapeBytesQuotes escapes apostrophes in []byte by doubling them up.
+// This escapes the contents of a string by doubling up any apostrophes that
+// it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
+// effect on the server.
+// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L963-L1038
+func escapeBytesQuotes(buf, v []byte) []byte {
+	pos := len(buf)
+	buf = reserveBuffer(buf, len(v)*2)
+
+	for _, c := range v {
+		if c == '\'' {
+			buf[pos] = '\''
+			buf[pos+1] = '\''
+			pos += 2
+		} else {
+			buf[pos] = c
+			pos++
+		}
+	}
+
+	return buf[:pos]
+}
+
+// escapeStringQuotes is similar to escapeBytesQuotes but for string.
+func escapeStringQuotes(buf []byte, v string) []byte {
+	pos := len(buf)
+	buf = reserveBuffer(buf, len(v)*2)
+
+	for i := 0; i < len(v); i++ {
+		c := v[i]
+		if c == '\'' {
+			buf[pos] = '\''
+			buf[pos+1] = '\''
+			pos += 2
+		} else {
+			buf[pos] = c
+			pos++
+		}
+	}
+
+	return buf[:pos]
+}

+ 422 - 0
src/mysql/mysql.go

@@ -0,0 +1,422 @@
+package mysql
+
+import (
+	"bytes"
+	"database/sql"
+	"fmt"
+	"log"
+	"reflect"
+	"strings"
+	"time"
+
+	_ "github.com/go-sql-driver/mysql"
+)
+
+type Mysql struct {
+	Address      string  //数据库地址:端口
+	UserName     string  //用户名
+	PassWord     string  //密码
+	DBName       string  //数据库名
+	DB           *sql.DB //数据库连接池对象
+	MaxOpenConns int     //用于设置最大打开的连接数,默认值为0表示不限制。
+	MaxIdleConns int     //用于设置闲置的连接数。
+}
+
+func (m *Mysql) Init() {
+	if m.MaxOpenConns <= 0 {
+		m.MaxOpenConns = 30
+	}
+	if m.MaxIdleConns <= 0 {
+		m.MaxIdleConns = 6
+	}
+	var err error
+	m.DB, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8", m.UserName, m.PassWord, m.Address, m.DBName))
+	if err != nil {
+		log.Println(err)
+		return
+	}
+	m.DB.SetMaxOpenConns(m.MaxOpenConns)
+	m.DB.SetMaxIdleConns(m.MaxIdleConns)
+	m.DB.SetConnMaxLifetime(14400 * time.Second)
+	err = m.DB.Ping()
+	if err != nil {
+		log.Println(err)
+	}
+}
+
+//新增
+func (m *Mysql) Insert(tableName string, data map[string]interface{}) int64 {
+	return m.InsertByTx(nil, tableName, data)
+}
+
+//带有事务的新增
+func (m *Mysql) InsertByTx(tx *sql.Tx, tableName string, data map[string]interface{}) int64 {
+	fields := []string{}
+	values := []interface{}{}
+	placeholders := []string{}
+	if tableName == "dataexport_order" {
+		if _, ok := data["user_nickname"]; ok {
+			data["user_nickname"] = ""
+		}
+	}
+	for k, v := range data {
+		fields = append(fields, k)
+		values = append(values, v)
+		placeholders = append(placeholders, "?")
+	}
+	q := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", tableName, strings.Join(fields, ","), strings.Join(placeholders, ","))
+	log.Println("mysql", q, values)
+	return m.InsertBySqlByTx(tx, q, values...)
+}
+
+//sql语句新增
+func (m *Mysql) InsertBySql(sql string, args ...interface{}) int64 {
+	return m.InsertBySqlByTx(nil, sql, args...)
+}
+
+//带有事务的sql语句新增
+func (m *Mysql) InsertBySqlByTx(tx *sql.Tx, sql string, args ...interface{}) int64 {
+	result, _ := m.ExecBySqlByTx(tx, sql, args...)
+	if result == nil {
+		return -1
+	}
+	id, err := result.LastInsertId()
+	if err != nil {
+		log.Println(err)
+		return -1
+	}
+	return id
+}
+
+//批量新增
+func (m *Mysql) InsertBatch(tableName string, fields []string, values []interface{}) (int64, int64) {
+	return m.InsertBatchByTx(nil, tableName, fields, values)
+}
+
+//带事务的批量新增
+func (m *Mysql) InsertBatchByTx(tx *sql.Tx, tableName string, fields []string, values []interface{}) (int64, int64) {
+	placeholders := []string{}
+	for range fields {
+		placeholders = append(placeholders, "?")
+	}
+	placeholder := strings.Join(placeholders, ",")
+	array := []string{}
+	for i := 0; i < len(values)/len(fields); i++ {
+		array = append(array, fmt.Sprintf("(%s)", placeholder))
+	}
+	q := fmt.Sprintf("INSERT INTO %s (%s) VALUES %s", tableName, strings.Join(fields, ","), strings.Join(array, ","))
+	result, _ := m.ExecBySqlByTx(tx, q, values...)
+	if result == nil {
+		return -1, -1
+	}
+	v1, e1 := result.RowsAffected()
+	if e1 != nil {
+		log.Println(e1)
+		return -1, -1
+	}
+	v2, e2 := result.LastInsertId()
+	if e2 != nil {
+		log.Println(e2)
+		return -1, -1
+	}
+	return v1, v2
+}
+
+//sql语句执行
+func (m *Mysql) ExecBySql(sql string, args ...interface{}) (sql.Result, error) {
+	return m.ExecBySqlByTx(nil, sql, args...)
+}
+
+//sql语句执行,带有事务
+func (m *Mysql) ExecBySqlByTx(tx *sql.Tx, q string, args ...interface{}) (sql.Result, error) {
+	var stmtIns *sql.Stmt
+	var err error
+	if tx == nil {
+		stmtIns, err = m.DB.Prepare(q)
+	} else {
+		stmtIns, err = tx.Prepare(q)
+	}
+	if err != nil {
+		log.Println(err)
+		return nil, err
+	}
+	defer stmtIns.Close()
+	result, err := stmtIns.Exec(args...)
+	if err != nil {
+		log.Println(err)
+		return nil, err
+	}
+	return result, nil
+}
+
+/*不等于 map[string]string{"ne":"1"}
+ *不等于多个 map[string]string{"notin":[]interface{}{1,2}}
+ *字段为空 map[string]string{"name":"$isNull"}
+ *字段不为空 map[string]string{"name":"$isNotNull"}
+ */
+func (m *Mysql) Find(tableName string, query map[string]interface{}, fields, order string, start, pageSize int) *[]map[string]interface{} {
+	fs := []string{}
+	vs := []interface{}{}
+	for k, v := range query {
+		rt := reflect.TypeOf(v)
+		rv := reflect.ValueOf(v)
+		if rt.Kind() == reflect.Map {
+			for _, rv_k := range rv.MapKeys() {
+				if rv_k.String() == "ne" {
+					fs = append(fs, fmt.Sprintf("%s!=?", k))
+					vs = append(vs, rv.MapIndex(rv_k).Interface())
+				}
+				if rv_k.String() == "notin" {
+					if len(rv.MapIndex(rv_k).Interface().([]interface{})) > 0 {
+						for _, v := range rv.MapIndex(rv_k).Interface().([]interface{}) {
+							fs = append(fs, fmt.Sprintf("%s!=?", k))
+							vs = append(vs, v)
+						}
+					}
+				}
+			}
+		} else {
+			if v == "$isNull" {
+				fs = append(fs, fmt.Sprintf("%s is null", k))
+			} else if v == "$isNotNull" {
+				fs = append(fs, fmt.Sprintf("%s is not null", k))
+			} else {
+				fs = append(fs, fmt.Sprintf("%s=?", k))
+				vs = append(vs, v)
+			}
+		}
+	}
+	var buffer bytes.Buffer
+	buffer.WriteString("select ")
+	if fields == "" {
+		buffer.WriteString("*")
+	} else {
+		buffer.WriteString(fields)
+	}
+	buffer.WriteString(" from ")
+	buffer.WriteString(tableName)
+	if len(fs) > 0 {
+		buffer.WriteString(" where ")
+		buffer.WriteString(strings.Join(fs, " and "))
+	}
+	if order != "" {
+		buffer.WriteString(" order by ")
+		buffer.WriteString(order)
+	}
+	if start > -1 && pageSize > 0 {
+		buffer.WriteString(" limit ")
+		buffer.WriteString(fmt.Sprint(start))
+		buffer.WriteString(",")
+		buffer.WriteString(fmt.Sprint(pageSize))
+	}
+	q := buffer.String()
+	log.Println(q, vs)
+	return m.SelectBySql(q, vs...)
+}
+
+//sql语句查询
+func (m *Mysql) SelectBySql(sql string, args ...interface{}) *[]map[string]interface{} {
+	return m.SelectBySqlByTx(nil, sql, args...)
+}
+func (m *Mysql) SelectBySqlByTx(tx *sql.Tx, q string, args ...interface{}) *[]map[string]interface{} {
+	var stmtOut *sql.Stmt
+	var err error
+	if tx == nil {
+		stmtOut, err = m.DB.Prepare(q)
+	} else {
+		stmtOut, err = tx.Prepare(q)
+	}
+	if err != nil {
+		log.Println(err)
+		return nil
+	}
+	defer stmtOut.Close()
+
+	rows, err := stmtOut.Query(args...)
+	if err != nil {
+		log.Println(err)
+		return nil
+	}
+	if rows != nil {
+		defer rows.Close()
+	}
+	columns, err := rows.Columns()
+	if err != nil {
+		log.Println(err)
+		return nil
+	}
+	list := []map[string]interface{}{}
+	for rows.Next() {
+		scanArgs := make([]interface{}, len(columns))
+		values := make([]interface{}, len(columns))
+		ret := make(map[string]interface{})
+		for k, _ := range values {
+			scanArgs[k] = &values[k]
+		}
+		err = rows.Scan(scanArgs...)
+		if err != nil {
+			log.Println(err)
+			break
+		}
+
+		for i, col := range values {
+			if v, ok := col.([]uint8); ok {
+				ret[columns[i]] = string(v)
+			} else {
+				ret[columns[i]] = col
+			}
+		}
+		list = append(list, ret)
+	}
+	return &list
+}
+func (m *Mysql) FindOne(tableName string, query map[string]interface{}, fields, order string) *map[string]interface{} {
+	list := m.Find(tableName, query, fields, order, 0, 1)
+	if list != nil && len(*list) == 1 {
+		temp := (*list)[0]
+		return &temp
+	}
+	return nil
+}
+
+//修改
+func (m *Mysql) Update(tableName string, query, update map[string]interface{}) bool {
+	return m.UpdateByTx(nil, tableName, query, update)
+}
+
+//带事务的修改
+func (m *Mysql) UpdateByTx(tx *sql.Tx, tableName string, query, update map[string]interface{}) bool {
+	q_fs := []string{}
+	u_fs := []string{}
+	values := []interface{}{}
+	for k, v := range update {
+		q_fs = append(q_fs, fmt.Sprintf("%s=?", k))
+		values = append(values, v)
+	}
+	for k, v := range query {
+		u_fs = append(u_fs, fmt.Sprintf("%s=?", k))
+		values = append(values, v)
+	}
+	q := fmt.Sprintf("update %s set %s where %s", tableName, strings.Join(q_fs, ","), strings.Join(u_fs, " and "))
+	log.Println(q, values)
+	return m.UpdateOrDeleteBySqlByTx(tx, q, values...) > 0
+}
+
+//删除
+func (m *Mysql) Delete(tableName string, query map[string]interface{}) bool {
+	return m.DeleteByTx(nil, tableName, query)
+}
+func (m *Mysql) DeleteByTx(tx *sql.Tx, tableName string, query map[string]interface{}) bool {
+	fields := []string{}
+	values := []interface{}{}
+	for k, v := range query {
+		fields = append(fields, fmt.Sprintf("%s=?", k))
+		values = append(values, v)
+	}
+	q := fmt.Sprintf("delete from %s where %s", tableName, strings.Join(fields, " and "))
+	log.Println(q, values)
+	return m.UpdateOrDeleteBySqlByTx(tx, q, values...) > 0
+}
+
+//修改或删除
+func (m *Mysql) UpdateOrDeleteBySql(sql string, args ...interface{}) int64 {
+	return m.UpdateOrDeleteBySqlByTx(nil, sql, args...)
+}
+
+//带事务的修改或删除
+func (m *Mysql) UpdateOrDeleteBySqlByTx(tx *sql.Tx, sql string, args ...interface{}) int64 {
+	result, err := m.ExecBySqlByTx(tx, sql, args...)
+	if err != nil {
+		log.Println(err)
+		return -1
+	}
+	count, err := result.RowsAffected()
+	if err != nil {
+		log.Println(err)
+		return -1
+	}
+	return count
+}
+
+//总数
+func (m *Mysql) Count(tableName string, query map[string]interface{}) int64 {
+	fields := []string{}
+	values := []interface{}{}
+	for k, v := range query {
+		if v == "$isNull" {
+			fields = append(fields, fmt.Sprintf("%s is null", k))
+		} else if v == "$isNotNull" {
+			fields = append(fields, fmt.Sprintf("%s is not null", k))
+		} else {
+			fields = append(fields, fmt.Sprintf("%s=?", k))
+			values = append(values, v)
+		}
+	}
+	q := fmt.Sprintf("select count(1) as count from %s", tableName)
+	if len(query) > 0 {
+		q += fmt.Sprintf(" where %s", strings.Join(fields, " and "))
+	}
+	log.Println(q, values)
+	return m.CountBySql(q, values...)
+}
+func (m *Mysql) CountBySql(sql string, args ...interface{}) int64 {
+	stmtIns, err := m.DB.Prepare(sql)
+	if err != nil {
+		log.Println(err)
+		return -1
+	}
+	defer stmtIns.Close()
+
+	rows, err := stmtIns.Query(args...)
+	if err != nil {
+		log.Println(err)
+		return -1
+	}
+	if rows != nil {
+		defer rows.Close()
+	}
+	var count int64 = -1
+	if rows.Next() {
+		err = rows.Scan(&count)
+		if err != nil {
+			log.Println(err)
+		}
+	}
+	return count
+}
+
+//执行事务
+func (m *Mysql) ExecTx(msg string, f func(tx *sql.Tx) bool) bool {
+	tx, err := m.DB.Begin()
+	if err != nil {
+		log.Println(msg, "获取事务错误", err)
+	} else {
+		if f(tx) {
+			if err := tx.Commit(); err != nil {
+				log.Println(msg, "提交事务错误", err)
+			} else {
+				return true
+			}
+		} else {
+			if err := tx.Rollback(); err != nil {
+				log.Println(msg, "事务回滚错误", err)
+			}
+		}
+	}
+	return false
+}
+
+/*************方法命名不规范,上面有替代方法*************/
+func (m *Mysql) Query(query string, args ...interface{}) *[]map[string]interface{} {
+	return m.SelectBySql(query, args...)
+}
+
+func (m *Mysql) QueryCount(query string, args ...interface{}) (count int) {
+	count = -1
+	if !strings.Contains(strings.ToLower(query), "count(*)") {
+		fmt.Println("QueryCount need query like < select count(*) from ..... >")
+		return
+	}
+	count = int(m.CountBySql(query, args...))
+	return
+}

File diff suppressed because it is too large
+ 35 - 0
src/mysql/mysql_test.go


Some files were not shown because too many files changed in this diff