Merge pull request #2019 from tonglil/fix-sql-bounds

Fix sql bounds
This commit is contained in:
Brad Rydzewski 2017-05-02 23:54:48 +02:00 committed by GitHub
commit 3940205573
19 changed files with 2413 additions and 875 deletions

View file

@ -11,18 +11,46 @@
# Individual Persons # Individual Persons
Aaron Hopkins <go-sql-driver at die.net>
Arne Hormann <arnehormann at gmail.com> Arne Hormann <arnehormann at gmail.com>
Carlos Nieto <jose.carlos at menteslibres.net> Carlos Nieto <jose.carlos at menteslibres.net>
Chris Moos <chris at tech9computers.com>
Daniel Nichter <nil at codenode.com>
Daniël van Eeden <git at myname.nl>
DisposaBoy <disposaboy at dby.me>
Frederick Mayle <frederickmayle at gmail.com>
Gustavo Kristic <gkristic at gmail.com>
Hanno Braun <mail at hannobraun.com> Hanno Braun <mail at hannobraun.com>
Henri Yandell <flamefew at gmail.com>
Hirotaka Yamamoto <ymmt2005 at gmail.com>
INADA Naoki <songofacandy at gmail.com>
James Harr <james.harr at gmail.com> James Harr <james.harr at gmail.com>
Jian Zhen <zhenjl at gmail.com>
Joshua Prunier <joshua.prunier at gmail.com>
Julien Lefevre <julien.lefevr at gmail.com>
Julien Schmidt <go-sql-driver at julienschmidt.com> Julien Schmidt <go-sql-driver at julienschmidt.com>
Kamil Dziedzic <kamil at klecza.pl>
Kevin Malachowski <kevin at chowski.com>
Lennart Rudolph <lrudolph at hmc.edu>
Leonardo YongUk Kim <dalinaum at gmail.com> Leonardo YongUk Kim <dalinaum at gmail.com>
Luca Looz <luca.looz92 at gmail.com>
Lucas Liu <extrafliu at gmail.com> Lucas Liu <extrafliu at gmail.com>
Luke Scott <luke at webconnex.com> Luke Scott <luke at webconnex.com>
Michael Woolnough <michael.woolnough at gmail.com> Michael Woolnough <michael.woolnough at gmail.com>
Nicola Peduzzi <thenikso at gmail.com> Nicola Peduzzi <thenikso at gmail.com>
Olivier Mengué <dolmen at cpan.org>
Paul Bonser <misterpib at gmail.com>
Runrioter Wung <runrioter at gmail.com>
Soroush Pour <me at soroushjp.com>
Stan Putrya <root.vagner at gmail.com>
Stanley Gunawan <gunawan.stanley at gmail.com>
Xiangyu Hu <xiangyu.hu at outlook.com>
Xiaobing Jiang <s7v7nislands at gmail.com> Xiaobing Jiang <s7v7nislands at gmail.com>
Xiuming Chen <cc at cxm.cc>
Zhenye Xie <xiezhenye at gmail.com>
# Organizations # Organizations
Barracuda Networks, Inc.
Google Inc. Google Inc.
Stripe Inc.

View file

