Browse Source

基本框架构建未完成

tangs 6 years ago
parent
commit
252170a06f
42 changed files with 8022 additions and 0 deletions
  1. 2 0
      .gitignore
  2. 31 0
      src/ddpf/login/login.go
  3. 7 0
      src/ddpf/login/router.go
  4. 5 0
      src/ddpf/model/dbm/dbm.go
  5. 26 0
      src/ddpf/model/user/items.go
  6. 5 0
      src/ddpf/model/user/login.go
  7. 17 0
      src/ddpf/model/user/user_test.go
  8. 46 0
      src/ddpf/request/request.go
  9. 42 0
      src/ddpf/response/items.go
  10. 10 0
      src/ddpf/util/error.go
  11. 14 0
      src/ddpf/util/trans.go
  12. 1 0
      src/github.com/Tangmz/tangs
  13. 8 0
      src/github.com/go-sql-driver/mysql/.gitignore
  14. 12 0
      src/github.com/go-sql-driver/mysql/.travis.yml
  15. 52 0
      src/github.com/go-sql-driver/mysql/AUTHORS
  16. 103 0
      src/github.com/go-sql-driver/mysql/CHANGELOG.md
  17. 23 0
      src/github.com/go-sql-driver/mysql/CONTRIBUTING.md
  18. 21 0
      src/github.com/go-sql-driver/mysql/ISSUE_TEMPLATE.md
  19. 373 0
      src/github.com/go-sql-driver/mysql/LICENSE
  20. 9 0
      src/github.com/go-sql-driver/mysql/PULL_REQUEST_TEMPLATE.md
  21. 420 0
      src/github.com/go-sql-driver/mysql/README.md
  22. 19 0
      src/github.com/go-sql-driver/mysql/appengine.go
  23. 246 0
      src/github.com/go-sql-driver/mysql/benchmark_test.go
  24. 147 0
      src/github.com/go-sql-driver/mysql/buffer.go
  25. 250 0
      src/github.com/go-sql-driver/mysql/collations.go
  26. 372 0
      src/github.com/go-sql-driver/mysql/connection.go
  27. 163 0
      src/github.com/go-sql-driver/mysql/const.go
  28. 167 0
      src/github.com/go-sql-driver/mysql/driver.go
  29. 1857 0
      src/github.com/go-sql-driver/mysql/driver_test.go
  30. 513 0
      src/github.com/go-sql-driver/mysql/dsn.go
  31. 207 0
      src/github.com/go-sql-driver/mysql/dsn_test.go
  32. 131 0
      src/github.com/go-sql-driver/mysql/errors.go
  33. 42 0
      src/github.com/go-sql-driver/mysql/errors_test.go
  34. 181 0
      src/github.com/go-sql-driver/mysql/infile.go
  35. 1243 0
      src/github.com/go-sql-driver/mysql/packets.go
  36. 22 0
      src/github.com/go-sql-driver/mysql/result.go
  37. 112 0
      src/github.com/go-sql-driver/mysql/rows.go
  38. 150 0
      src/github.com/go-sql-driver/mysql/statement.go
  39. 31 0
      src/github.com/go-sql-driver/mysql/transaction.go
  40. 740 0
      src/github.com/go-sql-driver/mysql/utils.go
  41. 197 0
      src/github.com/go-sql-driver/mysql/utils_test.go
  42. 5 0
      src/main.go

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+.idea/*
+*.iml

+ 31 - 0
src/ddpf/login/login.go

@@ -0,0 +1,31 @@
+package login
+
+import (
+	"net/http"
+	"ddpf/response"
+	"github.com/Tangmz/tangs/log"
+	"ddpf/util"
+)
+
+func Login(w http.ResponseWriter, r *http.Request) {
+	var code int
+	var err error
+	var msg string
+	var data map[string]interface{}
+	defer func() {
+		response.WriteResponse(w, code, msg, err, data)
+	}()
+	username := r.PostFormValue("username")
+	password := r.PostFormValue("password")
+
+	if len(username) < 1 || len(password) < 1 {
+		err = util.Error("username/password contain a empty value")
+		log.Error("Login receive invalid username/password ->(%v)", err)
+		code = 1
+		msg = "参数错误"
+		return
+	}
+
+	// check user if exists
+
+}

+ 7 - 0
src/ddpf/login/router.go

@@ -0,0 +1,7 @@
+package login
+
+import "net/http"
+
+func Router() {
+	http.HandleFunc("login", Login)
+}

+ 5 - 0
src/ddpf/model/dbm/dbm.go

@@ -0,0 +1,5 @@
+package dbm
+
+func Open() {
+	
+}

+ 26 - 0
src/ddpf/model/user/items.go

@@ -0,0 +1,26 @@
+package user
+
+type adminlevel int
+
+const (
+	DDPF_SYSTEM_ADMIN adminlevel = 1 << iota		//1. 系统管理员
+	DDPF_SUPERVISOR_ADMIN							//2. 督导管理员
+	DDPF_SUPERVISE									//3. 督导
+	DDPF_TEACHING_ADMIN								//4. 教学评优管理员
+	DDPF_STUDENT_ADMIN								//5. 学生评优管理员
+	DDPF_Bachelor_WORKER							//6. 本科工作量审核员
+	DDPF_graduate_WORKER							//7. 研究生工作量审核员
+	DDPF_College_ADMIN								//8. 学院管理员
+	DDPF_TEACHER									//9. 教师
+)
+
+type User struct  {
+	Id string `json:"id"`		// 用户id
+	Name string `json:"name"`	// 用户名
+	Account string `json:"account"`	// 用户工号
+	Role int `json:"role"`	// 用户角色
+	Status string `json:"status"` // 用户状态
+	Time int64 `json:"time"`	// 创建时间
+	College string `json:"college"` // 所在学院
+	Attrs map[string]interface{} // 其他属性
+}

+ 5 - 0
src/ddpf/model/user/login.go

@@ -0,0 +1,5 @@
+package user
+
+func Login(username string, password string) {
+
+}

+ 17 - 0
src/ddpf/model/user/user_test.go

@@ -0,0 +1,17 @@
+package user
+
+import (
+	"testing"
+	"fmt"
+)
+
+func TestIota(t *testing.T) {
+	type tlevel int
+	const (
+		a tlevel = 1 << iota
+		b
+		c
+		d
+	)
+	fmt.Println(a, b, c, d)
+}

+ 46 - 0
src/ddpf/request/request.go

@@ -0,0 +1,46 @@
+package request
+
+import (
+	"net/http"
+	"strconv"
+)
+
+// RequestString get string value from url, if not, get from post form.
+func RequestString(r *http.Request, key string) string {
+	var val string
+	val = r.FormValue(key)
+	if len(val) > 0 {
+		return val
+	}
+
+	val = r.PostFormValue(key)
+	return val
+}
+
+// RequestFormInt get int value from url, if not, get from post form.
+func RequestFormInt(r *http.Request, key string) int {
+	var val int
+	val, _ = strconv.Atoi(r.FormValue(key))
+	return val
+}
+
+// RequestPostFromInt get int value from url, if not, get from post form.
+func RequestPostFromInt(r *http.Request, key string) int {
+	var val int
+	val, _ = strconv.Atoi(r.PostFormValue(key))
+	return val
+}
+
+// RequestFormFloat get float val from url, if not, get from post form.
+func RequestFormFloat(r *http.Request, key string) float64 {
+	var val float64
+	val, _ = strconv.ParseFloat(r.FormValue(key), 64)
+	return val
+}
+
+// RequestPostFormFloat get float val from url, if not, get from post form.
+func RequestPostFormFloat(r *http.Request, key string) float64 {
+	var val float64
+	val, _ = strconv.ParseFloat(r.PostFormValue(key), 64)
+	return val
+}

+ 42 - 0
src/ddpf/response/items.go

@@ -0,0 +1,42 @@
+package response
+
+import (
+	"net/http"
+	"ddpf/util"
+)
+
+type Response struct {
+	Code int `json:"code"`
+	Message string `json:"message,omitempty"`
+	Error string `json:"error,omitempty"`
+	Data map[string]interface{} `json:"data,omitempty"`
+}
+
+// WriteResponse write data to http.ResponseWriter
+func WriteResponse(w http.ResponseWriter, code int, msg string, err error, data map[string]interface{}) (int, error) {
+	var resp = Response{
+		Code:code,
+	}
+	if len(msg) > 0 {
+		resp.Message = msg
+	}
+	if data != nil {
+		resp.Data = data
+	}
+	if err != nil {
+		resp.Error = err.Error()
+	}
+
+	var result = util.S2Json(resp)
+	return w.Write([]byte(result))
+}
+
+// WriteResponse write return valut to http.ResponseWriter if some error happened
+func WriteResponseError(w http.ResponseWriter, code int, msg string, err error) (int, error) {
+	return WriteResponse(w, code, msg, err, nil)
+}
+
+// WriteResponseSuccess write return value to http.ResponseWriter if http request succeed
+func WriteResponseSuccess(w http.ResponseWriter, data map[string]interface{}) {
+	return WriteResponse(w, 0, "", nil, data)
+}

+ 10 - 0
src/ddpf/util/error.go

@@ -0,0 +1,10 @@
+package util
+
+import (
+	"errors"
+	"fmt"
+)
+
+func Error(format string, args... interface{}) error {
+	return errors.New(fmt.Sprintf(format, args...))
+}

+ 14 - 0
src/ddpf/util/trans.go

@@ -0,0 +1,14 @@
+package util
+
+import "encoding/json"
+
+// S2Json trans data to json, e.g struct, map and so on.
+func S2Json(data interface{}) string {
+	bys, _ := json.Marshal(data)
+	return string(bys)
+}
+
+// Json2S trans json to object
+func Json2S(src string, dest interface{}) error {
+	return json.Unmarshal([]byte(src), dest)
+}

+ 1 - 0
src/github.com/Tangmz/tangs

@@ -0,0 +1 @@
+Subproject commit 045349c71386dc886c6728f82f4ba5cbef4bde37

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

@@ -0,0 +1,8 @@
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+Icon?
+ehthumbs.db
+Thumbs.db

+ 12 - 0
src/github.com/go-sql-driver/mysql/.travis.yml

@@ -0,0 +1,12 @@
+sudo: false
+language: go
+go:
+  - 1.2
+  - 1.3
+  - 1.4
+  - 1.5
+  - 1.6
+  - tip
+
+before_script:
+  - mysql -e 'create database gotest;'

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

@@ -0,0 +1,52 @@
+# This is the official list of Go-MySQL-Driver authors for copyright purposes.
+
+# If you are submitting a patch, please add your name or the name of the
+# organization which holds the copyright to this list in alphabetical order.
+
+# Names should be added to this file as
+#	Name <email address>
+# The email address is not required for organizations.
+# Please keep the list sorted.
+
+
+# Individual Persons
+
+Aaron Hopkins <go-sql-driver at die.net>
+Arne Hormann <arnehormann at gmail.com>
+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>
+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>
+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>
+Kamil Dziedzic <kamil at klecza.pl>
+Kevin Malachowski <kevin at chowski.com>
+Leonardo YongUk Kim <dalinaum at gmail.com>
+Luca Looz <luca.looz92 at gmail.com>
+Lucas Liu <extrafliu at gmail.com>
+Luke Scott <luke at webconnex.com>
+Michael Woolnough <michael.woolnough at gmail.com>
+Nicola Peduzzi <thenikso at gmail.com>
+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>
+Xiaobing Jiang <s7v7nislands at gmail.com>
+Xiuming Chen <cc at cxm.cc>
+
+# Organizations
+
+Barracuda Networks, Inc.
+Google Inc.
+Stripe Inc.

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

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

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

@@ -0,0 +1,23 @@
+# Contributing Guidelines
+
+## Reporting Issues
+
+Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed).
+
+## Contributing Code
+
+By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file.
+Don't forget to add yourself to the AUTHORS file.
+
+### Code Review
+
+Everyone is invited to review and comment on pull requests.
+If it looks fine to you, comment with "LGTM" (Looks good to me).
+
+If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes.
+
+Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM".
+
+## Development Ideas
+
+If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/go-sql-driver/mysql/wiki/Development-Ideas) Wiki page.

+ 21 - 0
src/github.com/go-sql-driver/mysql/ISSUE_TEMPLATE.md

@@ -0,0 +1,21 @@
+### Issue description
+Tell us what should happen and what happens instead
+
+### Example code
+```go
+If possible, please enter some example code here to reproduce the issue.
+```
+
+### Error log
+```
+If you have an error log, please paste it here.
+```
+
+### Configuration
+*Driver version (or git SHA):*
+
+*Go version:* run `go version` in your console
+
+*Server version:* E.g. MySQL 5.6, MariaDB 10.0.20
+
+*Server OS:* E.g. Debian 8.1 (Jessie), Windows 10

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

@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in 
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  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/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.

+ 9 - 0
src/github.com/go-sql-driver/mysql/PULL_REQUEST_TEMPLATE.md

@@ -0,0 +1,9 @@
+### Description
+Please explain the changes you made here.
+
+### Checklist
+- [ ] Code compiles correctly
+- [ ] Created tests which fail without the change (if possible)
+- [ ] All tests passing
+- [ ] Extended the README / documentation, if necessary
+- [ ] Added myself / the copyright holder to the AUTHORS file

File diff suppressed because it is too large
+ 420 - 0
src/github.com/go-sql-driver/mysql/README.md


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

@@ -0,0 +1,19 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// +build appengine
+
+package mysql
+
+import (
+	"appengine/cloudsql"
+)
+
+func init() {
+	RegisterDial("cloudsql", cloudsql.Dial)
+}

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

@@ -0,0 +1,246 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+import (
+	"bytes"
+	"database/sql"
+	"database/sql/driver"
+	"math"
+	"strings"
+	"sync"
+	"sync/atomic"
+	"testing"
+	"time"
+)
+
+type TB testing.B
+
+func (tb *TB) check(err error) {
+	if err != nil {
+		tb.Fatal(err)
+	}
+}
+
+func (tb *TB) checkDB(db *sql.DB, err error) *sql.DB {
+	tb.check(err)
+	return db
+}
+
+func (tb *TB) checkRows(rows *sql.Rows, err error) *sql.Rows {
+	tb.check(err)
+	return rows
+}
+
+func (tb *TB) checkStmt(stmt *sql.Stmt, err error) *sql.Stmt {
+	tb.check(err)
+	return stmt
+}
+
+func initDB(b *testing.B, queries ...string) *sql.DB {
+	tb := (*TB)(b)
+	db := tb.checkDB(sql.Open("mysql", dsn))
+	for _, query := range queries {
+		if _, err := db.Exec(query); err != nil {
+			if w, ok := err.(MySQLWarnings); ok {
+				b.Logf("warning on %q: %v", query, w)
+			} else {
+				b.Fatalf("error on %q: %v", query, err)
+			}
+		}
+	}
+	return db
+}
+
+const concurrencyLevel = 10
+
+func BenchmarkQuery(b *testing.B) {
+	tb := (*TB)(b)
+	b.StopTimer()
+	b.ReportAllocs()
+	db := initDB(b,
+		"DROP TABLE IF EXISTS foo",
+		"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
+		`INSERT INTO foo VALUES (1, "one")`,
+		`INSERT INTO foo VALUES (2, "two")`,
+	)
+	db.SetMaxIdleConns(concurrencyLevel)
+	defer db.Close()
+
+	stmt := tb.checkStmt(db.Prepare("SELECT val FROM foo WHERE id=?"))
+	defer stmt.Close()
+
+	remain := int64(b.N)
+	var wg sync.WaitGroup
+	wg.Add(concurrencyLevel)
+	defer wg.Wait()
+	b.StartTimer()
+
+	for i := 0; i < concurrencyLevel; i++ {
+		go func() {
+			for {
+				if atomic.AddInt64(&remain, -1) < 0 {
+					wg.Done()
+					return
+				}
+
+				var got string
+				tb.check(stmt.QueryRow(1).Scan(&got))
+				if got != "one" {
+					b.Errorf("query = %q; want one", got)
+					wg.Done()
+					return
+				}
+			}
+		}()
+	}
+}
+
+func BenchmarkExec(b *testing.B) {
+	tb := (*TB)(b)
+	b.StopTimer()
+	b.ReportAllocs()
+	db := tb.checkDB(sql.Open("mysql", dsn))
+	db.SetMaxIdleConns(concurrencyLevel)
+	defer db.Close()
+
+	stmt := tb.checkStmt(db.Prepare("DO 1"))
+	defer stmt.Close()
+
+	remain := int64(b.N)
+	var wg sync.WaitGroup
+	wg.Add(concurrencyLevel)
+	defer wg.Wait()
+	b.StartTimer()
+
+	for i := 0; i < concurrencyLevel; i++ {
+		go func() {
+			for {
+				if atomic.AddInt64(&remain, -1) < 0 {
+					wg.Done()
+					return
+				}
+
+				if _, err := stmt.Exec(); err != nil {
+					b.Fatal(err.Error())
+				}
+			}
+		}()
+	}
+}
+
+// data, but no db writes
+var roundtripSample []byte
+
+func initRoundtripBenchmarks() ([]byte, int, int) {
+	if roundtripSample == nil {
+		roundtripSample = []byte(strings.Repeat("0123456789abcdef", 1024*1024))
+	}
+	return roundtripSample, 16, len(roundtripSample)
+}
+
+func BenchmarkRoundtripTxt(b *testing.B) {
+	b.StopTimer()
+	sample, min, max := initRoundtripBenchmarks()
+	sampleString := string(sample)
+	b.ReportAllocs()
+	tb := (*TB)(b)
+	db := tb.checkDB(sql.Open("mysql", dsn))
+	defer db.Close()
+	b.StartTimer()
+	var result string
+	for i := 0; i < b.N; i++ {
+		length := min + i
+		if length > max {
+			length = max
+		}
+		test := sampleString[0:length]
+		rows := tb.checkRows(db.Query(`SELECT "` + test + `"`))
+		if !rows.Next() {
+			rows.Close()
+			b.Fatalf("crashed")
+		}
+		err := rows.Scan(&result)
+		if err != nil {
+			rows.Close()
+			b.Fatalf("crashed")
+		}
+		if result != test {
+			rows.Close()
+			b.Errorf("mismatch")
+		}
+		rows.Close()
+	}
+}
+
+func BenchmarkRoundtripBin(b *testing.B) {
+	b.StopTimer()
+	sample, min, max := initRoundtripBenchmarks()
+	b.ReportAllocs()
+	tb := (*TB)(b)
+	db := tb.checkDB(sql.Open("mysql", dsn))
+	defer db.Close()
+	stmt := tb.checkStmt(db.Prepare("SELECT ?"))
+	defer stmt.Close()
+	b.StartTimer()
+	var result sql.RawBytes
+	for i := 0; i < b.N; i++ {
+		length := min + i
+		if length > max {
+			length = max
+		}
+		test := sample[0:length]
+		rows := tb.checkRows(stmt.Query(test))
+		if !rows.Next() {
+			rows.Close()
+			b.Fatalf("crashed")
+		}
+		err := rows.Scan(&result)
+		if err != nil {
+			rows.Close()
+			b.Fatalf("crashed")
+		}
+		if !bytes.Equal(result, test) {
+			rows.Close()
+			b.Errorf("mismatch")
+		}
+		rows.Close()
+	}
+}
+
+func BenchmarkInterpolation(b *testing.B) {
+	mc := &mysqlConn{
+		cfg: &Config{
+			InterpolateParams: true,
+			Loc:               time.UTC,
+		},
+		maxPacketAllowed: maxPacketSize,
+		maxWriteSize:     maxPacketSize - 1,
+		buf:              newBuffer(nil),
+	}
+
+	args := []driver.Value{
+		int64(42424242),
+		float64(math.Pi),
+		false,
+		time.Unix(1423411542, 807015000),
+		[]byte("bytes containing special chars ' \" \a \x00"),
+		"string containing special chars ' \" \a \x00",
+	}
+	q := "SELECT ?, ?, ?, ?, ?, ?"
+
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, err := mc.interpolateParams(q, args)
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+}

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

@@ -0,0 +1,147 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+import (
+	"io"
+	"net"
+	"time"
+)
+
+const defaultBufSize = 4096
+
+// A buffer which is used for both reading and writing.
+// This is possible since communication on each connection is synchronous.
+// In other words, we can't write and read simultaneously on the same connection.
+// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
+// Also highly optimized for this particular use case.
+type buffer struct {
+	buf     []byte
+	nc      net.Conn
+	idx     int
+	length  int
+	timeout time.Duration
+}
+
+func newBuffer(nc net.Conn) buffer {
+	var b [defaultBufSize]byte
+	return buffer{
+		buf: b[:],
+		nc:  nc,
+	}
+}
+
+// fill reads into the buffer until at least _need_ bytes are in it
+func (b *buffer) fill(need int) error {
+	n := b.length
+
+	// move existing data to the beginning
+	if n > 0 && b.idx > 0 {
+		copy(b.buf[0:n], b.buf[b.idx:])
+	}
+
+	// grow buffer if necessary
+	// TODO: let the buffer shrink again at some point
+	//       Maybe keep the org buf slice and swap back?
+	if need > len(b.buf) {
+		// Round up to the next multiple of the default size
+		newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
+		copy(newBuf, b.buf)
+		b.buf = newBuf
+	}
+
+	b.idx = 0
+
+	for {
+		if b.timeout > 0 {
+			if err := b.nc.SetReadDeadline(time.Now().Add(b.timeout)); err != nil {
+				return err
+			}
+		}
+
+		nn, err := b.nc.Read(b.buf[n:])
+		n += nn
+
+		switch err {
+		case nil:
+			if n < need {
+				continue
+			}
+			b.length = n
+			return nil
+
+		case io.EOF:
+			if n >= need {
+				b.length = n
+				return nil
+			}
+			return io.ErrUnexpectedEOF
+
+		default:
+			return err
+		}
+	}
+}
+
+// returns next N bytes from buffer.
+// The returned slice is only guaranteed to be valid until the next read
+func (b *buffer) readNext(need int) ([]byte, error) {
+	if b.length < need {
+		// refill
+		if err := b.fill(need); err != nil {
+			return nil, err
+		}
+	}
+
+	offset := b.idx
+	b.idx += need
+	b.length -= need
+	return b.buf[offset:b.idx], nil
+}
+
+// returns a buffer with the requested size.
+// If possible, a slice from the existing buffer is returned.
+// Otherwise a bigger buffer is made.
+// Only one buffer (total) can be used at a time.
+func (b *buffer) takeBuffer(length int) []byte {
+	if b.length > 0 {
+		return nil
+	}
+
+	// test (cheap) general case first
+	if length <= defaultBufSize || length <= cap(b.buf) {
+		return b.buf[:length]
+	}
+
+	if length < maxPacketSize {
+		b.buf = make([]byte, length)
+		return b.buf
+	}
+	return make([]byte, length)
+}
+
+// shortcut which can be used if the requested buffer is guaranteed to be
+// smaller than defaultBufSize
+// Only one buffer (total) can be used at a time.
+func (b *buffer) takeSmallBuffer(length int) []byte {
+	if b.length == 0 {
+		return b.buf[:length]
+	}
+	return nil
+}
+
+// takeCompleteBuffer returns the complete existing buffer.
+// This can be used if the necessary buffer size is unknown.
+// Only one buffer (total) can be used at a time.
+func (b *buffer) takeCompleteBuffer() []byte {
+	if b.length == 0 {
+		return b.buf
+	}
+	return nil
+}

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

@@ -0,0 +1,250 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+const defaultCollation = "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,
+}

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

@@ -0,0 +1,372 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+import (
+	"database/sql/driver"
+	"net"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type mysqlConn struct {
+	buf              buffer
+	netConn          net.Conn
+	affectedRows     uint64
+	insertId         uint64
+	cfg              *Config
+	maxPacketAllowed int
+	maxWriteSize     int
+	writeTimeout     time.Duration
+	flags            clientFlag
+	status           statusFlag
+	sequence         uint8
+	parseTime        bool
+	strict           bool
+}
+
+// Handles parameters set in DSN after the connection is established
+func (mc *mysqlConn) handleParams() (err error) {
+	for param, val := range mc.cfg.Params {
+		switch param {
+		// Charset
+		case "charset":
+			charsets := strings.Split(val, ",")
+			for i := range charsets {
+				// ignore errors here - a charset may not exist
+				err = mc.exec("SET NAMES " + charsets[i])
+				if err == nil {
+					break
+				}
+			}
+			if err != nil {
+				return
+			}
+
+		// System Vars
+		default:
+			err = mc.exec("SET " + param + "=" + val + "")
+			if err != nil {
+				return
+			}
+		}
+	}
+
+	return
+}
+
+func (mc *mysqlConn) Begin() (driver.Tx, error) {
+	if mc.netConn == nil {
+		errLog.Print(ErrInvalidConn)
+		return nil, driver.ErrBadConn
+	}
+	err := mc.exec("START TRANSACTION")
+	if err == nil {
+		return &mysqlTx{mc}, err
+	}
+
+	return nil, err
+}
+
+func (mc *mysqlConn) Close() (err error) {
+	// Makes Close idempotent
+	if mc.netConn != nil {
+		err = mc.writeCommandPacket(comQuit)
+	}
+
+	mc.cleanup()
+
+	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) {
+	if mc.netConn == nil {
+		errLog.Print(ErrInvalidConn)
+		return nil, driver.ErrBadConn
+	}
+	// Send command
+	err := mc.writeCommandPacketStr(comStmtPrepare, query)
+	if err != nil {
+		return nil, err
+	}
+
+	stmt := &mysqlStmt{
+		mc: mc,
+	}
+
+	// Read Result
+	columnCount, err := stmt.readPrepareResultPacket()
+	if err == nil {
+		if stmt.paramCount > 0 {
+			if err = mc.readUntilEOF(); err != nil {
+				return nil, err
+			}
+		}
+
+		if columnCount > 0 {
+			err = mc.readUntilEOF()
+		}
+	}
+
+	return stmt, err
+}
+
+func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) {
+	buf := mc.buf.takeCompleteBuffer()
+	if buf == nil {
+		// can not take the buffer. Something must be wrong with the connection
+		errLog.Print(ErrBusyBuffer)
+		return "", driver.ErrBadConn
+	}
+	buf = buf[:0]
+	argPos := 0
+
+	for i := 0; i < len(query); i++ {
+		q := strings.IndexByte(query[i:], '?')
+		if q == -1 {
+			buf = append(buf, query[i:]...)
+			break
+		}
+		buf = append(buf, query[i:i+q]...)
+		i += q
+
+		arg := args[argPos]
+		argPos++
+
+		if arg == nil {
+			buf = append(buf, "NULL"...)
+			continue
+		}
+
+		switch v := arg.(type) {
+		case int64:
+			buf = strconv.AppendInt(buf, v, 10)
+		case float64:
+			buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
+		case bool:
+			if v {
+				buf = append(buf, '1')
+			} else {
+				buf = append(buf, '0')
+			}
+		case time.Time:
+			if v.IsZero() {
+				buf = append(buf, "'0000-00-00'"...)
+			} else {
+				v := v.In(mc.cfg.Loc)
+				v = v.Add(time.Nanosecond * 500) // To round under microsecond
+				year := v.Year()
+				year100 := year / 100
+				year1 := year % 100
+				month := v.Month()
+				day := v.Day()
+				hour := v.Hour()
+				minute := v.Minute()
+				second := v.Second()
+				micro := v.Nanosecond() / 1000
+
+				buf = append(buf, []byte{
+					'\'',
+					digits10[year100], digits01[year100],
+					digits10[year1], digits01[year1],
+					'-',
+					digits10[month], digits01[month],
+					'-',
+					digits10[day], digits01[day],
+					' ',
+					digits10[hour], digits01[hour],
+					':',
+					digits10[minute], digits01[minute],
+					':',
+					digits10[second], digits01[second],
+				}...)
+
+				if micro != 0 {
+					micro10000 := micro / 10000
+					micro100 := micro / 100 % 100
+					micro1 := micro % 100
+					buf = append(buf, []byte{
+						'.',
+						digits10[micro10000], digits01[micro10000],
+						digits10[micro100], digits01[micro100],
+						digits10[micro1], digits01[micro1],
+					}...)
+				}
+				buf = append(buf, '\'')
+			}
+		case []byte:
+			if v == nil {
+				buf = append(buf, "NULL"...)
+			} else {
+				buf = append(buf, "_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.maxPacketAllowed {
+			return "", driver.ErrSkip
+		}
+	}
+	if argPos != len(args) {
+		return "", driver.ErrSkip
+	}
+	return string(buf), nil
+}
+
+func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
+	if mc.netConn == nil {
+		errLog.Print(ErrInvalidConn)
+		return nil, driver.ErrBadConn
+	}
+	if len(args) != 0 {
+		if !mc.cfg.InterpolateParams {
+			return nil, driver.ErrSkip
+		}
+		// try to interpolate the parameters to save extra roundtrips for preparing and closing a statement
+		prepared, err := mc.interpolateParams(query, args)
+		if err != nil {
+			return nil, err
+		}
+		query = prepared
+		args = nil
+	}
+	mc.affectedRows = 0
+	mc.insertId = 0
+
+	err := mc.exec(query)
+	if err == nil {
+		return &mysqlResult{
+			affectedRows: int64(mc.affectedRows),
+			insertId:     int64(mc.insertId),
+		}, err
+	}
+	return nil, err
+}
+
+// Internal function to execute commands
+func (mc *mysqlConn) exec(query string) error {
+	// Send command
+	err := mc.writeCommandPacketStr(comQuery, query)
+	if err != nil {
+		return err
+	}
+
+	// Read Result
+	resLen, err := mc.readResultSetHeaderPacket()
+	if err == nil && resLen > 0 {
+		if err = mc.readUntilEOF(); err != nil {
+			return err
+		}
+
+		err = mc.readUntilEOF()
+	}
+
+	return err
+}
+
+func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
+	if mc.netConn == nil {
+		errLog.Print(ErrInvalidConn)
+		return nil, driver.ErrBadConn
+	}
+	if len(args) != 0 {
+		if !mc.cfg.InterpolateParams {
+			return nil, driver.ErrSkip
+		}
+		// try client-side prepare to reduce roundtrip
+		prepared, err := mc.interpolateParams(query, args)
+		if err != nil {
+			return nil, err
+		}
+		query = prepared
+		args = nil
+	}
+	// Send command
+	err := mc.writeCommandPacketStr(comQuery, query)
+	if err == nil {
+		// Read Result
+		var resLen int
+		resLen, err = mc.readResultSetHeaderPacket()
+		if err == nil {
+			rows := new(textRows)
+			rows.mc = mc
+
+			if resLen == 0 {
+				// no columns, no more data
+				return emptyRows{}, nil
+			}
+			// Columns
+			rows.columns, err = mc.readColumns(resLen)
+			return rows, err
+		}
+	}
+	return nil, err
+}
+
+// Gets the value of the given MySQL System Variable
+// The returned byte slice is only valid until the next read
+func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
+	// Send command
+	if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil {
+		return nil, err
+	}
+
+	// Read Result
+	resLen, err := mc.readResultSetHeaderPacket()
+	if err == nil {
+		rows := new(textRows)
+		rows.mc = mc
+		rows.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
+
+		if resLen > 0 {
+			// Columns
+			if err := mc.readUntilEOF(); err != nil {
+				return nil, err
+			}
+		}
+
+		dest := make([]driver.Value, resLen)
+		if err = rows.readRow(dest); err == nil {
+			return dest[0].([]byte), mc.readUntilEOF()
+		}
+	}
+	return nil, err
+}

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

@@ -0,0 +1,163 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+const (
+	minProtocolVersion byte = 10
+	maxPacketSize           = 1<<24 - 1
+	timeFormat              = "2006-01-02 15:04:05.999999"
+)
+
+// MySQL constants documentation:
+// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
+
+const (
+	iOK          byte = 0x00
+	iLocalInFile byte = 0xfb
+	iEOF         byte = 0xfe
+	iERR         byte = 0xff
+)
+
+// https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
+type clientFlag uint32
+
+const (
+	clientLongPassword clientFlag = 1 << iota
+	clientFoundRows
+	clientLongFlag
+	clientConnectWithDB
+	clientNoSchema
+	clientCompress
+	clientODBC
+	clientLocalFiles
+	clientIgnoreSpace
+	clientProtocol41
+	clientInteractive
+	clientSSL
+	clientIgnoreSIGPIPE
+	clientTransactions
+	clientReserved
+	clientSecureConn
+	clientMultiStatements
+	clientMultiResults
+	clientPSMultiResults
+	clientPluginAuth
+	clientConnectAttrs
+	clientPluginAuthLenEncClientData
+	clientCanHandleExpiredPasswords
+	clientSessionTrack
+	clientDeprecateEOF
+)
+
+const (
+	comQuit byte = iota + 1
+	comInitDB
+	comQuery
+	comFieldList
+	comCreateDB
+	comDropDB
+	comRefresh
+	comShutdown
+	comStatistics
+	comProcessInfo
+	comConnect
+	comProcessKill
+	comDebug
+	comPing
+	comTime
+	comDelayedInsert
+	comChangeUser
+	comBinlogDump
+	comTableDump
+	comConnectOut
+	comRegisterSlave
+	comStmtPrepare
+	comStmtExecute
+	comStmtSendLongData
+	comStmtClose
+	comStmtReset
+	comSetOption
+	comStmtFetch
+)
+
+// https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType
+const (
+	fieldTypeDecimal byte = iota
+	fieldTypeTiny
+	fieldTypeShort
+	fieldTypeLong
+	fieldTypeFloat
+	fieldTypeDouble
+	fieldTypeNULL
+	fieldTypeTimestamp
+	fieldTypeLongLong
+	fieldTypeInt24
+	fieldTypeDate
+	fieldTypeTime
+	fieldTypeDateTime
+	fieldTypeYear
+	fieldTypeNewDate
+	fieldTypeVarChar
+	fieldTypeBit
+)
+const (
+	fieldTypeJSON byte = iota + 0xf5
+	fieldTypeNewDecimal
+	fieldTypeEnum
+	fieldTypeSet
+	fieldTypeTinyBLOB
+	fieldTypeMediumBLOB
+	fieldTypeLongBLOB
+	fieldTypeBLOB
+	fieldTypeVarString
+	fieldTypeString
+	fieldTypeGeometry
+)
+
+type fieldFlag uint16
+
+const (
+	flagNotNULL fieldFlag = 1 << iota
+	flagPriKey
+	flagUniqueKey
+	flagMultipleKey
+	flagBLOB
+	flagUnsigned
+	flagZeroFill
+	flagBinary
+	flagEnum
+	flagAutoIncrement
+	flagTimestamp
+	flagSet
+	flagUnknown1
+	flagUnknown2
+	flagUnknown3
+	flagUnknown4
+)
+
+// http://dev.mysql.com/doc/internals/en/status-flags.html
+type statusFlag uint16
+
+const (
+	statusInTrans statusFlag = 1 << iota
+	statusInAutocommit
+	statusReserved // Not in documentation
+	statusMoreResultsExists
+	statusNoGoodIndexUsed
+	statusNoIndexUsed
+	statusCursorExists
+	statusLastRowSent
+	statusDbDropped
+	statusNoBackslashEscapes
+	statusMetadataChanged
+	statusQueryWasSlow
+	statusPsOutParams
+	statusInTransReadonly
+	statusSessionStateChanged
+)

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

@@ -0,0 +1,167 @@
+// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Package mysql provides a MySQL driver for Go's database/sql package
+//
+// The driver should be used via the database/sql package:
+//
+//  import "database/sql"
+//  import _ "github.com/go-sql-driver/mysql"
+//
+//  db, err := sql.Open("mysql", "user:password@/dbname")
+//
+// See https://github.com/go-sql-driver/mysql#usage for details
+package mysql
+
+import (
+	"database/sql"
+	"database/sql/driver"
+	"net"
+)
+
+// MySQLDriver is exported to make the driver directly accessible.
+// In general the driver is used via the database/sql package.
+type MySQLDriver struct{}
+
+// DialFunc is a function which can be used to establish the network connection.
+// Custom dial functions must be registered with RegisterDial
+type DialFunc func(addr string) (net.Conn, error)
+
+var dials map[string]DialFunc
+
+// RegisterDial registers a custom dial function. It can then be used by the
+// network address mynet(addr), where mynet is the registered new network.
+// addr is passed as a parameter to the dial function.
+func RegisterDial(net string, dial DialFunc) {
+	if dials == nil {
+		dials = make(map[string]DialFunc)
+	}
+	dials[net] = dial
+}
+
+// Open new Connection.
+// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
+// the DSN string is formated
+func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
+	var err error
+
+	// New mysqlConn
+	mc := &mysqlConn{
+		maxPacketAllowed: maxPacketSize,
+		maxWriteSize:     maxPacketSize - 1,
+	}
+	mc.cfg, err = ParseDSN(dsn)
+	if err != nil {
+		return nil, err
+	}
+	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 {
+		return nil, err
+	}
+
+	// Enable TCP Keepalives on TCP connections
+	if tc, ok := mc.netConn.(*net.TCPConn); ok {
+		if err := tc.SetKeepAlive(true); err != nil {
+			// Don't send COM_QUIT before handshake.
+			mc.netConn.Close()
+			mc.netConn = nil
+			return nil, err
+		}
+	}
+
+	mc.buf = newBuffer(mc.netConn)
+
+	// Set I/O timeouts
+	mc.buf.timeout = mc.cfg.ReadTimeout
+	mc.writeTimeout = mc.cfg.WriteTimeout
+
+	// Reading Handshake Initialization Packet
+	cipher, err := mc.readInitPacket()
+	if err != nil {
+		mc.cleanup()
+		return nil, err
+	}
+
+	// Send Client Authentication Packet
+	if err = mc.writeAuthPacket(cipher); err != nil {
+		mc.cleanup()
+		return nil, err
+	}
+
+	// Handle response to auth packet, switch methods if possible
+	if err = handleAuthResult(mc, cipher); err != nil {
+		// Authentication failed and MySQL has already closed the connection
+		// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
+		// Do not send COM_QUIT, just cleanup and return the error.
+		mc.cleanup()
+		return nil, err
+	}
+
+	// Get max allowed packet size
+	maxap, err := mc.getSystemVar("max_allowed_packet")
+	if err != nil {
+		mc.Close()
+		return nil, err
+	}
+	mc.maxPacketAllowed = stringToInt(maxap) - 1
+	if mc.maxPacketAllowed < maxPacketSize {
+		mc.maxWriteSize = mc.maxPacketAllowed
+	}
+
+	// Handle DSN Params
+	err = mc.handleParams()
+	if err != nil {
+		mc.Close()
+		return nil, err
+	}
+
+	return mc, nil
+}
+
+func handleAuthResult(mc *mysqlConn, cipher []byte) error {
+	// Read Result Packet
+	err := mc.readResultOK()
+	if err == nil {
+		return nil // auth successful
+	}
+
+	if mc.cfg == nil {
+		return err // auth failed and retry not possible
+	}
+
+	// Retry auth if configured to do so.
+	if mc.cfg.AllowOldPasswords && err == ErrOldPassword {
+		// Retry with old authentication method. Note: there are edge cases
+		// where this should work but doesn't; this is currently "wontfix":
+		// https://github.com/go-sql-driver/mysql/issues/184
+		if err = mc.writeOldAuthPacket(cipher); err != nil {
+			return err
+		}
+		err = mc.readResultOK()
+	} else if mc.cfg.AllowCleartextPasswords && err == ErrCleartextPassword {
+		// Retry with clear text password for
+		// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
+		// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
+		if err = mc.writeClearAuthPacket(); err != nil {
+			return err
+		}
+		err = mc.readResultOK()
+	}
+	return err
+}
+
+func init() {
+	sql.Register("mysql", &MySQLDriver{})
+}

File diff suppressed because it is too large
+ 1857 - 0
src/github.com/go-sql-driver/mysql/driver_test.go


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

@@ -0,0 +1,513 @@
+// 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"
+	"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
+	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
+	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.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())
+	}
+
+	// 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 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
+			}
+
+		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
+}

File diff suppressed because it is too large
+ 207 - 0
src/github.com/go-sql-driver/mysql/dsn_test.go


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

@@ -0,0 +1,131 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+import (
+	"database/sql/driver"
+	"errors"
+	"fmt"
+	"io"
+	"log"
+	"os"
+)
+
+// Various errors the driver might return. Can change between driver versions.
+var (
+	ErrInvalidConn       = errors.New("invalid connection")
+	ErrMalformPkt        = errors.New("malformed packet")
+	ErrNoTLS             = errors.New("TLS requested but server does not support TLS")
+	ErrOldPassword       = errors.New("this user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
+	ErrCleartextPassword = errors.New("this user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN")
+	ErrUnknownPlugin     = errors.New("this authentication plugin is not supported")
+	ErrOldProtocol       = errors.New("MySQL server does not support required protocol 41+")
+	ErrPktSync           = errors.New("commands out of sync. You can't run this command now")
+	ErrPktSyncMul        = errors.New("commands out of sync. Did you run multiple statements at once?")
+	ErrPktTooLarge       = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server")
+	ErrBusyBuffer        = errors.New("busy buffer")
+)
+
+var errLog = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))
+
+// Logger is used to log critical error messages.
+type Logger interface {
+	Print(v ...interface{})
+}
+
+// SetLogger is used to set the logger for critical errors.
+// The initial logger is os.Stderr.
+func SetLogger(logger Logger) error {
+	if logger == nil {
+		return errors.New("logger is nil")
+	}
+	errLog = logger
+	return nil
+}
+
+// MySQLError is an error type which represents a single MySQL error
+type MySQLError struct {
+	Number  uint16
+	Message string
+}
+
+func (me *MySQLError) Error() string {
+	return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
+}
+
+// MySQLWarnings is an error type which represents a group of one or more MySQL
+// warnings
+type MySQLWarnings []MySQLWarning
+
+func (mws MySQLWarnings) Error() string {
+	var msg string
+	for i, warning := range mws {
+		if i > 0 {
+			msg += "\r\n"
+		}
+		msg += fmt.Sprintf(
+			"%s %s: %s",
+			warning.Level,
+			warning.Code,
+			warning.Message,
+		)
+	}
+	return msg
+}
+
+// MySQLWarning is an error type which represents a single MySQL warning.
+// Warnings are returned in groups only. See MySQLWarnings
+type MySQLWarning struct {
+	Level   string
+	Code    string
+	Message string
+}
+
+func (mc *mysqlConn) getWarnings() (err error) {
+	rows, err := mc.Query("SHOW WARNINGS", nil)
+	if err != nil {
+		return
+	}
+
+	var warnings = MySQLWarnings{}
+	var values = make([]driver.Value, 3)
+
+	for {
+		err = rows.Next(values)
+		switch err {
+		case nil:
+			warning := MySQLWarning{}
+
+			if raw, ok := values[0].([]byte); ok {
+				warning.Level = string(raw)
+			} else {
+				warning.Level = fmt.Sprintf("%s", values[0])
+			}
+			if raw, ok := values[1].([]byte); ok {
+				warning.Code = string(raw)
+			} else {
+				warning.Code = fmt.Sprintf("%s", values[1])
+			}
+			if raw, ok := values[2].([]byte); ok {
+				warning.Message = string(raw)
+			} else {
+				warning.Message = fmt.Sprintf("%s", values[0])
+			}
+
+			warnings = append(warnings, warning)
+
+		case io.EOF:
+			return warnings
+
+		default:
+			rows.Close()
+			return
+		}
+	}
+}

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

@@ -0,0 +1,42 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+import (
+	"bytes"
+	"log"
+	"testing"
+)
+
+func TestErrorsSetLogger(t *testing.T) {
+	previous := errLog
+	defer func() {
+		errLog = previous
+	}()
+
+	// set up logger
+	const expected = "prefix: test\n"
+	buffer := bytes.NewBuffer(make([]byte, 0, 64))
+	logger := log.New(buffer, "prefix: ", 0)
+
+	// print
+	SetLogger(logger)
+	errLog.Print("test")
+
+	// check result
+	if actual := buffer.String(); actual != expected {
+		t.Errorf("expected %q, got %q", expected, actual)
+	}
+}
+
+func TestErrorsStrictIgnoreNotes(t *testing.T) {
+	runTests(t, dsn+"&sql_notes=false", func(dbt *DBTest) {
+		dbt.mustExec("DROP TABLE IF EXISTS does_not_exist")
+	})
+}

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

@@ -0,0 +1,181 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"strings"
+	"sync"
+)
+
+var (
+	fileRegister       map[string]bool
+	fileRegisterLock   sync.RWMutex
+	readerRegister     map[string]func() io.Reader
+	readerRegisterLock sync.RWMutex
+)
+
+// RegisterLocalFile adds the given file to the file whitelist,
+// so that it can be used by "LOAD DATA LOCAL INFILE <filepath>".
+// Alternatively you can allow the use of all local files with
+// the DSN parameter 'allowAllFiles=true'
+//
+//  filePath := "/home/gopher/data.csv"
+//  mysql.RegisterLocalFile(filePath)
+//  err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo")
+//  if err != nil {
+//  ...
+//
+func RegisterLocalFile(filePath string) {
+	fileRegisterLock.Lock()
+	// lazy map init
+	if fileRegister == nil {
+		fileRegister = make(map[string]bool)
+	}
+
+	fileRegister[strings.Trim(filePath, `"`)] = true
+	fileRegisterLock.Unlock()
+}
+
+// DeregisterLocalFile removes the given filepath from the whitelist.
+func DeregisterLocalFile(filePath string) {
+	fileRegisterLock.Lock()
+	delete(fileRegister, strings.Trim(filePath, `"`))
+	fileRegisterLock.Unlock()
+}
+
+// RegisterReaderHandler registers a handler function which is used
+// to receive a io.Reader.
+// The Reader can be used by "LOAD DATA LOCAL INFILE Reader::<name>".
+// If the handler returns a io.ReadCloser Close() is called when the
+// request is finished.
+//
+//  mysql.RegisterReaderHandler("data", func() io.Reader {
+//  	var csvReader io.Reader // Some Reader that returns CSV data
+//  	... // Open Reader here
+//  	return csvReader
+//  })
+//  err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo")
+//  if err != nil {
+//  ...
+//
+func RegisterReaderHandler(name string, handler func() io.Reader) {
+	readerRegisterLock.Lock()
+	// lazy map init
+	if readerRegister == nil {
+		readerRegister = make(map[string]func() io.Reader)
+	}
+
+	readerRegister[name] = handler
+	readerRegisterLock.Unlock()
+}
+
+// DeregisterReaderHandler removes the ReaderHandler function with
+// the given name from the registry.
+func DeregisterReaderHandler(name string) {
+	readerRegisterLock.Lock()
+	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) {
+	var rdr io.Reader
+	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 idx := strings.Index(name, "Reader::"); idx == 0 || (idx > 0 && name[idx-1] == '/') { // io.Reader
+		// The server might return an an absolute path. See issue #355.
+		name = name[idx+8:]
+
+		readerRegisterLock.RLock()
+		handler, inMap := readerRegister[name]
+		readerRegisterLock.RUnlock()
+
+		if inMap {
+			rdr = handler()
+			if rdr != nil {
+				if cl, ok := rdr.(io.Closer); ok {
+					defer deferredClose(&err, cl)
+				}
+			} else {
+				err = fmt.Errorf("Reader '%s' is <nil>", name)
+			}
+		} else {
+			err = fmt.Errorf("Reader '%s' is not registered", name)
+		}
+	} else { // File
+		name = strings.Trim(name, `"`)
+		fileRegisterLock.RLock()
+		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 {
+			err = fmt.Errorf("local file '%s' is not registered", name)
+		}
+	}
+
+	// send content packets
+	if err == nil {
+		data := make([]byte, 4+packetSize)
+		var n int
+		for err == nil {
+			n, err = rdr.Read(data[4:])
+			if n > 0 {
+				if ioErr := mc.writePacket(data[:4+n]); ioErr != nil {
+					return ioErr
+				}
+			}
+		}
+		if err == io.EOF {
+			err = nil
+		}
+	}
+
+	// send empty packet (termination)
+	if data == nil {
+		data = make([]byte, 4)
+	}
+	if ioErr := mc.writePacket(data[:4]); ioErr != nil {
+		return ioErr
+	}
+
+	// read OK packet
+	if err == nil {
+		return mc.readResultOK()
+	}
+
+	mc.readPacket()
+	return err
+}

File diff suppressed because it is too large
+ 1243 - 0
src/github.com/go-sql-driver/mysql/packets.go


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

@@ -0,0 +1,22 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+type mysqlResult struct {
+	affectedRows int64
+	insertId     int64
+}
+
+func (res *mysqlResult) LastInsertId() (int64, error) {
+	return res.insertId, nil
+}
+
+func (res *mysqlResult) RowsAffected() (int64, error) {
+	return res.affectedRows, nil
+}

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

@@ -0,0 +1,112 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+import (
+	"database/sql/driver"
+	"io"
+)
+
+type mysqlField struct {
+	tableName string
+	name      string
+	flags     fieldFlag
+	fieldType byte
+	decimals  byte
+}
+
+type mysqlRows struct {
+	mc      *mysqlConn
+	columns []mysqlField
+}
+
+type binaryRows struct {
+	mysqlRows
+}
+
+type textRows struct {
+	mysqlRows
+}
+
+type emptyRows struct{}
+
+func (rows *mysqlRows) Columns() []string {
+	columns := make([]string, len(rows.columns))
+	if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias {
+		for i := range columns {
+			if tableName := rows.columns[i].tableName; len(tableName) > 0 {
+				columns[i] = tableName + "." + rows.columns[i].name
+			} else {
+				columns[i] = rows.columns[i].name
+			}
+		}
+	} else {
+		for i := range columns {
+			columns[i] = rows.columns[i].name
+		}
+	}
+	return columns
+}
+
+func (rows *mysqlRows) Close() error {
+	mc := rows.mc
+	if mc == nil {
+		return nil
+	}
+	if mc.netConn == nil {
+		return ErrInvalidConn
+	}
+
+	// Remove unread packets from stream
+	err := mc.readUntilEOF()
+	if err == nil {
+		if err = mc.discardResults(); err != nil {
+			return err
+		}
+	}
+
+	rows.mc = nil
+	return err
+}
+
+func (rows *binaryRows) Next(dest []driver.Value) error {
+	if mc := rows.mc; mc != nil {
+		if mc.netConn == nil {
+			return ErrInvalidConn
+		}
+
+		// Fetch next row from stream
+		return rows.readRow(dest)
+	}
+	return io.EOF
+}
+
+func (rows *textRows) Next(dest []driver.Value) error {
+	if mc := rows.mc; mc != nil {
+		if mc.netConn == nil {
+			return ErrInvalidConn
+		}
+
+		// Fetch next row from stream
+		return rows.readRow(dest)
+	}
+	return io.EOF
+}
+
+func (rows emptyRows) Columns() []string {
+	return nil
+}
+
+func (rows emptyRows) Close() error {
+	return nil
+}
+
+func (rows emptyRows) Next(dest []driver.Value) error {
+	return io.EOF
+}

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

@@ -0,0 +1,150 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+import (
+	"database/sql/driver"
+	"fmt"
+	"reflect"
+	"strconv"
+)
+
+type mysqlStmt struct {
+	mc         *mysqlConn
+	id         uint32
+	paramCount int
+	columns    []mysqlField // cached from the first query
+}
+
+func (stmt *mysqlStmt) Close() error {
+	if stmt.mc == nil || stmt.mc.netConn == nil {
+		errLog.Print(ErrInvalidConn)
+		return driver.ErrBadConn
+	}
+
+	err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id)
+	stmt.mc = nil
+	return err
+}
+
+func (stmt *mysqlStmt) NumInput() int {
+	return stmt.paramCount
+}
+
+func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
+	return converter{}
+}
+
+func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
+	if stmt.mc.netConn == nil {
+		errLog.Print(ErrInvalidConn)
+		return nil, driver.ErrBadConn
+	}
+	// Send command
+	err := stmt.writeExecutePacket(args)
+	if err != nil {
+		return nil, err
+	}
+
+	mc := stmt.mc
+
+	mc.affectedRows = 0
+	mc.insertId = 0
+
+	// Read Result
+	resLen, err := mc.readResultSetHeaderPacket()
+	if err == nil {
+		if resLen > 0 {
+			// Columns
+			err = mc.readUntilEOF()
+			if err != nil {
+				return nil, err
+			}
+
+			// Rows
+			err = mc.readUntilEOF()
+		}
+		if err == nil {
+			return &mysqlResult{
+				affectedRows: int64(mc.affectedRows),
+				insertId:     int64(mc.insertId),
+			}, nil
+		}
+	}
+
+	return nil, err
+}
+
+func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
+	if stmt.mc.netConn == nil {
+		errLog.Print(ErrInvalidConn)
+		return nil, driver.ErrBadConn
+	}
+	// Send command
+	err := stmt.writeExecutePacket(args)
+	if err != nil {
+		return nil, err
+	}
+
+	mc := stmt.mc
+
+	// Read Result
+	resLen, err := mc.readResultSetHeaderPacket()
+	if err != nil {
+		return nil, err
+	}
+
+	rows := new(binaryRows)
+
+	if resLen > 0 {
+		rows.mc = mc
+		// Columns
+		// If not cached, read them and cache them
+		if stmt.columns == nil {
+			rows.columns, err = mc.readColumns(resLen)
+			stmt.columns = rows.columns
+		} else {
+			rows.columns = stmt.columns
+			err = mc.readUntilEOF()
+		}
+	}
+
+	return rows, err
+}
+
+type converter struct{}
+
+func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
+	if driver.IsValue(v) {
+		return v, nil
+	}
+
+	rv := reflect.ValueOf(v)
+	switch rv.Kind() {
+	case reflect.Ptr:
+		// indirect pointers
+		if rv.IsNil() {
+			return nil, nil
+		}
+		return c.ConvertValue(rv.Elem().Interface())
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return rv.Int(), nil
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
+		return int64(rv.Uint()), nil
+	case reflect.Uint64:
+		u64 := rv.Uint()
+		if u64 >= 1<<63 {
+			return strconv.FormatUint(u64, 10), nil
+		}
+		return int64(u64), nil
+	case reflect.Float32, reflect.Float64:
+		return rv.Float(), nil
+	}
+	return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
+}

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

@@ -0,0 +1,31 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+type mysqlTx struct {
+	mc *mysqlConn
+}
+
+func (tx *mysqlTx) Commit() (err error) {
+	if tx.mc == nil || tx.mc.netConn == nil {
+		return ErrInvalidConn
+	}
+	err = tx.mc.exec("COMMIT")
+	tx.mc = nil
+	return
+}
+
+func (tx *mysqlTx) Rollback() (err error) {
+	if tx.mc == nil || tx.mc.netConn == nil {
+		return ErrInvalidConn
+	}
+	err = tx.mc.exec("ROLLBACK")
+	tx.mc = nil
+	return
+}

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

@@ -0,0 +1,740 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+import (
+	"crypto/sha1"
+	"crypto/tls"
+	"database/sql/driver"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"strings"
+	"time"
+)
+
+var (
+	tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
+)
+
+// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
+// Use the key as a value in the DSN where tls=value.
+//
+//  rootCertPool := x509.NewCertPool()
+//  pem, err := ioutil.ReadFile("/path/ca-cert.pem")
+//  if err != nil {
+//      log.Fatal(err)
+//  }
+//  if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
+//      log.Fatal("Failed to append PEM.")
+//  }
+//  clientCert := make([]tls.Certificate, 0, 1)
+//  certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem")
+//  if err != nil {
+//      log.Fatal(err)
+//  }
+//  clientCert = append(clientCert, certs)
+//  mysql.RegisterTLSConfig("custom", &tls.Config{
+//      RootCAs: rootCertPool,
+//      Certificates: clientCert,
+//  })
+//  db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
+//
+func RegisterTLSConfig(key string, config *tls.Config) error {
+	if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" {
+		return fmt.Errorf("key '%s' is reserved", key)
+	}
+
+	if tlsConfigRegister == nil {
+		tlsConfigRegister = make(map[string]*tls.Config)
+	}
+
+	tlsConfigRegister[key] = config
+	return nil
+}
+
+// DeregisterTLSConfig removes the tls.Config associated with key.
+func DeregisterTLSConfig(key string) {
+	if tlsConfigRegister != nil {
+		delete(tlsConfigRegister, key)
+	}
+}
+
+// Returns the bool value of the input.
+// The 2nd return value indicates if the input was a valid bool value
+func readBool(input string) (value bool, valid bool) {
+	switch input {
+	case "1", "true", "TRUE", "True":
+		return true, true
+	case "0", "false", "FALSE", "False":
+		return false, true
+	}
+
+	// Not a valid bool value
+	return
+}
+
+/******************************************************************************
+*                             Authentication                                  *
+******************************************************************************/
+
+// Encrypt password using 4.1+ method
+func scramblePassword(scramble, password []byte) []byte {
+	if len(password) == 0 {
+		return nil
+	}
+
+	// stage1Hash = SHA1(password)
+	crypt := sha1.New()
+	crypt.Write(password)
+	stage1 := crypt.Sum(nil)
+
+	// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
+	// inner Hash
+	crypt.Reset()
+	crypt.Write(stage1)
+	hash := crypt.Sum(nil)
+
+	// outer Hash
+	crypt.Reset()
+	crypt.Write(scramble)
+	crypt.Write(hash)
+	scramble = crypt.Sum(nil)
+
+	// token = scrambleHash XOR stage1Hash
+	for i := range scramble {
+		scramble[i] ^= stage1[i]
+	}
+	return scramble
+}
+
+// Encrypt password using pre 4.1 (old password) method
+// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
+type myRnd struct {
+	seed1, seed2 uint32
+}
+
+const myRndMaxVal = 0x3FFFFFFF
+
+// Pseudo random number generator
+func newMyRnd(seed1, seed2 uint32) *myRnd {
+	return &myRnd{
+		seed1: seed1 % myRndMaxVal,
+		seed2: seed2 % myRndMaxVal,
+	}
+}
+
+// Tested to be equivalent to MariaDB's floating point variant
+// http://play.golang.org/p/QHvhd4qved
+// http://play.golang.org/p/RG0q4ElWDx
+func (r *myRnd) NextByte() byte {
+	r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
+	r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
+
+	return byte(uint64(r.seed1) * 31 / myRndMaxVal)
+}
+
+// Generate binary hash from byte string using insecure pre 4.1 method
+func pwHash(password []byte) (result [2]uint32) {
+	var add uint32 = 7
+	var tmp uint32
+
+	result[0] = 1345345333
+	result[1] = 0x12345671
+
+	for _, c := range password {
+		// skip spaces and tabs in password
+		if c == ' ' || c == '\t' {
+			continue
+		}
+
+		tmp = uint32(c)
+		result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
+		result[1] += (result[1] << 8) ^ result[0]
+		add += tmp
+	}
+
+	// Remove sign bit (1<<31)-1)
+	result[0] &= 0x7FFFFFFF
+	result[1] &= 0x7FFFFFFF
+
+	return
+}
+
+// Encrypt password using insecure pre 4.1 method
+func scrambleOldPassword(scramble, password []byte) []byte {
+	if len(password) == 0 {
+		return nil
+	}
+
+	scramble = scramble[:8]
+
+	hashPw := pwHash(password)
+	hashSc := pwHash(scramble)
+
+	r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
+
+	var out [8]byte
+	for i := range out {
+		out[i] = r.NextByte() + 64
+	}
+
+	mask := r.NextByte()
+	for i := range out {
+		out[i] ^= mask
+	}
+
+	return out[:]
+}
+
+/******************************************************************************
+*                           Time related utils                                *
+******************************************************************************/
+
+// NullTime represents a time.Time that may be NULL.
+// NullTime implements the Scanner interface so
+// it can be used as a scan destination:
+//
+//  var nt NullTime
+//  err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
+//  ...
+//  if nt.Valid {
+//     // use nt.Time
+//  } else {
+//     // NULL value
+//  }
+//
+// This NullTime implementation is not driver-specific
+type NullTime struct {
+	Time  time.Time
+	Valid bool // Valid is true if Time is not NULL
+}
+
+// Scan implements the Scanner interface.
+// The value type must be time.Time or string / []byte (formatted time-string),
+// otherwise Scan fails.
+func (nt *NullTime) Scan(value interface{}) (err error) {
+	if value == nil {
+		nt.Time, nt.Valid = time.Time{}, false
+		return
+	}
+
+	switch v := value.(type) {
+	case time.Time:
+		nt.Time, nt.Valid = v, true
+		return
+	case []byte:
+		nt.Time, err = parseDateTime(string(v), time.UTC)
+		nt.Valid = (err == nil)
+		return
+	case string:
+		nt.Time, err = parseDateTime(v, time.UTC)
+		nt.Valid = (err == nil)
+		return
+	}
+
+	nt.Valid = false
+	return fmt.Errorf("Can't convert %T to time.Time", value)
+}
+
+// Value implements the driver Valuer interface.
+func (nt NullTime) Value() (driver.Value, error) {
+	if !nt.Valid {
+		return nil, nil
+	}
+	return nt.Time, nil
+}
+
+func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
+	base := "0000-00-00 00:00:00.0000000"
+	switch len(str) {
+	case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
+		if str == base[:len(str)] {
+			return
+		}
+		t, err = time.Parse(timeFormat[:len(str)], str)
+	default:
+		err = fmt.Errorf("invalid time string: %s", str)
+		return
+	}
+
+	// Adjust location
+	if err == nil && loc != time.UTC {
+		y, mo, d := t.Date()
+		h, mi, s := t.Clock()
+		t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil
+	}
+
+	return
+}
+
+func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) {
+	switch num {
+	case 0:
+		return time.Time{}, nil
+	case 4:
+		return time.Date(
+			int(binary.LittleEndian.Uint16(data[:2])), // year
+			time.Month(data[2]),                       // month
+			int(data[3]),                              // day
+			0, 0, 0, 0,
+			loc,
+		), nil
+	case 7:
+		return time.Date(
+			int(binary.LittleEndian.Uint16(data[:2])), // year
+			time.Month(data[2]),                       // month
+			int(data[3]),                              // day
+			int(data[4]),                              // hour
+			int(data[5]),                              // minutes
+			int(data[6]),                              // seconds
+			0,
+			loc,
+		), nil
+	case 11:
+		return time.Date(
+			int(binary.LittleEndian.Uint16(data[:2])), // year
+			time.Month(data[2]),                       // month
+			int(data[3]),                              // day
+			int(data[4]),                              // hour
+			int(data[5]),                              // minutes
+			int(data[6]),                              // seconds
+			int(binary.LittleEndian.Uint32(data[7:11]))*1000, // nanoseconds
+			loc,
+		), nil
+	}
+	return nil, fmt.Errorf("invalid DATETIME packet length %d", num)
+}
+
+// zeroDateTime is used in formatBinaryDateTime to avoid an allocation
+// if the DATE or DATETIME has the zero value.
+// It must never be changed.
+// The current behavior depends on database/sql copying the result.
+var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
+
+const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
+const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
+
+func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {
+	// length expects the deterministic length of the zero value,
+	// negative time and 100+ hours are automatically added if needed
+	if len(src) == 0 {
+		if justTime {
+			return zeroDateTime[11 : 11+length], nil
+		}
+		return zeroDateTime[:length], nil
+	}
+	var dst []byte          // return value
+	var pt, p1, p2, p3 byte // current digit pair
+	var zOffs byte          // offset of value in zeroDateTime
+	if justTime {
+		switch length {
+		case
+			8,                      // time (can be up to 10 when negative and 100+ hours)
+			10, 11, 12, 13, 14, 15: // time with fractional seconds
+		default:
+			return nil, fmt.Errorf("illegal TIME length %d", length)
+		}
+		switch len(src) {
+		case 8, 12:
+		default:
+			return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
+		}
+		// +2 to enable negative time and 100+ hours
+		dst = make([]byte, 0, length+2)
+		if src[0] == 1 {
+			dst = append(dst, '-')
+		}
+		if src[1] != 0 {
+			hour := uint16(src[1])*24 + uint16(src[5])
+			pt = byte(hour / 100)
+			p1 = byte(hour - 100*uint16(pt))
+			dst = append(dst, digits01[pt])
+		} else {
+			p1 = src[5]
+		}
+		zOffs = 11
+		src = src[6:]
+	} else {
+		switch length {
+		case 10, 19, 21, 22, 23, 24, 25, 26:
+		default:
+			t := "DATE"
+			if length > 10 {
+				t += "TIME"
+			}
+			return nil, fmt.Errorf("illegal %s length %d", t, length)
+		}
+		switch len(src) {
+		case 4, 7, 11:
+		default:
+			t := "DATE"
+			if length > 10 {
+				t += "TIME"
+			}
+			return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
+		}
+		dst = make([]byte, 0, length)
+		// start with the date
+		year := binary.LittleEndian.Uint16(src[:2])
+		pt = byte(year / 100)
+		p1 = byte(year - 100*uint16(pt))
+		p2, p3 = src[2], src[3]
+		dst = append(dst,
+			digits10[pt], digits01[pt],
+			digits10[p1], digits01[p1], '-',
+			digits10[p2], digits01[p2], '-',
+			digits10[p3], digits01[p3],
+		)
+		if length == 10 {
+			return dst, nil
+		}
+		if len(src) == 4 {
+			return append(dst, zeroDateTime[10:length]...), nil
+		}
+		dst = append(dst, ' ')
+		p1 = src[4] // hour
+		src = src[5:]
+	}
+	// p1 is 2-digit hour, src is after hour
+	p2, p3 = src[0], src[1]
+	dst = append(dst,
+		digits10[p1], digits01[p1], ':',
+		digits10[p2], digits01[p2], ':',
+		digits10[p3], digits01[p3],
+	)
+	if length <= byte(len(dst)) {
+		return dst, nil
+	}
+	src = src[2:]
+	if len(src) == 0 {
+		return append(dst, zeroDateTime[19:zOffs+length]...), nil
+	}
+	microsecs := binary.LittleEndian.Uint32(src[:4])
+	p1 = byte(microsecs / 10000)
+	microsecs -= 10000 * uint32(p1)
+	p2 = byte(microsecs / 100)
+	microsecs -= 100 * uint32(p2)
+	p3 = byte(microsecs)
+	switch decimals := zOffs + length - 20; decimals {
+	default:
+		return append(dst, '.',
+			digits10[p1], digits01[p1],
+			digits10[p2], digits01[p2],
+			digits10[p3], digits01[p3],
+		), nil
+	case 1:
+		return append(dst, '.',
+			digits10[p1],
+		), nil
+	case 2:
+		return append(dst, '.',
+			digits10[p1], digits01[p1],
+		), nil
+	case 3:
+		return append(dst, '.',
+			digits10[p1], digits01[p1],
+			digits10[p2],
+		), nil
+	case 4:
+		return append(dst, '.',
+			digits10[p1], digits01[p1],
+			digits10[p2], digits01[p2],
+		), nil
+	case 5:
+		return append(dst, '.',
+			digits10[p1], digits01[p1],
+			digits10[p2], digits01[p2],
+			digits10[p3],
+		), nil
+	}
+}
+
+/******************************************************************************
+*                       Convert from and to bytes                             *
+******************************************************************************/
+
+func uint64ToBytes(n uint64) []byte {
+	return []byte{
+		byte(n),
+		byte(n >> 8),
+		byte(n >> 16),
+		byte(n >> 24),
+		byte(n >> 32),
+		byte(n >> 40),
+		byte(n >> 48),
+		byte(n >> 56),
+	}
+}
+
+func uint64ToString(n uint64) []byte {
+	var a [20]byte
+	i := 20
+
+	// U+0030 = 0
+	// ...
+	// U+0039 = 9
+
+	var q uint64
+	for n >= 10 {
+		i--
+		q = n / 10
+		a[i] = uint8(n-q*10) + 0x30
+		n = q
+	}
+
+	i--
+	a[i] = uint8(n) + 0x30
+
+	return a[i:]
+}
+
+// treats string value as unsigned integer representation
+func stringToInt(b []byte) int {
+	val := 0
+	for i := range b {
+		val *= 10
+		val += int(b[i] - 0x30)
+	}
+	return val
+}
+
+// returns the string read as a bytes slice, wheter the value is NULL,
+// the number of bytes read and an error, in case the string is longer than
+// the input slice
+func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
+	// Get length
+	num, isNull, n := readLengthEncodedInteger(b)
+	if num < 1 {
+		return b[n:n], isNull, n, nil
+	}
+
+	n += int(num)
+
+	// Check data length
+	if len(b) >= n {
+		return b[n-int(num) : n], false, n, nil
+	}
+	return nil, false, n, io.EOF
+}
+
+// returns the number of bytes skipped and an error, in case the string is
+// longer than the input slice
+func skipLengthEncodedString(b []byte) (int, error) {
+	// Get length
+	num, _, n := readLengthEncodedInteger(b)
+	if num < 1 {
+		return n, nil
+	}
+
+	n += int(num)
+
+	// Check data length
+	if len(b) >= n {
+		return n, nil
+	}
+	return n, io.EOF
+}
+
+// returns the number read, whether the value is NULL and the number of bytes read
+func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
+	// See issue #349
+	if len(b) == 0 {
+		return 0, true, 1
+	}
+	switch b[0] {
+
+	// 251: NULL
+	case 0xfb:
+		return 0, true, 1
+
+	// 252: value of following 2
+	case 0xfc:
+		return uint64(b[1]) | uint64(b[2])<<8, false, 3
+
+	// 253: value of following 3
+	case 0xfd:
+		return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4
+
+	// 254: value of following 8
+	case 0xfe:
+		return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 |
+				uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 |
+				uint64(b[7])<<48 | uint64(b[8])<<56,
+			false, 9
+	}
+
+	// 0-250: value of first byte
+	return uint64(b[0]), false, 1
+}
+
+// encodes a uint64 value and appends it to the given bytes slice
+func appendLengthEncodedInteger(b []byte, n uint64) []byte {
+	switch {
+	case n <= 250:
+		return append(b, byte(n))
+
+	case n <= 0xffff:
+		return append(b, 0xfc, byte(n), byte(n>>8))
+
+	case n <= 0xffffff:
+		return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16))
+	}
+	return append(b, 0xfe, byte(n), byte(n>>8), byte(n>>16), byte(n>>24),
+		byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56))
+}
+
+// reserveBuffer checks cap(buf) and expand buffer to len(buf) + appendSize.
+// If cap(buf) is not enough, reallocate new buffer.
+func reserveBuffer(buf []byte, appendSize int) []byte {
+	newSize := len(buf) + appendSize
+	if cap(buf) < newSize {
+		// Grow buffer exponentially
+		newBuf := make([]byte, len(buf)*2+appendSize)
+		copy(newBuf, buf)
+		buf = newBuf
+	}
+	return buf[:newSize]
+}
+
+// escapeBytesBackslash escapes []byte with backslashes (\)
+// This escapes the contents of a string (provided as []byte) by adding backslashes before special
+// characters, and turning others into specific escape sequences, such as
+// turning newlines into \n and null bytes into \0.
+// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L823-L932
+func escapeBytesBackslash(buf, v []byte) []byte {
+	pos := len(buf)
+	buf = reserveBuffer(buf, len(v)*2)
+
+	for _, c := range v {
+		switch c {
+		case '\x00':
+			buf[pos] = '\\'
+			buf[pos+1] = '0'
+			pos += 2
+		case '\n':
+			buf[pos] = '\\'
+			buf[pos+1] = 'n'
+			pos += 2
+		case '\r':
+			buf[pos] = '\\'
+			buf[pos+1] = 'r'
+			pos += 2
+		case '\x1a':
+			buf[pos] = '\\'
+			buf[pos+1] = 'Z'
+			pos += 2
+		case '\'':
+			buf[pos] = '\\'
+			buf[pos+1] = '\''
+			pos += 2
+		case '"':
+			buf[pos] = '\\'
+			buf[pos+1] = '"'
+			pos += 2
+		case '\\':
+			buf[pos] = '\\'
+			buf[pos+1] = '\\'
+			pos += 2
+		default:
+			buf[pos] = c
+			pos++
+		}
+	}
+
+	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]
+}

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

@@ -0,0 +1,197 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+	"testing"
+	"time"
+)
+
+func TestScanNullTime(t *testing.T) {
+	var scanTests = []struct {
+		in    interface{}
+		error bool
+		valid bool
+		time  time.Time
+	}{
+		{tDate, false, true, tDate},
+		{sDate, false, true, tDate},
+		{[]byte(sDate), false, true, tDate},
+		{tDateTime, false, true, tDateTime},
+		{sDateTime, false, true, tDateTime},
+		{[]byte(sDateTime), false, true, tDateTime},
+		{tDate0, false, true, tDate0},
+		{sDate0, false, true, tDate0},
+		{[]byte(sDate0), false, true, tDate0},
+		{sDateTime0, false, true, tDate0},
+		{[]byte(sDateTime0), false, true, tDate0},
+		{"", true, false, tDate0},
+		{"1234", true, false, tDate0},
+		{0, true, false, tDate0},
+	}
+
+	var nt = NullTime{}
+	var err error
+
+	for _, tst := range scanTests {
+		err = nt.Scan(tst.in)
+		if (err != nil) != tst.error {
+			t.Errorf("%v: expected error status %t, got %t", tst.in, tst.error, (err != nil))
+		}
+		if nt.Valid != tst.valid {
+			t.Errorf("%v: expected valid status %t, got %t", tst.in, tst.valid, nt.Valid)
+		}
+		if nt.Time != tst.time {
+			t.Errorf("%v: expected time %v, got %v", tst.in, tst.time, nt.Time)
+		}
+	}
+}
+
+func TestLengthEncodedInteger(t *testing.T) {
+	var integerTests = []struct {
+		num     uint64
+		encoded []byte
+	}{
+		{0x0000000000000000, []byte{0x00}},
+		{0x0000000000000012, []byte{0x12}},
+		{0x00000000000000fa, []byte{0xfa}},
+		{0x0000000000000100, []byte{0xfc, 0x00, 0x01}},
+		{0x0000000000001234, []byte{0xfc, 0x34, 0x12}},
+		{0x000000000000ffff, []byte{0xfc, 0xff, 0xff}},
+		{0x0000000000010000, []byte{0xfd, 0x00, 0x00, 0x01}},
+		{0x0000000000123456, []byte{0xfd, 0x56, 0x34, 0x12}},
+		{0x0000000000ffffff, []byte{0xfd, 0xff, 0xff, 0xff}},
+		{0x0000000001000000, []byte{0xfe, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}},
+		{0x123456789abcdef0, []byte{0xfe, 0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12}},
+		{0xffffffffffffffff, []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
+	}
+
+	for _, tst := range integerTests {
+		num, isNull, numLen := readLengthEncodedInteger(tst.encoded)
+		if isNull {
+			t.Errorf("%x: expected %d, got NULL", tst.encoded, tst.num)
+		}
+		if num != tst.num {
+			t.Errorf("%x: expected %d, got %d", tst.encoded, tst.num, num)
+		}
+		if numLen != len(tst.encoded) {
+			t.Errorf("%x: expected size %d, got %d", tst.encoded, len(tst.encoded), numLen)
+		}
+		encoded := appendLengthEncodedInteger(nil, num)
+		if !bytes.Equal(encoded, tst.encoded) {
+			t.Errorf("%v: expected %x, got %x", num, tst.encoded, encoded)
+		}
+	}
+}
+
+func TestOldPass(t *testing.T) {
+	scramble := []byte{9, 8, 7, 6, 5, 4, 3, 2}
+	vectors := []struct {
+		pass string
+		out  string
+	}{
+		{" pass", "47575c5a435b4251"},
+		{"pass ", "47575c5a435b4251"},
+		{"123\t456", "575c47505b5b5559"},
+		{"C0mpl!ca ted#PASS123", "5d5d554849584a45"},
+	}
+	for _, tuple := range vectors {
+		ours := scrambleOldPassword(scramble, []byte(tuple.pass))
+		if tuple.out != fmt.Sprintf("%x", ours) {
+			t.Errorf("Failed old password %q", tuple.pass)
+		}
+	}
+}
+
+func TestFormatBinaryDateTime(t *testing.T) {
+	rawDate := [11]byte{}
+	binary.LittleEndian.PutUint16(rawDate[:2], 1978)   // years
+	rawDate[2] = 12                                    // months
+	rawDate[3] = 30                                    // days
+	rawDate[4] = 15                                    // hours
+	rawDate[5] = 46                                    // minutes
+	rawDate[6] = 23                                    // seconds
+	binary.LittleEndian.PutUint32(rawDate[7:], 987654) // microseconds
+	expect := func(expected string, inlen, outlen uint8) {
+		actual, _ := formatBinaryDateTime(rawDate[:inlen], outlen, false)
+		bytes, ok := actual.([]byte)
+		if !ok {
+			t.Errorf("formatBinaryDateTime must return []byte, was %T", actual)
+		}
+		if string(bytes) != expected {
+			t.Errorf(
+				"expected %q, got %q for length in %d, out %d",
+				bytes, actual, inlen, outlen,
+			)
+		}
+	}
+	expect("0000-00-00", 0, 10)
+	expect("0000-00-00 00:00:00", 0, 19)
+	expect("1978-12-30", 4, 10)
+	expect("1978-12-30 15:46:23", 7, 19)
+	expect("1978-12-30 15:46:23.987654", 11, 26)
+}
+
+func TestEscapeBackslash(t *testing.T) {
+	expect := func(expected, value string) {
+		actual := string(escapeBytesBackslash([]byte{}, []byte(value)))
+		if actual != expected {
+			t.Errorf(
+				"expected %s, got %s",
+				expected, actual,
+			)
+		}
+
+		actual = string(escapeStringBackslash([]byte{}, value))
+		if actual != expected {
+			t.Errorf(
+				"expected %s, got %s",
+				expected, actual,
+			)
+		}
+	}
+
+	expect("foo\\0bar", "foo\x00bar")
+	expect("foo\\nbar", "foo\nbar")
+	expect("foo\\rbar", "foo\rbar")
+	expect("foo\\Zbar", "foo\x1abar")
+	expect("foo\\\"bar", "foo\"bar")
+	expect("foo\\\\bar", "foo\\bar")
+	expect("foo\\'bar", "foo'bar")
+}
+
+func TestEscapeQuotes(t *testing.T) {
+	expect := func(expected, value string) {
+		actual := string(escapeBytesQuotes([]byte{}, []byte(value)))
+		if actual != expected {
+			t.Errorf(
+				"expected %s, got %s",
+				expected, actual,
+			)
+		}
+
+		actual = string(escapeStringQuotes([]byte{}, value))
+		if actual != expected {
+			t.Errorf(
+				"expected %s, got %s",
+				expected, actual,
+			)
+		}
+	}
+
+	expect("foo\x00bar", "foo\x00bar") // not affected
+	expect("foo\nbar", "foo\nbar")     // not affected
+	expect("foo\rbar", "foo\rbar")     // not affected
+	expect("foo\x1abar", "foo\x1abar") // not affected
+	expect("foo''bar", "foo'bar")      // affected
+	expect("foo\"bar", "foo\"bar")     // not affected
+}

+ 5 - 0
src/main.go

@@ -0,0 +1,5 @@
+package main
+
+func main() {
+
+}