@ -1,3 +1,82 @@
## Version 1.3 (2016-12-01)
Changes:
- Go 1.1 is no longer supported
- Use decimals fields in MySQL to format time types (#249)
- Buffer optimizations (#269)
- TLS ServerName defaults to the host (#283)
- Refactoring (#400, #410, #437)
- Adjusted documentation for second generation CloudSQL (#485)
- Documented DSN system var quoting rules (#502)
- Made statement.Close() calls idempotent to avoid errors in Go 1.6+ (#512)
New Features:
- Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249)
- Support for returning table alias on Columns() (#289, #359, #382)
- Placeholder interpolation, can be actived with the DSN parameter `interpolateParams=true` (#309, #318, #490)
- Support for uint64 parameters with high bit set (#332, #345)
- Cleartext authentication plugin support (#327)
- Exported ParseDSN function and the Config struct (#403, #419, #429)
- Read / Write timeouts (#401)
- Support for JSON field type (#414)
- Support for multi-statements and multi-results (#411, #431)
- DSN parameter to set the driver-side max_allowed_packet value manually (#489)
- Native password authentication plugin support (#494, #524)
Bugfixes:
- Fixed handling of queries without columns and rows (#255)
- Fixed a panic when SetKeepAlive() failed (#298)
- Handle ERR packets while reading rows (#321)
- Fixed reading NULL length-encoded integers in MySQL 5.6+ (#349)
- Fixed absolute paths support in LOAD LOCAL DATA INFILE (#356)
- Actually zero out bytes in handshake response (#378)
- Fixed race condition in registering LOAD DATA INFILE handler (#383)
- Fixed tests with MySQL 5.7.9+ (#380)
- QueryUnescape TLS config names (#397)
- Fixed "broken pipe" error by writing to closed socket (#390)
- Fixed LOAD LOCAL DATA INFILE buffering (#424)
- Fixed parsing of floats into float64 when placeholders are used (#434)
- Fixed DSN tests with Go 1.7+ (#459)
- Handle ERR packets while waiting for EOF (#473)
- Invalidate connection on error while discarding additional results (#513)
- Allow terminating packets of length 0 (#516)
## 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) ## Version 1.1 (2013-11-02)
Changes: Changes:
@ -5,7 +84,7 @@ Changes:
- Go-MySQL-Driver now requires Go 1.1 - 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 - 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 - 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("")` - `[]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 '&'. - 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 - Use the IO buffer also for writing. This results in zero allocations (by the driver) for most queries
- Optimized the buffer for reading - Optimized the buffer for reading

View file

@ -4,28 +4,11 @@
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). 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 ## Contributing Code
By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file. 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. 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 ### Code Review
Everyone is invited to review and comment on pull requests. Everyone is invited to review and comment on pull requests.

View file

@ -4,8 +4,6 @@ A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) packa
![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin") ![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin")
**Version 1.1** (November 02, 2013)
--------------------------------------- ---------------------------------------
* [Features](#features) * [Features](#features)
* [Requirements](#requirements) * [Requirements](#requirements)
@ -28,7 +26,7 @@ A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) packa
## Features ## Features
* Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance") * Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance")
* Native Go implementation. No C-bindings, just pure Go * Native Go implementation. No C-bindings, just pure Go
* Connections over TCP/IPv4, TCP/IPv6 or Unix domain sockets * Connections over TCP/IPv4, TCP/IPv6, Unix domain sockets or [custom protocols](http://godoc.org/github.com/go-sql-driver/mysql#DialFunc)
* Automatic handling of broken connections * Automatic handling of broken connections
* Automatic Connection Pooling *(by database/sql package)* * Automatic Connection Pooling *(by database/sql package)*
* Supports queries larger than 16MB * Supports queries larger than 16MB
@ -36,10 +34,11 @@ A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) packa
* Intelligent `LONG DATA` handling in prepared statements * Intelligent `LONG DATA` handling in prepared statements
* Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support * Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support
* Optional `time.Time` parsing * Optional `time.Time` parsing
* Optional placeholder interpolation
## Requirements ## Requirements
* Go 1.1 or higher (use [v1.0](https://github.com/go-sql-driver/mysql/tags) for Go 1.0.x) * Go 1.2 or higher
* MySQL (Version 4.1 or higher), MariaDB or Percona Server * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
--------------------------------------- ---------------------------------------
@ -90,6 +89,8 @@ This has the same effect as an empty DSN string:
``` ```
Alternatively, [Config.FormatDSN](https://godoc.org/github.com/go-sql-driver/mysql#Config.FormatDSN) can be used to create a DSN string by filling a struct.
#### Password #### Password
Passwords can consist of any character. Escaping is **not** necessary. Passwords can consist of any character. Escaping is **not** necessary.
@ -107,25 +108,46 @@ For Unix domain sockets the address is the absolute path to the MySQL-Server-soc
#### Parameters #### Parameters
*Parameters are case-sensitive!* *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` ##### `allowAllFiles`
``` ```
Type: bool Type: bool
Valid Values: true, false Valid Values: true, false
Default: false Default: false
``` ```
`allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files. `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) [*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
##### `allowCleartextPasswords`
```
Type: bool
Valid Values: true, false
Default: false
```
`allowCleartextPasswords=true` allows using the [cleartext client side plugin](http://dev.mysql.com/doc/en/cleartext-authentication-plugin.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network.
##### `allowNativePasswords`
```
Type: bool
Valid Values: true, false
Default: false
```
`allowNativePasswords=true` allows the usage of the mysql native password method.
##### `allowOldPasswords` ##### `allowOldPasswords`
``` ```
Type: bool Type: bool
Valid Values: true, false Valid Values: true, false
Default: false Default: false
``` ```
`allowAllFiles=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). `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` ##### `charset`
@ -135,19 +157,60 @@ Valid Values: <name>
Default: none 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 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`). 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` ##### `clientFoundRows`
``` ```
Type: bool Type: bool
Valid Values: true, false Valid Values: true, false
Default: false Default: false
``` ```
`clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed. `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` ##### `loc`
@ -159,30 +222,63 @@ 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. 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.
Note that this sets the location for time.Time values but does not change MySQL's [time_zone setting](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html). For that see the [time_zone system variable](#system-variables), which can also be set as a DSN parameter.
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`. 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`.
##### `maxAllowedPacket`
```
Type: decimal number
Default: 0
```
Max packet size allowed in bytes. Use `maxAllowedPacket=0` to automatically fetch the `max_allowed_packet` variable from server.
##### `multiStatements`
```
Type: bool
Valid Values: true, false
Default: false
```
Allow multiple statements in one query. While this allows batch queries, it also greatly increases the risk of SQL injections. Only the result of the first query is returned, all other results are silently discarded.
When `multiStatements` is used, `?` parameters must only be used in the first statement.
##### `parseTime` ##### `parseTime`
``` ```
Type: bool Type: bool
Valid Values: true, false Valid Values: true, false
Default: false Default: false
``` ```
`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string` `parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
##### `readTimeout`
```
Type: decimal number
Default: 0
```
I/O read timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*.
##### `strict` ##### `strict`
``` ```
Type: bool Type: bool
Valid Values: true, false Valid Values: true, false
Default: false Default: false
``` ```
`strict=true` enables strict mode. MySQL warnings are treated as errors. `strict=true` enables a driver-side strict mode in which MySQL warnings are treated as errors. This mode should not be used in production as it may lead to data corruption in certain situations.
A server-side strict mode, which is safe for production use, can be set via the [`sql_mode`](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html) system variable.
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.
##### `timeout` ##### `timeout`
@ -191,29 +287,45 @@ Type: decimal number
Default: OS default 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). *Driver* side connection timeout. The value must be a decimal number with an 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` ##### `tls`
``` ```
Type: bool / string Type: bool / string
Valid Values: true, false, skip-verify, <name> Valid Values: true, false, skip-verify, <name>
Default: false 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). `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).
##### `writeTimeout`
```
Type: decimal number
Default: 0
```
I/O write timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*.
##### System Variables ##### System Variables
All other parameters are interpreted as system variables: Any other parameters are interpreted as system variables:
* `autocommit`: `"SET autocommit=<value>"` * `<boolean_var>=<value>`: `SET <boolean_var>=<value>`
* `time_zone`: `"SET time_zone=<value>"` * `<enum_var>=<value>`: `SET <enum_var>=<value>`
* [`tx_isolation`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `"SET tx_isolation=<value>"` * `<string_var>=%27<value>%27`: `SET <string_var>='<value>'`
* `param`: `"SET <param>=<value>"`
Rules:
* The values for string variables must be quoted with '
* The values must also be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!
(which implies values of string variables must be wrapped with `%27`)
Examples:
* `autocommit=1`: `SET autocommit=1`
* [`time_zone=%27Europe%2FParis%27`](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html): `SET time_zone='Europe/Paris'`
* [`tx_isolation=%27REPEATABLE-READ%27`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `SET tx_isolation='REPEATABLE-READ'`
*The values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!*
#### Examples #### Examples
``` ```
@ -228,9 +340,14 @@ root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local
user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true
``` ```
Treat warnings as errors by setting the system variable [`sql_mode`](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html):
```
user:password@/dbname?sql_mode=TRADITIONAL
```
TCP via IPv6: TCP via IPv6:
``` ```
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s 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: TCP on a remote host, e.g. Amazon RDS:
@ -238,9 +355,19 @@ TCP on a remote host, e.g. Amazon RDS:
id:password@tcp(your-amazonaws-uri.com:3306)/dbname id:password@tcp(your-amazonaws-uri.com:3306)/dbname
``` ```
Google Cloud SQL on App Engine (First Generation MySQL Server):
```
user@cloudsql(project-id:instance-name)/dbname
```
Google Cloud SQL on App Engine (Second Generation MySQL Server):
```
user@cloudsql(project-id:regionname:instance-name)/dbname
```
TCP using default port (3306) on localhost: TCP using default port (3306) on localhost:
``` ```
user:password@tcp/dbname&charset=utf8mb4,utf8&sys_var=esc%40ped user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped
``` ```
Use the default protocol (tcp) and host (localhost:3306): Use the default protocol (tcp) and host (localhost:3306):
@ -261,7 +388,7 @@ 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)). 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. 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. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore.
See the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details. See the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
@ -277,7 +404,11 @@ Alternatively you can use the [`NullTime`](http://godoc.org/github.com/go-sql-dr
### Unicode support ### Unicode support
Since version 1.1 Go-MySQL-Driver automatically uses the collation `utf8_general_ci` by default. Adding `&charset=utf8` (alias for `SET NAMES utf8`) to the DSN is not necessary anymore in most cases. 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. See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
@ -288,7 +419,7 @@ To run the driver tests you may need to adjust the configuration. See the [Testi
Go-MySQL-Driver is not feature-complete yet. Your help is very appreciated. 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). 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 [Contributing Guidelines](https://github.com/go-sql-driver/mysql/blob/master/CHANGELOG.md) for details. See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/CONTRIBUTING.md) for details.
--------------------------------------- ---------------------------------------
@ -300,9 +431,9 @@ Mozilla summarizes the license scope as follows:
That means: That means:
* You can **use** the **unchanged** source code both in private as also commercial * You can **use** the **unchanged** source code both in private and commercially
* You **needn't publish** the source code of your library as long the files licensed under the MPL 2.0 are **unchanged** * 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 **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. Please read the [MPL 2.0 FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html) if you have further questions regarding the license.

19
vendor/github.com/go-sql-driver/mysql/appengine.go generated vendored Normal file
View file

@ -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)
}

View file

@ -8,7 +8,11 @@
package mysql package mysql
import "io" import (
"io"
"net"
"time"
)
const defaultBufSize = 4096 const defaultBufSize = 4096
@ -18,25 +22,28 @@ const defaultBufSize = 4096
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish // The buffer is similar to bufio.Reader / Writer but zero-copy-ish
// Also highly optimized for this particular use case. // Also highly optimized for this particular use case.
type buffer struct { type buffer struct {
buf []byte buf []byte
rd io.Reader nc net.Conn
idx int idx int
length int length int
timeout time.Duration
} }
func newBuffer(rd io.Reader) *buffer { func newBuffer(nc net.Conn) buffer {
var b [defaultBufSize]byte var b [defaultBufSize]byte
return &buffer{ return buffer{
buf: b[:], buf: b[:],
rd: rd, nc: nc,
} }
} }
// fill reads into the buffer until at least _need_ bytes are in it // fill reads into the buffer until at least _need_ bytes are in it
func (b *buffer) fill(need int) error { func (b *buffer) fill(need int) error {
n := b.length
// move existing data to the beginning // move existing data to the beginning
if b.length > 0 && b.idx > 0 { if n > 0 && b.idx > 0 {
copy(b.buf[0:b.length], b.buf[b.idx:]) copy(b.buf[0:n], b.buf[b.idx:])
} }
// grow buffer if necessary // grow buffer if necessary
@ -52,19 +59,33 @@ func (b *buffer) fill(need int) error {
b.idx = 0 b.idx = 0
for { for {
n, err := b.rd.Read(b.buf[b.length:]) if b.timeout > 0 {
b.length += n if err := b.nc.SetReadDeadline(time.Now().Add(b.timeout)); err != nil {
return err
}
}
if err == nil { nn, err := b.nc.Read(b.buf[n:])
if b.length < need { n += nn
switch err {
case nil:
if n < need {
continue continue
} }
b.length = n
return nil return nil
case io.EOF:
if n >= need {
b.length = n
return nil
}
return io.ErrUnexpectedEOF
default:
return err
} }
if b.length >= need && err == io.EOF {
return nil
}
return err
} }
} }

250
vendor/github.com/go-sql-driver/mysql/collations.go generated vendored Normal file
View file

@ -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 = "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[string]bool{
"big5_chinese_ci": true,
"sjis_japanese_ci": true,
"gbk_chinese_ci": true,
"big5_bin": true,
"gb2312_bin": true,
"gbk_bin": true,
"sjis_bin": true,
"cp932_japanese_ci": true,
"cp932_bin": true,
}

View file

@ -9,46 +9,32 @@
package mysql package mysql
import ( import (
"crypto/tls"
"database/sql/driver" "database/sql/driver"
"errors"
"net" "net"
"strconv"
"strings" "strings"
"time" "time"
) )
type mysqlConn struct { type mysqlConn struct {
buf *buffer buf buffer
netConn net.Conn netConn net.Conn
affectedRows uint64 affectedRows uint64
insertId uint64 insertId uint64
cfg *config cfg *Config
maxPacketAllowed int maxAllowedPacket int
maxWriteSize int maxWriteSize int
writeTimeout time.Duration
flags clientFlag flags clientFlag
status statusFlag
sequence uint8 sequence uint8
parseTime bool parseTime bool
strict bool strict bool
} }
type config struct { // Handles parameters set in DSN after the connection is established
user string
passwd string
net string
addr string
dbname string
params map[string]string
loc *time.Location
timeout time.Duration
tls *tls.Config
allowAllFiles bool
allowOldPasswords bool
clientFoundRows bool
}
// Handles parameters set in DSN
func (mc *mysqlConn) handleParams() (err error) { func (mc *mysqlConn) handleParams() (err error) {
for param, val := range mc.cfg.params { for param, val := range mc.cfg.Params {
switch param { switch param {
// Charset // Charset
case "charset": case "charset":
@ -64,27 +50,6 @@ func (mc *mysqlConn) handleParams() (err error) {
return 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 // System Vars
default: default:
err = mc.exec("SET " + param + "=" + val + "") err = mc.exec("SET " + param + "=" + val + "")
@ -99,7 +64,7 @@ func (mc *mysqlConn) handleParams() (err error) {
func (mc *mysqlConn) Begin() (driver.Tx, error) { func (mc *mysqlConn) Begin() (driver.Tx, error) {
if mc.netConn == nil { if mc.netConn == nil {
errLog.Print(errInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
err := mc.exec("START TRANSACTION") err := mc.exec("START TRANSACTION")
@ -113,20 +78,33 @@ func (mc *mysqlConn) Begin() (driver.Tx, error) {
func (mc *mysqlConn) Close() (err error) { func (mc *mysqlConn) Close() (err error) {
// Makes Close idempotent // Makes Close idempotent
if mc.netConn != nil { if mc.netConn != nil {
mc.writeCommandPacket(comQuit) err = mc.writeCommandPacket(comQuit)
mc.netConn.Close()
mc.netConn = nil
} }
mc.cfg = nil mc.cleanup()
mc.buf = nil
return return
} }
// Closes the network connection and unsets internal variables. Do not call this
// function after successfully authentication, call Close instead. This function
// is called before auth or on auth failure because MySQL will have already
// closed the network connection.
func (mc *mysqlConn) cleanup() {
// Makes cleanup idempotent
if mc.netConn != nil {
if err := mc.netConn.Close(); err != nil {
errLog.Print(err)
}
mc.netConn = nil
}
mc.cfg = nil
mc.buf.nc = nil
}
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) { func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
if mc.netConn == nil { if mc.netConn == nil {
errLog.Print(errInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
// Send command // Send command
@ -156,28 +134,156 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
return stmt, err return stmt, err
} }
func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) {
// Number of ? should be same to len(args)
if strings.Count(query, "?") != len(args) {
return "", driver.ErrSkip
}
buf := mc.buf.takeCompleteBuffer()
if buf == nil {
// can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer)
return "", driver.ErrBadConn
}
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, "_binary'"...)
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.maxAllowedPacket {
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) { func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
if mc.netConn == nil { if mc.netConn == nil {
errLog.Print(errInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
if len(args) == 0 { // no args, fastpath if len(args) != 0 {
mc.affectedRows = 0 if !mc.cfg.InterpolateParams {
mc.insertId = 0 return nil, driver.ErrSkip
err := mc.exec(query)
if err == nil {
return &mysqlResult{
affectedRows: int64(mc.affectedRows),
insertId: int64(mc.insertId),
}, err
} }
return nil, err // 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
// with args, must use prepared stmt err := mc.exec(query)
return nil, driver.ErrSkip if err == nil {
return &mysqlResult{
affectedRows: int64(mc.affectedRows),
insertId: int64(mc.insertId),
}, err
}
return nil, err
} }
// Internal function to execute commands // Internal function to execute commands
@ -203,32 +309,41 @@ func (mc *mysqlConn) exec(query string) error {
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) { func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
if mc.netConn == nil { if mc.netConn == nil {
errLog.Print(errInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
if len(args) == 0 { // no args, fastpath if len(args) != 0 {
// Send command if !mc.cfg.InterpolateParams {
err := mc.writeCommandPacketStr(comQuery, query) return nil, driver.ErrSkip
if err == nil {
// Read Result
var resLen int
resLen, err = mc.readResultSetHeaderPacket()
if err == nil {
rows := new(textRows)
rows.mc = mc
if resLen > 0 {
// Columns
rows.columns, err = mc.readColumns(resLen)
}
return rows, err
}
} }
return nil, err // 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
// with args, must use prepared stmt if resLen == 0 {
return nil, driver.ErrSkip // 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 // Gets the value of the given MySQL System Variable
@ -244,6 +359,7 @@ func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
if err == nil { if err == nil {
rows := new(textRows) rows := new(textRows)
rows.mc = mc rows.mc = mc
rows.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
if resLen > 0 { if resLen > 0 {
// Columns // Columns

View file

@ -11,7 +11,7 @@ package mysql
const ( const (
minProtocolVersion byte = 10 minProtocolVersion byte = 10
maxPacketSize = 1<<24 - 1 maxPacketSize = 1<<24 - 1
timeFormat = "2006-01-02 15:04:05" timeFormat = "2006-01-02 15:04:05.999999"
) )
// MySQL constants documentation: // MySQL constants documentation:
@ -24,6 +24,7 @@ const (
iERR byte = 0xff iERR byte = 0xff
) )
// https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
type clientFlag uint32 type clientFlag uint32
const ( const (
@ -45,6 +46,13 @@ const (
clientSecureConn clientSecureConn
clientMultiStatements clientMultiStatements
clientMultiResults clientMultiResults
clientPSMultiResults
clientPluginAuth
clientConnectAttrs
clientPluginAuthLenEncClientData
clientCanHandleExpiredPasswords
clientSessionTrack
clientDeprecateEOF
) )
const ( const (
@ -68,7 +76,7 @@ const (
comBinlogDump comBinlogDump
comTableDump comTableDump
comConnectOut comConnectOut
comRegiserSlave comRegisterSlave
comStmtPrepare comStmtPrepare
comStmtExecute comStmtExecute
comStmtSendLongData comStmtSendLongData
@ -78,6 +86,7 @@ const (
comStmtFetch comStmtFetch
) )
// https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType
const ( const (
fieldTypeDecimal byte = iota fieldTypeDecimal byte = iota
fieldTypeTiny fieldTypeTiny
@ -98,7 +107,8 @@ const (
fieldTypeBit fieldTypeBit
) )
const ( const (
fieldTypeNewDecimal byte = iota + 0xf6 fieldTypeJSON byte = iota + 0xf5
fieldTypeNewDecimal
fieldTypeEnum fieldTypeEnum
fieldTypeSet fieldTypeSet
fieldTypeTinyBLOB fieldTypeTinyBLOB
@ -131,12 +141,23 @@ const (
flagUnknown4 flagUnknown4
) )
// http://dev.mysql.com/doc/internals/en/status-flags.html
type statusFlag uint16
const ( const (
collation_ascii_general_ci byte = 11 statusInTrans statusFlag = 1 << iota
collation_utf8_general_ci byte = 33 statusInAutocommit
collation_utf8mb4_general_ci byte = 45 statusReserved // Not in documentation
collation_utf8mb4_bin byte = 46 statusMoreResultsExists
collation_latin1_general_ci byte = 48 statusNoGoodIndexUsed
collation_binary byte = 63 statusNoIndexUsed
collation_utf8mb4_unicode_ci byte = 224 statusCursorExists
statusLastRowSent
statusDbDropped
statusNoBackslashEscapes
statusMetadataChanged
statusQueryWasSlow
statusPsOutParams
statusInTransReadonly
statusSessionStateChanged
) )

View file

@ -4,7 +4,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file, // 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/. // You can obtain one at http://mozilla.org/MPL/2.0/.
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package // Package mysql provides a MySQL driver for Go's database/sql package
// //
// The driver should be used via the database/sql package: // The driver should be used via the database/sql package:
// //
@ -22,76 +22,106 @@ import (
"net" "net"
) )
// This struct is exported to make the driver directly accessible. // MySQLDriver is exported to make the driver directly accessible.
// In general the driver is used via the database/sql package. // In general the driver is used via the database/sql package.
type MySQLDriver struct{} 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. // Open new Connection.
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how // See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
// the DSN string is formated // the DSN string is formated
func (d *MySQLDriver) Open(dsn string) (driver.Conn, error) { func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
var err error var err error
// New mysqlConn // New mysqlConn
mc := &mysqlConn{ mc := &mysqlConn{
maxPacketAllowed: maxPacketSize, maxAllowedPacket: maxPacketSize,
maxWriteSize: maxPacketSize - 1, maxWriteSize: maxPacketSize - 1,
} }
mc.cfg, err = parseDSN(dsn) mc.cfg, err = ParseDSN(dsn)
if err != nil {
return nil, err
}
mc.parseTime = mc.cfg.ParseTime
mc.strict = mc.cfg.Strict
// Connect to Server
if dial, ok := dials[mc.cfg.Net]; ok {
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 { if err != nil {
return nil, err return nil, err
} }
// Connect to Server // Enable TCP Keepalives on TCP connections
nd := net.Dialer{Timeout: mc.cfg.timeout} if tc, ok := mc.netConn.(*net.TCPConn); ok {
mc.netConn, err = nd.Dial(mc.cfg.net, mc.cfg.addr) if err := tc.SetKeepAlive(true); err != nil {
if err != nil { // Don't send COM_QUIT before handshake.
return nil, err mc.netConn.Close()
mc.netConn = nil
return nil, err
}
} }
mc.buf = newBuffer(mc.netConn) mc.buf = newBuffer(mc.netConn)
// Set I/O timeouts
mc.buf.timeout = mc.cfg.ReadTimeout
mc.writeTimeout = mc.cfg.WriteTimeout
// Reading Handshake Initialization Packet // Reading Handshake Initialization Packet
cipher, err := mc.readInitPacket() cipher, err := mc.readInitPacket()
if err != nil { if err != nil {
mc.Close() mc.cleanup()
return nil, err return nil, err
} }
// Send Client Authentication Packet // Send Client Authentication Packet
if err = mc.writeAuthPacket(cipher); err != nil { if err = mc.writeAuthPacket(cipher); err != nil {
mc.Close() mc.cleanup()
return nil, err return nil, err
} }
// Read Result Packet // Handle response to auth packet, switch methods if possible
err = mc.readResultOK() if err = handleAuthResult(mc, cipher); err != nil {
if err != nil { // Authentication failed and MySQL has already closed the connection
// Retry with old authentication method, if allowed // (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
if mc.cfg.allowOldPasswords && err == errOldPassword { // Do not send COM_QUIT, just cleanup and return the error.
if err = mc.writeOldAuthPacket(cipher); err != nil { mc.cleanup()
mc.Close() return nil, err
return nil, err }
}
if err = mc.readResultOK(); err != nil { if mc.cfg.MaxAllowedPacket > 0 {
mc.Close() mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket
return nil, err } else {
} // Get max allowed packet size
} else { maxap, err := mc.getSystemVar("max_allowed_packet")
if err != nil {
mc.Close() mc.Close()
return nil, err return nil, err
} }
mc.maxAllowedPacket = stringToInt(maxap) - 1
} }
if mc.maxAllowedPacket < maxPacketSize {
// Get max allowed packet size mc.maxWriteSize = mc.maxAllowedPacket
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 // Handle DSN Params
@ -104,6 +134,50 @@ func (d *MySQLDriver) Open(dsn string) (driver.Conn, error) {
return mc, nil return mc, nil
} }
func handleAuthResult(mc *mysqlConn, oldCipher []byte) error {
// Read Result Packet
cipher, err := mc.readResultOK()
if err == nil {
return nil // auth successful
}
if mc.cfg == nil {
return err // auth failed and retry not possible
}
// Retry auth if configured to do so.
if mc.cfg.AllowOldPasswords && err == ErrOldPassword {
// Retry with old authentication method. Note: there are edge cases
// where this should work but doesn't; this is currently "wontfix":
// https://github.com/go-sql-driver/mysql/issues/184
// If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
// sent and we have to keep using the cipher sent in the init packet.
if cipher == nil {
cipher = oldCipher
}
if err = mc.writeOldAuthPacket(cipher); err != nil {
return err
}
_, err = mc.readResultOK()
} else if mc.cfg.AllowCleartextPasswords && err == ErrCleartextPassword {
// Retry with clear text password for
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
if err = mc.writeClearAuthPacket(); err != nil {
return err
}
_, err = mc.readResultOK()
} else if mc.cfg.AllowNativePasswords && err == ErrNativePassword {
if err = mc.writeNativeAuthPacket(cipher); err != nil {
return err
}
_, err = mc.readResultOK()
}
return err
}
func init() { func init() {
sql.Register("mysql", &MySQLDriver{}) sql.Register("mysql", &MySQLDriver{})
} }

548
vendor/github.com/go-sql-driver/mysql/dsn.go generated vendored Normal file
View file

@ -0,0 +1,548 @@
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2016 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"
"errors"
"fmt"
"net"
"net/url"
"strconv"
"strings"
"time"
)
var (
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 not be used with unsafe collations")
)
// Config is a configuration parsed from a DSN string
type Config struct {
User string // Username
Passwd string // Password (requires User)
Net string // Network type
Addr string // Network address (requires Net)
DBName string // Database name
Params map[string]string // Connection parameters
Collation string // Connection collation
Loc *time.Location // Location for time.Time values
MaxAllowedPacket int // Max packet size allowed
TLSConfig string // TLS configuration name
tls *tls.Config // TLS configuration
Timeout time.Duration // Dial timeout
ReadTimeout time.Duration // I/O read timeout
WriteTimeout time.Duration // I/O write timeout
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
AllowCleartextPasswords bool // Allows the cleartext client side plugin
AllowNativePasswords bool // Allows the native password authentication method
AllowOldPasswords bool // Allows the old insecure password method
ClientFoundRows bool // Return number of matching rows instead of rows changed
ColumnsWithAlias bool // Prepend table alias to column names
InterpolateParams bool // Interpolate placeholders into query string
MultiStatements bool // Allow multiple statements in one query
ParseTime bool // Parse time values to time.Time
Strict bool // Return warnings as errors
}
// FormatDSN formats the given Config into a DSN string which can be passed to
// the driver.
func (cfg *Config) FormatDSN() string {
var buf bytes.Buffer
// [username[:password]@]
if len(cfg.User) > 0 {
buf.WriteString(cfg.User)
if len(cfg.Passwd) > 0 {
buf.WriteByte(':')
buf.WriteString(cfg.Passwd)
}
buf.WriteByte('@')
}
// [protocol[(address)]]
if len(cfg.Net) > 0 {
buf.WriteString(cfg.Net)
if len(cfg.Addr) > 0 {
buf.WriteByte('(')
buf.WriteString(cfg.Addr)
buf.WriteByte(')')
}
}
// /dbname
buf.WriteByte('/')
buf.WriteString(cfg.DBName)
// [?param1=value1&...&paramN=valueN]
hasParam := false
if cfg.AllowAllFiles {
hasParam = true
buf.WriteString("?allowAllFiles=true")
}
if cfg.AllowCleartextPasswords {
if hasParam {
buf.WriteString("&allowCleartextPasswords=true")
} else {
hasParam = true
buf.WriteString("?allowCleartextPasswords=true")
}
}
if cfg.AllowNativePasswords {
if hasParam {
buf.WriteString("&allowNativePasswords=true")
} else {
hasParam = true
buf.WriteString("?allowNativePasswords=true")
}
}
if cfg.AllowOldPasswords {
if hasParam {
buf.WriteString("&allowOldPasswords=true")
} else {
hasParam = true
buf.WriteString("?allowOldPasswords=true")
}
}
if cfg.ClientFoundRows {
if hasParam {
buf.WriteString("&clientFoundRows=true")
} else {
hasParam = true
buf.WriteString("?clientFoundRows=true")
}
}
if col := cfg.Collation; col != defaultCollation && len(col) > 0 {
if hasParam {
buf.WriteString("&collation=")
} else {
hasParam = true
buf.WriteString("?collation=")
}
buf.WriteString(col)
}
if cfg.ColumnsWithAlias {
if hasParam {
buf.WriteString("&columnsWithAlias=true")
} else {
hasParam = true
buf.WriteString("?columnsWithAlias=true")
}
}
if cfg.InterpolateParams {
if hasParam {
buf.WriteString("&interpolateParams=true")
} else {
hasParam = true
buf.WriteString("?interpolateParams=true")
}
}
if cfg.Loc != time.UTC && cfg.Loc != nil {
if hasParam {
buf.WriteString("&loc=")
} else {
hasParam = true
buf.WriteString("?loc=")
}
buf.WriteString(url.QueryEscape(cfg.Loc.String()))
}
if cfg.MultiStatements {
if hasParam {
buf.WriteString("&multiStatements=true")
} else {
hasParam = true
buf.WriteString("?multiStatements=true")
}
}
if cfg.ParseTime {
if hasParam {
buf.WriteString("&parseTime=true")
} else {
hasParam = true
buf.WriteString("?parseTime=true")
}
}
if cfg.ReadTimeout > 0 {
if hasParam {
buf.WriteString("&readTimeout=")
} else {
hasParam = true
buf.WriteString("?readTimeout=")
}
buf.WriteString(cfg.ReadTimeout.String())
}
if cfg.Strict {
if hasParam {
buf.WriteString("&strict=true")
} else {
hasParam = true
buf.WriteString("?strict=true")
}
}
if cfg.Timeout > 0 {
if hasParam {
buf.WriteString("&timeout=")
} else {
hasParam = true
buf.WriteString("?timeout=")
}
buf.WriteString(cfg.Timeout.String())
}
if len(cfg.TLSConfig) > 0 {
if hasParam {
buf.WriteString("&tls=")
} else {
hasParam = true
buf.WriteString("?tls=")
}
buf.WriteString(url.QueryEscape(cfg.TLSConfig))
}
if cfg.WriteTimeout > 0 {
if hasParam {
buf.WriteString("&writeTimeout=")
} else {
hasParam = true
buf.WriteString("?writeTimeout=")
}
buf.WriteString(cfg.WriteTimeout.String())
}
if cfg.MaxAllowedPacket > 0 {
if hasParam {
buf.WriteString("&maxAllowedPacket=")
} else {
hasParam = true
buf.WriteString("?maxAllowedPacket=")
}
buf.WriteString(strconv.Itoa(cfg.MaxAllowedPacket))
}
// other params
if cfg.Params != nil {
for param, value := range cfg.Params {
if hasParam {
buf.WriteByte('&')
} else {
hasParam = true
buf.WriteByte('?')
}
buf.WriteString(param)
buf.WriteByte('=')
buf.WriteString(url.QueryEscape(value))
}
}
return buf.String()
}
// ParseDSN parses the DSN string to a Config
func ParseDSN(dsn string) (cfg *Config, err error) {
// New config with some default values
cfg = &Config{
Loc: time.UTC,
Collation: defaultCollation,
}
// [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] {
// Disable INFILE whitelist / enable all files
case "allowAllFiles":
var isBool bool
cfg.AllowAllFiles, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Use cleartext authentication mode (MySQL 5.5.10+)
case "allowCleartextPasswords":
var isBool bool
cfg.AllowCleartextPasswords, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Use native password authentication
case "allowNativePasswords":
var isBool bool
cfg.AllowNativePasswords, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Use old authentication mode (pre MySQL 4.1)
case "allowOldPasswords":
var isBool bool
cfg.AllowOldPasswords, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Switch "rowsAffected" mode
case "clientFoundRows":
var isBool bool
cfg.ClientFoundRows, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Collation
case "collation":
cfg.Collation = value
break
case "columnsWithAlias":
var isBool bool
cfg.ColumnsWithAlias, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Compression
case "compress":
return errors.New("compression not implemented yet")
// Enable client side placeholder substitution
case "interpolateParams":
var isBool bool
cfg.InterpolateParams, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Time Location
case "loc":
if value, err = url.QueryUnescape(value); err != nil {
return
}
cfg.Loc, err = time.LoadLocation(value)
if err != nil {
return
}
// multiple statements in one query
case "multiStatements":
var isBool bool
cfg.MultiStatements, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// time.Time parsing
case "parseTime":
var isBool bool
cfg.ParseTime, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// I/O read Timeout
case "readTimeout":
cfg.ReadTimeout, err = time.ParseDuration(value)
if err != nil {
return
}
// Strict mode
case "strict":
var isBool bool
cfg.Strict, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// 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.TLSConfig = "true"
cfg.tls = &tls.Config{}
} else {
cfg.TLSConfig = "false"
}
} else if vl := strings.ToLower(value); vl == "skip-verify" {
cfg.TLSConfig = vl
cfg.tls = &tls.Config{InsecureSkipVerify: true}
} else {
name, err := url.QueryUnescape(value)
if err != nil {
return fmt.Errorf("invalid value for TLS config name: %v", err)
}
if tlsConfig, ok := tlsConfigRegister[name]; ok {
if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
host, _, err := net.SplitHostPort(cfg.Addr)
if err == nil {
tlsConfig.ServerName = host
}
}
cfg.TLSConfig = name
cfg.tls = tlsConfig
} else {
return errors.New("invalid value / unknown config name: " + name)
}
}
// I/O write Timeout
case "writeTimeout":
cfg.WriteTimeout, err = time.ParseDuration(value)
if err != nil {
return
}
case "maxAllowedPacket":
cfg.MaxAllowedPacket, err = strconv.Atoi(value)
if err != nil {
return
}
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
}

View file

@ -13,20 +13,44 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"os"
) )
// Various errors the driver might return. Can change between driver versions.
var ( var (
errInvalidConn = errors.New("Invalid Connection") ErrInvalidConn = errors.New("invalid connection")
errMalformPkt = errors.New("Malformed Packet") ErrMalformPkt = errors.New("malformed packet")
errNoTLS = errors.New("TLS encryption requested but server does not support TLS") ErrNoTLS = errors.New("TLS 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") ErrCleartextPassword = errors.New("this user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN")
errOldProtocol = errors.New("MySQL-Server does not support required Protocol 41+") ErrNativePassword = errors.New("this user requires mysql native password authentication.")
errPktSync = errors.New("Commands out of sync. You can't run this command now") ErrOldPassword = errors.New("this user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
errPktSyncMul = errors.New("Commands out of sync. Did you run multiple statements at once?") ErrUnknownPlugin = errors.New("this authentication plugin is not supported")
errPktTooLarge = errors.New("Packet for query is too large. You can change this value on the server by adjusting the 'max_allowed_packet' variable.") ErrOldProtocol = errors.New("MySQL server does not support required protocol 41+")
ErrPktSync = errors.New("commands out of sync. You can't run this command now")
ErrPktSyncMul = errors.New("commands out of sync. Did you run multiple statements at once?")
ErrPktTooLarge = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server")
ErrBusyBuffer = errors.New("busy buffer")
) )
// error type which represents a single MySQL error 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 { type MySQLError struct {
Number uint16 Number uint16
Message string Message string
@ -36,8 +60,9 @@ func (me *MySQLError) Error() string {
return fmt.Sprintf("Error %d: %s", me.Number, me.Message) return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
} }
// error type which represents a group of one or more MySQL warnings // MySQLWarnings is an error type which represents a group of one or more MySQL
type MySQLWarnings []mysqlWarning // warnings
type MySQLWarnings []MySQLWarning
func (mws MySQLWarnings) Error() string { func (mws MySQLWarnings) Error() string {
var msg string var msg string
@ -45,20 +70,26 @@ func (mws MySQLWarnings) Error() string {
if i > 0 { if i > 0 {
msg += "\r\n" msg += "\r\n"
} }
msg += fmt.Sprintf("%s %s: %s", warning.Level, warning.Code, warning.Message) msg += fmt.Sprintf(
"%s %s: %s",
warning.Level,
warning.Code,
warning.Message,
)
} }
return msg return msg
} }
// error type which represents a single MySQL warning // MySQLWarning is an error type which represents a single MySQL warning.
type mysqlWarning struct { // Warnings are returned in groups only. See MySQLWarnings
type MySQLWarning struct {
Level string Level string
Code string Code string
Message string Message string
} }
func (mc *mysqlConn) getWarnings() (err error) { func (mc *mysqlConn) getWarnings() (err error) {
rows, err := mc.Query("SHOW WARNINGS", []driver.Value{}) rows, err := mc.Query("SHOW WARNINGS", nil)
if err != nil { if err != nil {
return return
} }
@ -66,27 +97,23 @@ func (mc *mysqlConn) getWarnings() (err error) {
var warnings = MySQLWarnings{} var warnings = MySQLWarnings{}
var values = make([]driver.Value, 3) var values = make([]driver.Value, 3)
var warning mysqlWarning
var raw []byte
var ok bool
for { for {
err = rows.Next(values) err = rows.Next(values)
switch err { switch err {
case nil: case nil:
warning = mysqlWarning{} warning := MySQLWarning{}
if raw, ok = values[0].([]byte); ok { if raw, ok := values[0].([]byte); ok {
warning.Level = string(raw) warning.Level = string(raw)
} else { } else {
warning.Level = fmt.Sprintf("%s", values[0]) warning.Level = fmt.Sprintf("%s", values[0])
} }
if raw, ok = values[1].([]byte); ok { if raw, ok := values[1].([]byte); ok {
warning.Code = string(raw) warning.Code = string(raw)
} else { } else {
warning.Code = fmt.Sprintf("%s", values[1]) warning.Code = fmt.Sprintf("%s", values[1])
} }
if raw, ok = values[2].([]byte); ok { if raw, ok := values[2].([]byte); ok {
warning.Message = string(raw) warning.Message = string(raw)
} else { } else {
warning.Message = fmt.Sprintf("%s", values[0]) warning.Message = fmt.Sprintf("%s", values[0])

View file

@ -9,23 +9,20 @@
package mysql package mysql
import ( import (
"database/sql/driver"
"fmt" "fmt"
"io" "io"
"os" "os"
"strings" "strings"
"sync"
) )
var ( var (
fileRegister map[string]bool fileRegister map[string]bool
readerRegister map[string]func() io.Reader fileRegisterLock sync.RWMutex
readerRegister map[string]func() io.Reader
readerRegisterLock sync.RWMutex
) )
func init() {
fileRegister = make(map[string]bool)
readerRegister = make(map[string]func() io.Reader)
}
// RegisterLocalFile adds the given file to the file whitelist, // RegisterLocalFile adds the given file to the file whitelist,
// so that it can be used by "LOAD DATA LOCAL INFILE <filepath>". // so that it can be used by "LOAD DATA LOCAL INFILE <filepath>".
// Alternatively you can allow the use of all local files with // Alternatively you can allow the use of all local files with
@ -38,12 +35,21 @@ func init() {
// ... // ...
// //
func RegisterLocalFile(filePath string) { func RegisterLocalFile(filePath string) {
fileRegisterLock.Lock()
// lazy map init
if fileRegister == nil {
fileRegister = make(map[string]bool)
}
fileRegister[strings.Trim(filePath, `"`)] = true fileRegister[strings.Trim(filePath, `"`)] = true
fileRegisterLock.Unlock()
} }
// DeregisterLocalFile removes the given filepath from the whitelist. // DeregisterLocalFile removes the given filepath from the whitelist.
func DeregisterLocalFile(filePath string) { func DeregisterLocalFile(filePath string) {
fileRegisterLock.Lock()
delete(fileRegister, strings.Trim(filePath, `"`)) delete(fileRegister, strings.Trim(filePath, `"`))
fileRegisterLock.Unlock()
} }
// RegisterReaderHandler registers a handler function which is used // RegisterReaderHandler registers a handler function which is used
@ -62,91 +68,115 @@ func DeregisterLocalFile(filePath string) {
// ... // ...
// //
func RegisterReaderHandler(name string, handler func() io.Reader) { func RegisterReaderHandler(name string, handler func() io.Reader) {
readerRegisterLock.Lock()
// lazy map init
if readerRegister == nil {
readerRegister = make(map[string]func() io.Reader)
}
readerRegister[name] = handler readerRegister[name] = handler
readerRegisterLock.Unlock()
} }
// DeregisterReaderHandler removes the ReaderHandler function with // DeregisterReaderHandler removes the ReaderHandler function with
// the given name from the registry. // the given name from the registry.
func DeregisterReaderHandler(name string) { func DeregisterReaderHandler(name string) {
readerRegisterLock.Lock()
delete(readerRegister, name) delete(readerRegister, name)
readerRegisterLock.Unlock()
}
func deferredClose(err *error, closer io.Closer) {
closeErr := closer.Close()
if *err == nil {
*err = closeErr
}
} }
func (mc *mysqlConn) handleInFileRequest(name string) (err error) { func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
var rdr io.Reader var rdr io.Reader
data := make([]byte, 4+mc.maxWriteSize) var data []byte
packetSize := 16 * 1024 // 16KB is small enough for disk readahead and large enough for TCP
if mc.maxWriteSize < packetSize {
packetSize = mc.maxWriteSize
}
if strings.HasPrefix(name, "Reader::") { // io.Reader if idx := strings.Index(name, "Reader::"); idx == 0 || (idx > 0 && name[idx-1] == '/') { // io.Reader
name = name[8:] // The server might return an an absolute path. See issue #355.
name = name[idx+8:]
readerRegisterLock.RLock()
handler, inMap := readerRegister[name] handler, inMap := readerRegister[name]
if handler != nil { readerRegisterLock.RUnlock()
if inMap {
rdr = handler() rdr = handler()
} if rdr != nil {
if rdr == nil { if cl, ok := rdr.(io.Closer); ok {
if !inMap { defer deferredClose(&err, cl)
err = fmt.Errorf("Reader '%s' is not registered", name) }
} else { } else {
err = fmt.Errorf("Reader '%s' is <nil>", name) err = fmt.Errorf("Reader '%s' is <nil>", name)
} }
} else {
err = fmt.Errorf("Reader '%s' is not registered", name)
} }
} else { // File } else { // File
name = strings.Trim(name, `"`) name = strings.Trim(name, `"`)
if mc.cfg.allowAllFiles || fileRegister[name] { fileRegisterLock.RLock()
rdr, err = os.Open(name) fr := fileRegister[name]
fileRegisterLock.RUnlock()
if mc.cfg.AllowAllFiles || fr {
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 < packetSize {
packetSize = fileSize
}
}
}
} else { } else {
err = fmt.Errorf("Local File '%s' is not registered. Use the DSN parameter 'allowAllFiles=true' to allow all files", name) err = fmt.Errorf("local file '%s' is not registered", name)
} }
} }
if rdc, ok := rdr.(io.ReadCloser); ok {
defer func() {
if err == nil {
err = rdc.Close()
} else {
rdc.Close()
}
}()
}
// send content packets // send content packets
var ioErr error
if err == nil { if err == nil {
data := make([]byte, 4+packetSize)
var n int var n int
for err == nil && ioErr == nil { for err == nil {
n, err = rdr.Read(data[4:]) n, err = rdr.Read(data[4:])
if n > 0 { if n > 0 {
data[0] = byte(n) if ioErr := mc.writePacket(data[:4+n]); ioErr != nil {
data[1] = byte(n >> 8) return ioErr
data[2] = byte(n >> 16) }
data[3] = mc.sequence
ioErr = mc.writePacket(data[:4+n])
} }
} }
if err == io.EOF { if err == io.EOF {
err = nil err = nil
} }
if ioErr != nil {
errLog.Print(ioErr.Error())
return driver.ErrBadConn
}
} }
// send empty packet (termination) // send empty packet (termination)
ioErr = mc.writePacket([]byte{ if data == nil {
0x00, data = make([]byte, 4)
0x00, }
0x00, if ioErr := mc.writePacket(data[:4]); ioErr != nil {
mc.sequence, return ioErr
})
if ioErr != nil {
errLog.Print(ioErr.Error())
return driver.ErrBadConn
} }
// read OK packet // read OK packet
if err == nil { if err == nil {
return mc.readResultOK() _, err = mc.readResultOK()
} else { return err
mc.readPacket()
} }
mc.readPacket()
return err return err
} }

File diff suppressed because it is too large Load diff

View file

@ -14,9 +14,11 @@ import (
) )
type mysqlField struct { type mysqlField struct {
fieldType byte tableName string
flags fieldFlag
name string name string
flags fieldFlag
fieldType byte
decimals byte
} }
type mysqlRows struct { type mysqlRows struct {
@ -32,10 +34,22 @@ type textRows struct {
mysqlRows mysqlRows
} }
type emptyRows struct{}
func (rows *mysqlRows) Columns() []string { func (rows *mysqlRows) Columns() []string {
columns := make([]string, len(rows.columns)) columns := make([]string, len(rows.columns))
for i := range columns { if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias {
columns[i] = rows.columns[i].name for i := range columns {
if tableName := rows.columns[i].tableName; len(tableName) > 0 {
columns[i] = tableName + "." + rows.columns[i].name
} else {
columns[i] = rows.columns[i].name
}
}
} else {
for i := range columns {
columns[i] = rows.columns[i].name
}
} }
return columns return columns
} }
@ -46,11 +60,17 @@ func (rows *mysqlRows) Close() error {
return nil return nil
} }
if mc.netConn == nil { if mc.netConn == nil {
return errInvalidConn return ErrInvalidConn
} }
// Remove unread packets from stream // Remove unread packets from stream
err := mc.readUntilEOF() err := mc.readUntilEOF()
if err == nil {
if err = mc.discardResults(); err != nil {
return err
}
}
rows.mc = nil rows.mc = nil
return err return err
} }
@ -58,14 +78,11 @@ func (rows *mysqlRows) Close() error {
func (rows *binaryRows) Next(dest []driver.Value) error { func (rows *binaryRows) Next(dest []driver.Value) error {
if mc := rows.mc; mc != nil { if mc := rows.mc; mc != nil {
if mc.netConn == nil { if mc.netConn == nil {
return errInvalidConn return ErrInvalidConn
} }
// Fetch next row from stream // Fetch next row from stream
if err := rows.readRow(dest); err != io.EOF { return rows.readRow(dest)
return err
}
rows.mc = nil
} }
return io.EOF return io.EOF
} }
@ -73,14 +90,23 @@ func (rows *binaryRows) Next(dest []driver.Value) error {
func (rows *textRows) Next(dest []driver.Value) error { func (rows *textRows) Next(dest []driver.Value) error {
if mc := rows.mc; mc != nil { if mc := rows.mc; mc != nil {
if mc.netConn == nil { if mc.netConn == nil {
return errInvalidConn return ErrInvalidConn
} }
// Fetch next row from stream // Fetch next row from stream
if err := rows.readRow(dest); err != io.EOF { return rows.readRow(dest)
return err
}
rows.mc = nil
} }
return io.EOF 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
}

View file

@ -10,6 +10,9 @@ package mysql
import ( import (
"database/sql/driver" "database/sql/driver"
"fmt"
"reflect"
"strconv"
) )
type mysqlStmt struct { type mysqlStmt struct {
@ -21,7 +24,10 @@ type mysqlStmt struct {
func (stmt *mysqlStmt) Close() error { func (stmt *mysqlStmt) Close() error {
if stmt.mc == nil || stmt.mc.netConn == nil { if stmt.mc == nil || stmt.mc.netConn == nil {
errLog.Print(errInvalidConn) // driver.Stmt.Close can be called more than once, thus this function
// has to be idempotent.
// See also Issue #450 and golang/go#16019.
//errLog.Print(ErrInvalidConn)
return driver.ErrBadConn return driver.ErrBadConn
} }
@ -34,9 +40,13 @@ func (stmt *mysqlStmt) NumInput() int {
return stmt.paramCount return stmt.paramCount
} }
func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
return converter{}
}
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) { func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
if stmt.mc.netConn == nil { if stmt.mc.netConn == nil {
errLog.Print(errInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
// Send command // Send command
@ -76,7 +86,7 @@ func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) { func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
if stmt.mc.netConn == nil { if stmt.mc.netConn == nil {
errLog.Print(errInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
// Send command // Send command
@ -94,9 +104,9 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
} }
rows := new(binaryRows) rows := new(binaryRows)
rows.mc = mc
if resLen > 0 { if resLen > 0 {
rows.mc = mc
// Columns // Columns
// If not cached, read them and cache them // If not cached, read them and cache them
if stmt.columns == nil { if stmt.columns == nil {
@ -110,3 +120,34 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
return rows, err return rows, err
} }
type converter struct{}
func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
if driver.IsValue(v) {
return v, nil
}
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Ptr:
// indirect pointers
if rv.IsNil() {
return nil, nil
}
return c.ConvertValue(rv.Elem().Interface())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return rv.Int(), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
return int64(rv.Uint()), nil
case reflect.Uint64:
u64 := rv.Uint()
if u64 >= 1<<63 {
return strconv.FormatUint(u64, 10), nil
}
return int64(u64), nil
case reflect.Float32, reflect.Float64:
return rv.Float(), nil
}
return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
}

View file

@ -14,7 +14,7 @@ type mysqlTx struct {
func (tx *mysqlTx) Commit() (err error) { func (tx *mysqlTx) Commit() (err error) {
if tx.mc == nil || tx.mc.netConn == nil { if tx.mc == nil || tx.mc.netConn == nil {
return errInvalidConn return ErrInvalidConn
} }
err = tx.mc.exec("COMMIT") err = tx.mc.exec("COMMIT")
tx.mc = nil tx.mc = nil
@ -23,7 +23,7 @@ func (tx *mysqlTx) Commit() (err error) {
func (tx *mysqlTx) Rollback() (err error) { func (tx *mysqlTx) Rollback() (err error) {
if tx.mc == nil || tx.mc.netConn == nil { if tx.mc == nil || tx.mc.netConn == nil {
return errInvalidConn return ErrInvalidConn
} }
err = tx.mc.exec("ROLLBACK") err = tx.mc.exec("ROLLBACK")
tx.mc = nil tx.mc = nil

View file

@ -13,29 +13,16 @@ import (
"crypto/tls" "crypto/tls"
"database/sql/driver" "database/sql/driver"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"io" "io"
"log"
"net/url"
"os"
"strings" "strings"
"time" "time"
) )
var ( var (
errLog *log.Logger // Error Logger
tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs 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)")
) )
func init() {
errLog = log.New(os.Stderr, "[MySQL] ", log.Ldate|log.Ltime|log.Lshortfile)
tlsConfigRegister = make(map[string]*tls.Config)
}
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open. // RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
// Use the key as a value in the DSN where tls=value. // Use the key as a value in the DSN where tls=value.
// //
@ -61,7 +48,11 @@ func init() {
// //
func RegisterTLSConfig(key string, config *tls.Config) error { func RegisterTLSConfig(key string, config *tls.Config) error {
if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" { if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" {
return fmt.Errorf("Key '%s' is reserved", key) return fmt.Errorf("key '%s' is reserved", key)
}
if tlsConfigRegister == nil {
tlsConfigRegister = make(map[string]*tls.Config)
} }
tlsConfigRegister[key] = config tlsConfigRegister[key] = config
@ -70,184 +61,9 @@ func RegisterTLSConfig(key string, config *tls.Config) error {
// DeregisterTLSConfig removes the tls.Config associated with key. // DeregisterTLSConfig removes the tls.Config associated with key.
func DeregisterTLSConfig(key string) { func DeregisterTLSConfig(key string) {
delete(tlsConfigRegister, key) if tlsConfigRegister != nil {
} delete(tlsConfigRegister, key)
// parseDSN parses the DSN string to a config
func parseDSN(dsn string) (cfg *config, err error) {
cfg = new(config)
// 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 '/')
for i := len(dsn) - 1; i >= 0; i-- {
if dsn[i] == '/' {
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 adress 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
}
} }
// Set default network if empty
if cfg.net == "" {
cfg.net = "tcp"
}
// Set default adress 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")
}
}
// Set default location if empty
if cfg.loc == nil {
cfg.loc = time.UTC
}
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] {
// 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)
}
// Switch "rowsAffected" mode
case "clientFoundRows":
var isBool bool
cfg.clientFoundRows, 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)
}
// 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 {
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. // Returns the bool value of the input.
@ -436,19 +252,15 @@ func (nt NullTime) Value() (driver.Value, error) {
} }
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) { func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
base := "0000-00-00 00:00:00.0000000"
switch len(str) { switch len(str) {
case 10: // YYYY-MM-DD case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
if str == "0000-00-00" { if str == base[:len(str)] {
return return
} }
t, err = time.Parse(timeFormat[:10], str) t, err = time.Parse(timeFormat[:len(str)], str)
case 19: // YYYY-MM-DD HH:MM:SS
if str == "0000-00-00 00:00:00" {
return
}
t, err = time.Parse(timeFormat, str)
default: default:
err = fmt.Errorf("Invalid Time-String: %s", str) err = fmt.Errorf("invalid time string: %s", str)
return return
} }
@ -497,58 +309,151 @@ func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Va
loc, loc,
), nil ), nil
} }
return nil, fmt.Errorf("Invalid DATETIME-packet length %d", num) return nil, fmt.Errorf("invalid DATETIME packet length %d", num)
} }
func formatBinaryDate(num uint64, data []byte) (driver.Value, error) { // zeroDateTime is used in formatBinaryDateTime to avoid an allocation
switch num { // if the DATE or DATETIME has the zero value.
case 0: // It must never be changed.
return []byte("0000-00-00"), nil // The current behavior depends on database/sql copying the result.
case 4: var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
return []byte(fmt.Sprintf(
"%04d-%02d-%02d",
binary.LittleEndian.Uint16(data[:2]),
data[2],
data[3],
)), nil
}
return nil, fmt.Errorf("Invalid DATE-packet length %d", num)
}
func formatBinaryDateTime(num uint64, data []byte) (driver.Value, error) { const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
switch num { const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
case 0:
return []byte("0000-00-00 00:00:00"), nil func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {
case 4: // length expects the deterministic length of the zero value,
return []byte(fmt.Sprintf( // negative time and 100+ hours are automatically added if needed
"%04d-%02d-%02d 00:00:00", if len(src) == 0 {
binary.LittleEndian.Uint16(data[:2]), if justTime {
data[2], return zeroDateTime[11 : 11+length], nil
data[3], }
)), nil return zeroDateTime[:length], nil
case 7: }
return []byte(fmt.Sprintf( var dst []byte // return value
"%04d-%02d-%02d %02d:%02d:%02d", var pt, p1, p2, p3 byte // current digit pair
binary.LittleEndian.Uint16(data[:2]), var zOffs byte // offset of value in zeroDateTime
data[2], if justTime {
data[3], switch length {
data[4], case
data[5], 8, // time (can be up to 10 when negative and 100+ hours)
data[6], 10, 11, 12, 13, 14, 15: // time with fractional seconds
)), nil default:
case 11: return nil, fmt.Errorf("illegal TIME length %d", length)
return []byte(fmt.Sprintf( }
"%04d-%02d-%02d %02d:%02d:%02d.%06d", switch len(src) {
binary.LittleEndian.Uint16(data[:2]), case 8, 12:
data[2], default:
data[3], return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
data[4], }
data[5], // +2 to enable negative time and 100+ hours
data[6], dst = make([]byte, 0, length+2)
binary.LittleEndian.Uint32(data[7:11]), if src[0] == 1 {
)), nil 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
} }
return nil, fmt.Errorf("Invalid DATETIME-packet length %d", num)
} }
/****************************************************************************** /******************************************************************************
@ -603,7 +508,7 @@ func stringToInt(b []byte) int {
// returns the string read as a bytes slice, wheter the value is NULL, // 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 number of bytes read and an error, in case the string is longer than
// the input slice // the input slice
func readLengthEnodedString(b []byte) ([]byte, bool, int, error) { func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
// Get length // Get length
num, isNull, n := readLengthEncodedInteger(b) num, isNull, n := readLengthEncodedInteger(b)
if num < 1 { if num < 1 {
@ -621,7 +526,7 @@ func readLengthEnodedString(b []byte) ([]byte, bool, int, error) {
// returns the number of bytes skipped and an error, in case the string is // returns the number of bytes skipped and an error, in case the string is
// longer than the input slice // longer than the input slice
func skipLengthEnodedString(b []byte) (int, error) { func skipLengthEncodedString(b []byte) (int, error) {
// Get length // Get length
num, _, n := readLengthEncodedInteger(b) num, _, n := readLengthEncodedInteger(b)
if num < 1 { if num < 1 {
@ -639,6 +544,10 @@ func skipLengthEnodedString(b []byte) (int, error) {
// returns the number read, whether the value is NULL and the number of bytes read // returns the number read, whether the value is NULL and the number of bytes read
func readLengthEncodedInteger(b []byte) (uint64, bool, int) { func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
// See issue #349
if len(b) == 0 {
return 0, true, 1
}
switch b[0] { switch b[0] {
// 251: NULL // 251: NULL
@ -657,7 +566,7 @@ func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
case 0xfe: case 0xfe:
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 | return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 |
uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 | uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 |
uint64(b[7])<<48 | uint64(b[8])<<54, uint64(b[7])<<48 | uint64(b[8])<<56,
false, 9 false, 9
} }
@ -677,5 +586,155 @@ func appendLengthEncodedInteger(b []byte, n uint64) []byte {
case n <= 0xffffff: case n <= 0xffffff:
return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16)) return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16))
} }
return b 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++
}
}
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++
}
}
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]
} }

11
vendor/vendor.json vendored
View file

@ -2,6 +2,10 @@
"comment": "", "comment": "",
"ignore": "test github.com/drone/mq/ github.com/tidwall/redlog/ google.golang.org/appengine/ github.com/syndtr/goleveldb/ github.com/drone/drone-ui/", "ignore": "test github.com/drone/mq/ github.com/tidwall/redlog/ google.golang.org/appengine/ github.com/syndtr/goleveldb/ github.com/drone/drone-ui/",
"package": [ "package": [
{
"path": "appengine/cloudsql",
"revision": ""
},
{ {
"checksumSHA1": "zTn0jzjOiJlScR1px66MvrgrlLs=", "checksumSHA1": "zTn0jzjOiJlScR1px66MvrgrlLs=",
"origin": "github.com/docker/docker/vendor/github.com/Microsoft/go-winio", "origin": "github.com/docker/docker/vendor/github.com/Microsoft/go-winio",
@ -346,9 +350,12 @@
"revisionTime": "2016-01-30T01:28:57+01:00" "revisionTime": "2016-01-30T01:28:57+01:00"
}, },
{ {
"checksumSHA1": "xmTgJXWHguGKHPuZE50FIbs88L0=",
"path": "github.com/go-sql-driver/mysql", "path": "github.com/go-sql-driver/mysql",
"revision": "9a7aa3606b82e2081a13a008ada88dfdb96c20fd", "revision": "a0583e0143b1624142adab07e0e97fe106d99561",
"revisionTime": "2013-11-02T07:16:43-07:00" "revisionTime": "2016-12-01T11:50:36Z",
"version": "v1.3",
"versionExact": "v1.3"
}, },
{ {
"path": "github.com/gogits/go-gogs-client", "path": "github.com/gogits/go-gogs-client",