Commit f85b64607b323564dc2793ed3114106cc5c0c4e1

Authored by 陆恒
1 parent 47fafa0f
Exists in master

提交mysql 库

Showing 31 changed files with 8084 additions and 0 deletions   Show diff stats
src/github.com/go-sql-driver/mysql/.gitignore 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +.DS_Store
  2 +.DS_Store?
  3 +._*
  4 +.Spotlight-V100
  5 +.Trashes
  6 +Icon?
  7 +ehthumbs.db
  8 +Thumbs.db
... ...
src/github.com/go-sql-driver/mysql/.travis.yml 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +sudo: false
  2 +language: go
  3 +go:
  4 + - 1.2
  5 + - 1.3
  6 + - 1.4
  7 + - 1.5
  8 + - 1.6
  9 + - 1.7
  10 + - tip
  11 +
  12 +before_script:
  13 + - mysql -e 'create database gotest;'
... ...
src/github.com/go-sql-driver/mysql/AUTHORS 0 → 100644
... ... @@ -0,0 +1,56 @@
  1 +# This is the official list of Go-MySQL-Driver authors for copyright purposes.
  2 +
  3 +# If you are submitting a patch, please add your name or the name of the
  4 +# organization which holds the copyright to this list in alphabetical order.
  5 +
  6 +# Names should be added to this file as
  7 +# Name <email address>
  8 +# The email address is not required for organizations.
  9 +# Please keep the list sorted.
  10 +
  11 +
  12 +# Individual Persons
  13 +
  14 +Aaron Hopkins <go-sql-driver at die.net>
  15 +Arne Hormann <arnehormann at gmail.com>
  16 +Carlos Nieto <jose.carlos at menteslibres.net>
  17 +Chris Moos <chris at tech9computers.com>
  18 +Daniel Nichter <nil at codenode.com>
  19 +Daniël van Eeden <git at myname.nl>
  20 +DisposaBoy <disposaboy at dby.me>
  21 +Frederick Mayle <frederickmayle at gmail.com>
  22 +Gustavo Kristic <gkristic at gmail.com>
  23 +Hanno Braun <mail at hannobraun.com>
  24 +Henri Yandell <flamefew at gmail.com>
  25 +Hirotaka Yamamoto <ymmt2005 at gmail.com>
  26 +INADA Naoki <songofacandy at gmail.com>
  27 +James Harr <james.harr at gmail.com>
  28 +Jian Zhen <zhenjl at gmail.com>
  29 +Joshua Prunier <joshua.prunier at gmail.com>
  30 +Julien Lefevre <julien.lefevr at gmail.com>
  31 +Julien Schmidt <go-sql-driver at julienschmidt.com>
  32 +Kamil Dziedzic <kamil at klecza.pl>
  33 +Kevin Malachowski <kevin at chowski.com>
  34 +Lennart Rudolph <lrudolph at hmc.edu>
  35 +Leonardo YongUk Kim <dalinaum at gmail.com>
  36 +Luca Looz <luca.looz92 at gmail.com>
  37 +Lucas Liu <extrafliu at gmail.com>
  38 +Luke Scott <luke at webconnex.com>
  39 +Michael Woolnough <michael.woolnough at gmail.com>
  40 +Nicola Peduzzi <thenikso at gmail.com>
  41 +Olivier Mengué <dolmen at cpan.org>
  42 +Paul Bonser <misterpib at gmail.com>
  43 +Runrioter Wung <runrioter at gmail.com>
  44 +Soroush Pour <me at soroushjp.com>
  45 +Stan Putrya <root.vagner at gmail.com>
  46 +Stanley Gunawan <gunawan.stanley at gmail.com>
  47 +Xiangyu Hu <xiangyu.hu at outlook.com>
  48 +Xiaobing Jiang <s7v7nislands at gmail.com>
  49 +Xiuming Chen <cc at cxm.cc>
  50 +Zhenye Xie <xiezhenye at gmail.com>
  51 +
  52 +# Organizations
  53 +
  54 +Barracuda Networks, Inc.
  55 +Google Inc.
  56 +Stripe Inc.
... ...
src/github.com/go-sql-driver/mysql/CHANGELOG.md 0 → 100644
... ... @@ -0,0 +1,114 @@
  1 +## HEAD
  2 +
  3 +Changes:
  4 +
  5 + - Go 1.1 is no longer supported
  6 + - Use decimals fields in MySQL to format time types (#249)
  7 + - Buffer optimizations (#269)
  8 + - TLS ServerName defaults to the host (#283)
  9 + - Refactoring (#400, #410, #437)
  10 + - Adjusted documentation for second generation CloudSQL (#485)
  11 +
  12 +New Features:
  13 +
  14 + - Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249)
  15 + - Support for returning table alias on Columns() (#289, #359, #382)
  16 + - Placeholder interpolation, can be actived with the DSN parameter `interpolateParams=true` (#309, #318, #490)
  17 + - Support for uint64 parameters with high bit set (#332, #345)
  18 + - Cleartext authentication plugin support (#327)
  19 + - Exported ParseDSN function and the Config struct (#403, #419, #429)
  20 + - Read / Write timeouts (#401)
  21 + - Support for JSON field type (#414)
  22 + - Support for multi-statements and multi-results (#411, #431)
  23 + - DSN parameter to set the driver-side max_allowed_packet value manually (#489)
  24 +
  25 +Bugfixes:
  26 +
  27 + - Fixed handling of queries without columns and rows (#255)
  28 + - Fixed a panic when SetKeepAlive() failed (#298)
  29 + - Handle ERR packets while reading rows (#321)
  30 + - Fixed reading NULL length-encoded integers in MySQL 5.6+ (#349)
  31 + - Fixed absolute paths support in LOAD LOCAL DATA INFILE (#356)
  32 + - Actually zero out bytes in handshake response (#378)
  33 + - Fixed race condition in registering LOAD DATA INFILE handler (#383)
  34 + - Fixed tests with MySQL 5.7.9+ (#380)
  35 + - QueryUnescape TLS config names (#397)
  36 + - Fixed "broken pipe" error by writing to closed socket (#390)
  37 + - Fixed LOAD LOCAL DATA INFILE buffering (#424)
  38 + - Fixed parsing of floats into float64 when placeholders are used (#434)
  39 + - Fixed DSN tests with Go 1.7+ (#459)
  40 + - Handle ERR packets while waiting for EOF (#473)
  41 +
  42 +
  43 +## Version 1.2 (2014-06-03)
  44 +
  45 +Changes:
  46 +
  47 + - We switched back to a "rolling release". `go get` installs the current master branch again
  48 + - Version v1 of the driver will not be maintained anymore. Go 1.0 is no longer supported by this driver
  49 + - Exported errors to allow easy checking from application code
  50 + - Enabled TCP Keepalives on TCP connections
  51 + - Optimized INFILE handling (better buffer size calculation, lazy init, ...)
  52 + - The DSN parser also checks for a missing separating slash
  53 + - Faster binary date / datetime to string formatting
  54 + - Also exported the MySQLWarning type
  55 + - mysqlConn.Close returns the first error encountered instead of ignoring all errors
  56 + - writePacket() automatically writes the packet size to the header
  57 + - readPacket() uses an iterative approach instead of the recursive approach to merge splitted packets
  58 +
  59 +New Features:
  60 +
  61 + - `RegisterDial` allows the usage of a custom dial function to establish the network connection
  62 + - Setting the connection collation is possible with the `collation` DSN parameter. This parameter should be preferred over the `charset` parameter
  63 + - Logging of critical errors is configurable with `SetLogger`
  64 + - Google CloudSQL support
  65 +
  66 +Bugfixes:
  67 +
  68 + - Allow more than 32 parameters in prepared statements
  69 + - Various old_password fixes
  70 + - Fixed TestConcurrent test to pass Go's race detection
  71 + - Fixed appendLengthEncodedInteger for large numbers
  72 + - Renamed readLengthEnodedString to readLengthEncodedString and skipLengthEnodedString to skipLengthEncodedString (fixed typo)
  73 +
  74 +
  75 +## Version 1.1 (2013-11-02)
  76 +
  77 +Changes:
  78 +
  79 + - Go-MySQL-Driver now requires Go 1.1
  80 + - Connections now use the collation `utf8_general_ci` by default. Adding `&charset=UTF8` to the DSN should not be necessary anymore
  81 + - Made closing rows and connections error tolerant. This allows for example deferring rows.Close() without checking for errors
  82 + - `[]byte(nil)` is now treated as a NULL value. Before, it was treated like an empty string / `[]byte("")`
  83 + - DSN parameter values must now be url.QueryEscape'ed. This allows text values to contain special characters, such as '&'.
  84 + - Use the IO buffer also for writing. This results in zero allocations (by the driver) for most queries
  85 + - Optimized the buffer for reading
  86 + - stmt.Query now caches column metadata
  87 + - New Logo
  88 + - Changed the copyright header to include all contributors
  89 + - Improved the LOAD INFILE documentation
  90 + - The driver struct is now exported to make the driver directly accessible
  91 + - Refactored the driver tests
  92 + - Added more benchmarks and moved all to a separate file
  93 + - Other small refactoring
  94 +
  95 +New Features:
  96 +
  97 + - Added *old_passwords* support: Required in some cases, but must be enabled by adding `allowOldPasswords=true` to the DSN since it is insecure
  98 + - Added a `clientFoundRows` parameter: Return the number of matching rows instead of the number of rows changed on UPDATEs
  99 + - Added TLS/SSL support: Use a TLS/SSL encrypted connection to the server. Custom TLS configs can be registered and used
  100 +
  101 +Bugfixes:
  102 +
  103 + - Fixed MySQL 4.1 support: MySQL 4.1 sends packets with lengths which differ from the specification
  104 + - Convert to DB timezone when inserting `time.Time`
  105 + - Splitted packets (more than 16MB) are now merged correctly
  106 + - Fixed false positive `io.EOF` errors when the data was fully read
  107 + - Avoid panics on reuse of closed connections
  108 + - Fixed empty string producing false nil values
  109 + - Fixed sign byte for positive TIME fields
  110 +
  111 +
  112 +## Version 1.0 (2013-05-14)
  113 +
  114 +Initial Release
... ...
src/github.com/go-sql-driver/mysql/CONTRIBUTING.md 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +# Contributing Guidelines
  2 +
  3 +## Reporting Issues
  4 +
  5 +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).
  6 +
  7 +## Contributing Code
  8 +
  9 +By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file.
  10 +Don't forget to add yourself to the AUTHORS file.
  11 +
  12 +### Code Review
  13 +
  14 +Everyone is invited to review and comment on pull requests.
  15 +If it looks fine to you, comment with "LGTM" (Looks good to me).
  16 +
  17 +If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes.
  18 +
  19 +Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM".
  20 +
  21 +## Development Ideas
  22 +
  23 +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.
... ...
src/github.com/go-sql-driver/mysql/ISSUE_TEMPLATE.md 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +### Issue description
  2 +Tell us what should happen and what happens instead
  3 +
  4 +### Example code
  5 +```go
  6 +If possible, please enter some example code here to reproduce the issue.
  7 +```
  8 +
  9 +### Error log
  10 +```
  11 +If you have an error log, please paste it here.
  12 +```
  13 +
  14 +### Configuration
  15 +*Driver version (or git SHA):*
  16 +
  17 +*Go version:* run `go version` in your console
  18 +
  19 +*Server version:* E.g. MySQL 5.6, MariaDB 10.0.20
  20 +
  21 +*Server OS:* E.g. Debian 8.1 (Jessie), Windows 10
... ...
src/github.com/go-sql-driver/mysql/LICENSE 0 → 100644
... ... @@ -0,0 +1,373 @@
  1 +Mozilla Public License Version 2.0
  2 +==================================
  3 +
  4 +1. Definitions
  5 +--------------
  6 +
  7 +1.1. "Contributor"
  8 + means each individual or legal entity that creates, contributes to
  9 + the creation of, or owns Covered Software.
  10 +
  11 +1.2. "Contributor Version"
  12 + means the combination of the Contributions of others (if any) used
  13 + by a Contributor and that particular Contributor's Contribution.
  14 +
  15 +1.3. "Contribution"
  16 + means Covered Software of a particular Contributor.
  17 +
  18 +1.4. "Covered Software"
  19 + means Source Code Form to which the initial Contributor has attached
  20 + the notice in Exhibit A, the Executable Form of such Source Code
  21 + Form, and Modifications of such Source Code Form, in each case
  22 + including portions thereof.
  23 +
  24 +1.5. "Incompatible With Secondary Licenses"
  25 + means
  26 +
  27 + (a) that the initial Contributor has attached the notice described
  28 + in Exhibit B to the Covered Software; or
  29 +
  30 + (b) that the Covered Software was made available under the terms of
  31 + version 1.1 or earlier of the License, but not also under the
  32 + terms of a Secondary License.
  33 +
  34 +1.6. "Executable Form"
  35 + means any form of the work other than Source Code Form.
  36 +
  37 +1.7. "Larger Work"
  38 + means a work that combines Covered Software with other material, in
  39 + a separate file or files, that is not Covered Software.
  40 +
  41 +1.8. "License"
  42 + means this document.
  43 +
  44 +1.9. "Licensable"
  45 + means having the right to grant, to the maximum extent possible,
  46 + whether at the time of the initial grant or subsequently, any and
  47 + all of the rights conveyed by this License.
  48 +
  49 +1.10. "Modifications"
  50 + means any of the following:
  51 +
  52 + (a) any file in Source Code Form that results from an addition to,
  53 + deletion from, or modification of the contents of Covered
  54 + Software; or
  55 +
  56 + (b) any new file in Source Code Form that contains any Covered
  57 + Software.
  58 +
  59 +1.11. "Patent Claims" of a Contributor
  60 + means any patent claim(s), including without limitation, method,
  61 + process, and apparatus claims, in any patent Licensable by such
  62 + Contributor that would be infringed, but for the grant of the
  63 + License, by the making, using, selling, offering for sale, having
  64 + made, import, or transfer of either its Contributions or its
  65 + Contributor Version.
  66 +
  67 +1.12. "Secondary License"
  68 + means either the GNU General Public License, Version 2.0, the GNU
  69 + Lesser General Public License, Version 2.1, the GNU Affero General
  70 + Public License, Version 3.0, or any later versions of those
  71 + licenses.
  72 +
  73 +1.13. "Source Code Form"
  74 + means the form of the work preferred for making modifications.
  75 +
  76 +1.14. "You" (or "Your")
  77 + means an individual or a legal entity exercising rights under this
  78 + License. For legal entities, "You" includes any entity that
  79 + controls, is controlled by, or is under common control with You. For
  80 + purposes of this definition, "control" means (a) the power, direct
  81 + or indirect, to cause the direction or management of such entity,
  82 + whether by contract or otherwise, or (b) ownership of more than
  83 + fifty percent (50%) of the outstanding shares or beneficial
  84 + ownership of such entity.
  85 +
  86 +2. License Grants and Conditions
  87 +--------------------------------
  88 +
  89 +2.1. Grants
  90 +
  91 +Each Contributor hereby grants You a world-wide, royalty-free,
  92 +non-exclusive license:
  93 +
  94 +(a) under intellectual property rights (other than patent or trademark)
  95 + Licensable by such Contributor to use, reproduce, make available,
  96 + modify, display, perform, distribute, and otherwise exploit its
  97 + Contributions, either on an unmodified basis, with Modifications, or
  98 + as part of a Larger Work; and
  99 +
  100 +(b) under Patent Claims of such Contributor to make, use, sell, offer
  101 + for sale, have made, import, and otherwise transfer either its
  102 + Contributions or its Contributor Version.
  103 +
  104 +2.2. Effective Date
  105 +
  106 +The licenses granted in Section 2.1 with respect to any Contribution
  107 +become effective for each Contribution on the date the Contributor first
  108 +distributes such Contribution.
  109 +
  110 +2.3. Limitations on Grant Scope
  111 +
  112 +The licenses granted in this Section 2 are the only rights granted under
  113 +this License. No additional rights or licenses will be implied from the
  114 +distribution or licensing of Covered Software under this License.
  115 +Notwithstanding Section 2.1(b) above, no patent license is granted by a
  116 +Contributor:
  117 +
  118 +(a) for any code that a Contributor has removed from Covered Software;
  119 + or
  120 +
  121 +(b) for infringements caused by: (i) Your and any other third party's
  122 + modifications of Covered Software, or (ii) the combination of its
  123 + Contributions with other software (except as part of its Contributor
  124 + Version); or
  125 +
  126 +(c) under Patent Claims infringed by Covered Software in the absence of
  127 + its Contributions.
  128 +
  129 +This License does not grant any rights in the trademarks, service marks,
  130 +or logos of any Contributor (except as may be necessary to comply with
  131 +the notice requirements in Section 3.4).
  132 +
  133 +2.4. Subsequent Licenses
  134 +
  135 +No Contributor makes additional grants as a result of Your choice to
  136 +distribute the Covered Software under a subsequent version of this
  137 +License (see Section 10.2) or under the terms of a Secondary License (if
  138 +permitted under the terms of Section 3.3).
  139 +
  140 +2.5. Representation
  141 +
  142 +Each Contributor represents that the Contributor believes its
  143 +Contributions are its original creation(s) or it has sufficient rights
  144 +to grant the rights to its Contributions conveyed by this License.
  145 +
  146 +2.6. Fair Use
  147 +
  148 +This License is not intended to limit any rights You have under
  149 +applicable copyright doctrines of fair use, fair dealing, or other
  150 +equivalents.
  151 +
  152 +2.7. Conditions
  153 +
  154 +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
  155 +in Section 2.1.
  156 +
  157 +3. Responsibilities
  158 +-------------------
  159 +
  160 +3.1. Distribution of Source Form
  161 +
  162 +All distribution of Covered Software in Source Code Form, including any
  163 +Modifications that You create or to which You contribute, must be under
  164 +the terms of this License. You must inform recipients that the Source
  165 +Code Form of the Covered Software is governed by the terms of this
  166 +License, and how they can obtain a copy of this License. You may not
  167 +attempt to alter or restrict the recipients' rights in the Source Code
  168 +Form.
  169 +
  170 +3.2. Distribution of Executable Form
  171 +
  172 +If You distribute Covered Software in Executable Form then:
  173 +
  174 +(a) such Covered Software must also be made available in Source Code
  175 + Form, as described in Section 3.1, and You must inform recipients of
  176 + the Executable Form how they can obtain a copy of such Source Code
  177 + Form by reasonable means in a timely manner, at a charge no more
  178 + than the cost of distribution to the recipient; and
  179 +
  180 +(b) You may distribute such Executable Form under the terms of this
  181 + License, or sublicense it under different terms, provided that the
  182 + license for the Executable Form does not attempt to limit or alter
  183 + the recipients' rights in the Source Code Form under this License.
  184 +
  185 +3.3. Distribution of a Larger Work
  186 +
  187 +You may create and distribute a Larger Work under terms of Your choice,
  188 +provided that You also comply with the requirements of this License for
  189 +the Covered Software. If the Larger Work is a combination of Covered
  190 +Software with a work governed by one or more Secondary Licenses, and the
  191 +Covered Software is not Incompatible With Secondary Licenses, this
  192 +License permits You to additionally distribute such Covered Software
  193 +under the terms of such Secondary License(s), so that the recipient of
  194 +the Larger Work may, at their option, further distribute the Covered
  195 +Software under the terms of either this License or such Secondary
  196 +License(s).
  197 +
  198 +3.4. Notices
  199 +
  200 +You may not remove or alter the substance of any license notices
  201 +(including copyright notices, patent notices, disclaimers of warranty,
  202 +or limitations of liability) contained within the Source Code Form of
  203 +the Covered Software, except that You may alter any license notices to
  204 +the extent required to remedy known factual inaccuracies.
  205 +
  206 +3.5. Application of Additional Terms
  207 +
  208 +You may choose to offer, and to charge a fee for, warranty, support,
  209 +indemnity or liability obligations to one or more recipients of Covered
  210 +Software. However, You may do so only on Your own behalf, and not on
  211 +behalf of any Contributor. You must make it absolutely clear that any
  212 +such warranty, support, indemnity, or liability obligation is offered by
  213 +You alone, and You hereby agree to indemnify every Contributor for any
  214 +liability incurred by such Contributor as a result of warranty, support,
  215 +indemnity or liability terms You offer. You may include additional
  216 +disclaimers of warranty and limitations of liability specific to any
  217 +jurisdiction.
  218 +
  219 +4. Inability to Comply Due to Statute or Regulation
  220 +---------------------------------------------------
  221 +
  222 +If it is impossible for You to comply with any of the terms of this
  223 +License with respect to some or all of the Covered Software due to
  224 +statute, judicial order, or regulation then You must: (a) comply with
  225 +the terms of this License to the maximum extent possible; and (b)
  226 +describe the limitations and the code they affect. Such description must
  227 +be placed in a text file included with all distributions of the Covered
  228 +Software under this License. Except to the extent prohibited by statute
  229 +or regulation, such description must be sufficiently detailed for a
  230 +recipient of ordinary skill to be able to understand it.
  231 +
  232 +5. Termination
  233 +--------------
  234 +
  235 +5.1. The rights granted under this License will terminate automatically
  236 +if You fail to comply with any of its terms. However, if You become
  237 +compliant, then the rights granted under this License from a particular
  238 +Contributor are reinstated (a) provisionally, unless and until such
  239 +Contributor explicitly and finally terminates Your grants, and (b) on an
  240 +ongoing basis, if such Contributor fails to notify You of the
  241 +non-compliance by some reasonable means prior to 60 days after You have
  242 +come back into compliance. Moreover, Your grants from a particular
  243 +Contributor are reinstated on an ongoing basis if such Contributor
  244 +notifies You of the non-compliance by some reasonable means, this is the
  245 +first time You have received notice of non-compliance with this License
  246 +from such Contributor, and You become compliant prior to 30 days after
  247 +Your receipt of the notice.
  248 +
  249 +5.2. If You initiate litigation against any entity by asserting a patent
  250 +infringement claim (excluding declaratory judgment actions,
  251 +counter-claims, and cross-claims) alleging that a Contributor Version
  252 +directly or indirectly infringes any patent, then the rights granted to
  253 +You by any and all Contributors for the Covered Software under Section
  254 +2.1 of this License shall terminate.
  255 +
  256 +5.3. In the event of termination under Sections 5.1 or 5.2 above, all
  257 +end user license agreements (excluding distributors and resellers) which
  258 +have been validly granted by You or Your distributors under this License
  259 +prior to termination shall survive termination.
  260 +
  261 +************************************************************************
  262 +* *
  263 +* 6. Disclaimer of Warranty *
  264 +* ------------------------- *
  265 +* *
  266 +* Covered Software is provided under this License on an "as is" *
  267 +* basis, without warranty of any kind, either expressed, implied, or *
  268 +* statutory, including, without limitation, warranties that the *
  269 +* Covered Software is free of defects, merchantable, fit for a *
  270 +* particular purpose or non-infringing. The entire risk as to the *
  271 +* quality and performance of the Covered Software is with You. *
  272 +* Should any Covered Software prove defective in any respect, You *
  273 +* (not any Contributor) assume the cost of any necessary servicing, *
  274 +* repair, or correction. This disclaimer of warranty constitutes an *
  275 +* essential part of this License. No use of any Covered Software is *
  276 +* authorized under this License except under this disclaimer. *
  277 +* *
  278 +************************************************************************
  279 +
  280 +************************************************************************
  281 +* *
  282 +* 7. Limitation of Liability *
  283 +* -------------------------- *
  284 +* *
  285 +* Under no circumstances and under no legal theory, whether tort *
  286 +* (including negligence), contract, or otherwise, shall any *
  287 +* Contributor, or anyone who distributes Covered Software as *
  288 +* permitted above, be liable to You for any direct, indirect, *
  289 +* special, incidental, or consequential damages of any character *
  290 +* including, without limitation, damages for lost profits, loss of *
  291 +* goodwill, work stoppage, computer failure or malfunction, or any *
  292 +* and all other commercial damages or losses, even if such party *
  293 +* shall have been informed of the possibility of such damages. This *
  294 +* limitation of liability shall not apply to liability for death or *
  295 +* personal injury resulting from such party's negligence to the *
  296 +* extent applicable law prohibits such limitation. Some *
  297 +* jurisdictions do not allow the exclusion or limitation of *
  298 +* incidental or consequential damages, so this exclusion and *
  299 +* limitation may not apply to You. *
  300 +* *
  301 +************************************************************************
  302 +
  303 +8. Litigation
  304 +-------------
  305 +
  306 +Any litigation relating to this License may be brought only in the
  307 +courts of a jurisdiction where the defendant maintains its principal
  308 +place of business and such litigation shall be governed by laws of that
  309 +jurisdiction, without reference to its conflict-of-law provisions.
  310 +Nothing in this Section shall prevent a party's ability to bring
  311 +cross-claims or counter-claims.
  312 +
  313 +9. Miscellaneous
  314 +----------------
  315 +
  316 +This License represents the complete agreement concerning the subject
  317 +matter hereof. If any provision of this License is held to be
  318 +unenforceable, such provision shall be reformed only to the extent
  319 +necessary to make it enforceable. Any law or regulation which provides
  320 +that the language of a contract shall be construed against the drafter
  321 +shall not be used to construe this License against a Contributor.
  322 +
  323 +10. Versions of the License
  324 +---------------------------
  325 +
  326 +10.1. New Versions
  327 +
  328 +Mozilla Foundation is the license steward. Except as provided in Section
  329 +10.3, no one other than the license steward has the right to modify or
  330 +publish new versions of this License. Each version will be given a
  331 +distinguishing version number.
  332 +
  333 +10.2. Effect of New Versions
  334 +
  335 +You may distribute the Covered Software under the terms of the version
  336 +of the License under which You originally received the Covered Software,
  337 +or under the terms of any subsequent version published by the license
  338 +steward.
  339 +
  340 +10.3. Modified Versions
  341 +
  342 +If you create software not governed by this License, and you want to
  343 +create a new license for such software, you may create and use a
  344 +modified version of this License if you rename the license and remove
  345 +any references to the name of the license steward (except to note that
  346 +such modified license differs from this License).
  347 +
  348 +10.4. Distributing Source Code Form that is Incompatible With Secondary
  349 +Licenses
  350 +
  351 +If You choose to distribute Source Code Form that is Incompatible With
  352 +Secondary Licenses under the terms of this version of the License, the
  353 +notice described in Exhibit B of this License must be attached.
  354 +
  355 +Exhibit A - Source Code Form License Notice
  356 +-------------------------------------------
  357 +
  358 + This Source Code Form is subject to the terms of the Mozilla Public
  359 + License, v. 2.0. If a copy of the MPL was not distributed with this
  360 + file, You can obtain one at http://mozilla.org/MPL/2.0/.
  361 +
  362 +If it is not possible or desirable to put the notice in a particular
  363 +file, then You may include the notice in a location (such as a LICENSE
  364 +file in a relevant directory) where a recipient would be likely to look
  365 +for such a notice.
  366 +
  367 +You may add additional accurate notices of copyright ownership.
  368 +
  369 +Exhibit B - "Incompatible With Secondary Licenses" Notice
  370 +---------------------------------------------------------
  371 +
  372 + This Source Code Form is "Incompatible With Secondary Licenses", as
  373 + defined by the Mozilla Public License, v. 2.0.
... ...
src/github.com/go-sql-driver/mysql/PULL_REQUEST_TEMPLATE.md 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +### Description
  2 +Please explain the changes you made here.
  3 +
  4 +### Checklist
  5 +- [ ] Code compiles correctly
  6 +- [ ] Created tests which fail without the change (if possible)
  7 +- [ ] All tests passing
  8 +- [ ] Extended the README / documentation, if necessary
  9 +- [ ] Added myself / the copyright holder to the AUTHORS file
... ...
src/github.com/go-sql-driver/mysql/README.md 0 → 100644
... ... @@ -0,0 +1,447 @@
  1 +# Go-MySQL-Driver
  2 +
  3 +A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) package
  4 +
  5 +![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin")
  6 +
  7 +**Latest stable Release:** [Version 1.2 (June 03, 2014)](https://github.com/go-sql-driver/mysql/releases)
  8 +
  9 +[![Build Status](https://travis-ci.org/go-sql-driver/mysql.png?branch=master)](https://travis-ci.org/go-sql-driver/mysql)
  10 +
  11 +---------------------------------------
  12 + * [Features](#features)
  13 + * [Requirements](#requirements)
  14 + * [Installation](#installation)
  15 + * [Usage](#usage)
  16 + * [DSN (Data Source Name)](#dsn-data-source-name)
  17 + * [Password](#password)
  18 + * [Protocol](#protocol)
  19 + * [Address](#address)
  20 + * [Parameters](#parameters)
  21 + * [Examples](#examples)
  22 + * [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support)
  23 + * [time.Time support](#timetime-support)
  24 + * [Unicode support](#unicode-support)
  25 + * [Testing / Development](#testing--development)
  26 + * [License](#license)
  27 +
  28 +---------------------------------------
  29 +
  30 +## Features
  31 + * Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance")
  32 + * Native Go implementation. No C-bindings, just pure Go
  33 + * Connections over TCP/IPv4, TCP/IPv6, Unix domain sockets or [custom protocols](http://godoc.org/github.com/go-sql-driver/mysql#DialFunc)
  34 + * Automatic handling of broken connections
  35 + * Automatic Connection Pooling *(by database/sql package)*
  36 + * Supports queries larger than 16MB
  37 + * Full [`sql.RawBytes`](http://golang.org/pkg/database/sql/#RawBytes) support.
  38 + * Intelligent `LONG DATA` handling in prepared statements
  39 + * Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support
  40 + * Optional `time.Time` parsing
  41 + * Optional placeholder interpolation
  42 +
  43 +## Requirements
  44 + * Go 1.2 or higher
  45 + * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
  46 +
  47 +---------------------------------------
  48 +
  49 +## Installation
  50 +Simple install the package to your [$GOPATH](http://code.google.com/p/go-wiki/wiki/GOPATH "GOPATH") with the [go tool](http://golang.org/cmd/go/ "go command") from shell:
  51 +```bash
  52 +$ go get github.com/go-sql-driver/mysql
  53 +```
  54 +Make sure [Git is installed](http://git-scm.com/downloads) on your machine and in your system's `PATH`.
  55 +
  56 +## Usage
  57 +_Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](http://golang.org/pkg/database/sql) API then.
  58 +
  59 +Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name) as `dataSourceName`:
  60 +```go
  61 +import "database/sql"
  62 +import _ "github.com/go-sql-driver/mysql"
  63 +
  64 +db, err := sql.Open("mysql", "user:password@/dbname")
  65 +```
  66 +
  67 +[Examples are available in our Wiki](https://github.com/go-sql-driver/mysql/wiki/Examples "Go-MySQL-Driver Examples").
  68 +
  69 +
  70 +### DSN (Data Source Name)
  71 +
  72 +The Data Source Name has a common format, like e.g. [PEAR DB](http://pear.php.net/manual/en/package.database.db.intro-dsn.php) uses it, but without type-prefix (optional parts marked by squared brackets):
  73 +```
  74 +[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
  75 +```
  76 +
  77 +A DSN in its fullest form:
  78 +```
  79 +username:password@protocol(address)/dbname?param=value
  80 +```
  81 +
  82 +Except for the databasename, all values are optional. So the minimal DSN is:
  83 +```
  84 +/dbname
  85 +```
  86 +
  87 +If you do not want to preselect a database, leave `dbname` empty:
  88 +```
  89 +/
  90 +```
  91 +This has the same effect as an empty DSN string:
  92 +```
  93 +
  94 +```
  95 +
  96 +Alternatively, [Config.FormatDSN](https://godoc.org/github.com/go-sql-driver/mysql#Config.FormatDSN) can be used to create a DSN string by filling a struct.
  97 +
  98 +#### Password
  99 +Passwords can consist of any character. Escaping is **not** necessary.
  100 +
  101 +#### Protocol
  102 +See [net.Dial](http://golang.org/pkg/net/#Dial) for more information which networks are available.
  103 +In general you should use an Unix domain socket if available and TCP otherwise for best performance.
  104 +
  105 +#### Address
  106 +For TCP and UDP networks, addresses have the form `host:port`.
  107 +If `host` is a literal IPv6 address, it must be enclosed in square brackets.
  108 +The functions [net.JoinHostPort](http://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](http://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
  109 +
  110 +For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`.
  111 +
  112 +#### Parameters
  113 +*Parameters are case-sensitive!*
  114 +
  115 +Notice that any of `true`, `TRUE`, `True` or `1` is accepted to stand for a true boolean value. Not surprisingly, false can be specified as any of: `false`, `FALSE`, `False` or `0`.
  116 +
  117 +##### `allowAllFiles`
  118 +
  119 +```
  120 +Type: bool
  121 +Valid Values: true, false
  122 +Default: false
  123 +```
  124 +
  125 +`allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files.
  126 +[*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
  127 +
  128 +##### `allowCleartextPasswords`
  129 +
  130 +```
  131 +Type: bool
  132 +Valid Values: true, false
  133 +Default: false
  134 +```
  135 +
  136 +`allowCleartextPasswords=true` allows using the [cleartext client side plugin](http://dev.mysql.com/doc/en/cleartext-authentication-plugin.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network.
  137 +
  138 +##### `allowNativePasswords`
  139 +
  140 +```
  141 +Type: bool
  142 +Valid Values: true, false
  143 +Default: false
  144 +```
  145 +`allowNativePasswords=true` allows the usage of the mysql native password method.
  146 +
  147 +##### `allowOldPasswords`
  148 +
  149 +```
  150 +Type: bool
  151 +Valid Values: true, false
  152 +Default: false
  153 +```
  154 +`allowOldPasswords=true` allows the usage of the insecure old password method. This should be avoided, but is necessary in some cases. See also [the old_passwords wiki page](https://github.com/go-sql-driver/mysql/wiki/old_passwords).
  155 +
  156 +##### `charset`
  157 +
  158 +```
  159 +Type: string
  160 +Valid Values: <name>
  161 +Default: none
  162 +```
  163 +
  164 +Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
  165 +
  166 +Usage of the `charset` parameter is discouraged because it issues additional queries to the server.
  167 +Unless you need the fallback behavior, please use `collation` instead.
  168 +
  169 +##### `collation`
  170 +
  171 +```
  172 +Type: string
  173 +Valid Values: <name>
  174 +Default: utf8_general_ci
  175 +```
  176 +
  177 +Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail.
  178 +
  179 +A list of valid charsets for a server is retrievable with `SHOW COLLATION`.
  180 +
  181 +##### `clientFoundRows`
  182 +
  183 +```
  184 +Type: bool
  185 +Valid Values: true, false
  186 +Default: false
  187 +```
  188 +
  189 +`clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed.
  190 +
  191 +##### `columnsWithAlias`
  192 +
  193 +```
  194 +Type: bool
  195 +Valid Values: true, false
  196 +Default: false
  197 +```
  198 +
  199 +When `columnsWithAlias` is true, calls to `sql.Rows.Columns()` will return the table alias and the column name separated by a dot. For example:
  200 +
  201 +```
  202 +SELECT u.id FROM users as u
  203 +```
  204 +
  205 +will return `u.id` instead of just `id` if `columnsWithAlias=true`.
  206 +
  207 +##### `interpolateParams`
  208 +
  209 +```
  210 +Type: bool
  211 +Valid Values: true, false
  212 +Default: false
  213 +```
  214 +
  215 +If `interpolateParams` is true, placeholders (`?`) in calls to `db.Query()` and `db.Exec()` are interpolated into a single query string with given parameters. This reduces the number of roundtrips, since the driver has to prepare a statement, execute it with given parameters and close the statement again with `interpolateParams=false`.
  216 +
  217 +*This can not be used together with the multibyte encodings BIG5, CP932, GB2312, GBK or SJIS. These are blacklisted as they may [introduce a SQL injection vulnerability](http://stackoverflow.com/a/12118602/3430118)!*
  218 +
  219 +##### `loc`
  220 +
  221 +```
  222 +Type: string
  223 +Valid Values: <escaped name>
  224 +Default: UTC
  225 +```
  226 +
  227 +Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details.
  228 +
  229 +Note that this sets the location for time.Time values but does not change MySQL's [time_zone setting](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html). For that see the [time_zone system variable](#system-variables), which can also be set as a DSN parameter.
  230 +
  231 +Please keep in mind, that param values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`.
  232 +
  233 +##### `maxAllowedPacket`
  234 +```
  235 +Type: decimal number
  236 +Default: 0
  237 +```
  238 +
  239 +Max packet size allowed in bytes. Use `maxAllowedPacket=0` to automatically fetch the `max_allowed_packet` variable from server.
  240 +
  241 +##### `multiStatements`
  242 +
  243 +```
  244 +Type: bool
  245 +Valid Values: true, false
  246 +Default: false
  247 +```
  248 +
  249 +Allow multiple statements in one query. While this allows batch queries, it also greatly increases the risk of SQL injections. Only the result of the first query is returned, all other results are silently discarded.
  250 +
  251 +When `multiStatements` is used, `?` parameters must only be used in the first statement.
  252 +
  253 +##### `parseTime`
  254 +
  255 +```
  256 +Type: bool
  257 +Valid Values: true, false
  258 +Default: false
  259 +```
  260 +
  261 +`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
  262 +
  263 +
  264 +##### `readTimeout`
  265 +
  266 +```
  267 +Type: decimal number
  268 +Default: 0
  269 +```
  270 +
  271 +I/O read timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*.
  272 +
  273 +##### `strict`
  274 +
  275 +```
  276 +Type: bool
  277 +Valid Values: true, false
  278 +Default: false
  279 +```
  280 +
  281 +`strict=true` enables a driver-side strict mode in which MySQL warnings are treated as errors. This mode should not be used in production as it may lead to data corruption in certain situations.
  282 +
  283 +A server-side strict mode, which is safe for production use, can be set via the [`sql_mode`](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html) system variable.
  284 +
  285 +By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_notes) to ignore notes.
  286 +
  287 +##### `timeout`
  288 +
  289 +```
  290 +Type: decimal number
  291 +Default: OS default
  292 +```
  293 +
  294 +*Driver* side connection timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout).
  295 +
  296 +##### `tls`
  297 +
  298 +```
  299 +Type: bool / string
  300 +Valid Values: true, false, skip-verify, <name>
  301 +Default: false
  302 +```
  303 +
  304 +`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](http://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
  305 +
  306 +##### `writeTimeout`
  307 +
  308 +```
  309 +Type: decimal number
  310 +Default: 0
  311 +```
  312 +
  313 +I/O write timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*.
  314 +
  315 +
  316 +##### System Variables
  317 +
  318 +Any other parameters are interpreted as system variables:
  319 + * `<boolean_var>=<value>`: `SET <boolean_var>=<value>`
  320 + * `<enum_var>=<value>`: `SET <enum_var>=<value>`
  321 + * `<string_var>=%27<value>%27`: `SET <string_var>='<value>'`
  322 +
  323 +Rules:
  324 +* The values for string variables must be quoted with '
  325 +* The values must also be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!
  326 + (which implies values of string variables must be wrapped with `%27`)
  327 +
  328 +Examples:
  329 + * `autocommit=1`: `SET autocommit=1`
  330 + * [`time_zone=%27Europe%2FParis%27`](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html): `SET time_zone='Europe/Paris'`
  331 + * [`tx_isolation=%27REPEATABLE-READ%27`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `SET tx_isolation='REPEATABLE-READ'`
  332 +
  333 +
  334 +#### Examples
  335 +```
  336 +user@unix(/path/to/socket)/dbname
  337 +```
  338 +
  339 +```
  340 +root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local
  341 +```
  342 +
  343 +```
  344 +user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true
  345 +```
  346 +
  347 +Treat warnings as errors by setting the system variable [`sql_mode`](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html):
  348 +```
  349 +user:password@/dbname?sql_mode=TRADITIONAL
  350 +```
  351 +
  352 +TCP via IPv6:
  353 +```
  354 +user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s&collation=utf8mb4_unicode_ci
  355 +```
  356 +
  357 +TCP on a remote host, e.g. Amazon RDS:
  358 +```
  359 +id:password@tcp(your-amazonaws-uri.com:3306)/dbname
  360 +```
  361 +
  362 +Google Cloud SQL on App Engine (First Generation MySQL Server):
  363 +```
  364 +user@cloudsql(project-id:instance-name)/dbname
  365 +```
  366 +
  367 +Google Cloud SQL on App Engine (Second Generation MySQL Server):
  368 +```
  369 +user@cloudsql(project-id:regionname:instance-name)/dbname
  370 +```
  371 +
  372 +TCP using default port (3306) on localhost:
  373 +```
  374 +user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped
  375 +```
  376 +
  377 +Use the default protocol (tcp) and host (localhost:3306):
  378 +```
  379 +user:password@/dbname
  380 +```
  381 +
  382 +No Database preselected:
  383 +```
  384 +user:password@/
  385 +```
  386 +
  387 +### `LOAD DATA LOCAL INFILE` support
  388 +For this feature you need direct access to the package. Therefore you must change the import path (no `_`):
  389 +```go
  390 +import "github.com/go-sql-driver/mysql"
  391 +```
  392 +
  393 +Files must be whitelisted by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the Whitelist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)).
  394 +
  395 +To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore.
  396 +
  397 +See the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
  398 +
  399 +
  400 +### `time.Time` support
  401 +The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your programm.
  402 +
  403 +However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](http://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
  404 +
  405 +**Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
  406 +
  407 +Alternatively you can use the [`NullTime`](http://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`.
  408 +
  409 +
  410 +### Unicode support
  411 +Since version 1.1 Go-MySQL-Driver automatically uses the collation `utf8_general_ci` by default.
  412 +
  413 +Other collations / charsets can be set using the [`collation`](#collation) DSN parameter.
  414 +
  415 +Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAMES utf8`) to the DSN to enable proper UTF-8 support. This is not necessary anymore. The [`collation`](#collation) parameter should be preferred to set another collation / charset than the default.
  416 +
  417 +See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
  418 +
  419 +
  420 +## Testing / Development
  421 +To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
  422 +
  423 +Go-MySQL-Driver is not feature-complete yet. Your help is very appreciated.
  424 +If you want to contribute, you can work on an [open issue](https://github.com/go-sql-driver/mysql/issues?state=open) or review a [pull request](https://github.com/go-sql-driver/mysql/pulls).
  425 +
  426 +See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/CONTRIBUTING.md) for details.
  427 +
  428 +---------------------------------------
  429 +
  430 +## License
  431 +Go-MySQL-Driver is licensed under the [Mozilla Public License Version 2.0](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
  432 +
  433 +Mozilla summarizes the license scope as follows:
  434 +> MPL: The copyleft applies to any files containing MPLed code.
  435 +
  436 +
  437 +That means:
  438 + * You can **use** the **unchanged** source code both in private and commercially
  439 + * When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0)
  440 + * You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**
  441 +
  442 +Please read the [MPL 2.0 FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html) if you have further questions regarding the license.
  443 +
  444 +You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
  445 +
  446 +![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow")
  447 +
... ...
src/github.com/go-sql-driver/mysql/appengine.go 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +// +build appengine
  10 +
  11 +package mysql
  12 +
  13 +import (
  14 + "appengine/cloudsql"
  15 +)
  16 +
  17 +func init() {
  18 + RegisterDial("cloudsql", cloudsql.Dial)
  19 +}
... ...
src/github.com/go-sql-driver/mysql/benchmark_test.go 0 → 100644
... ... @@ -0,0 +1,246 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +import (
  12 + "bytes"
  13 + "database/sql"
  14 + "database/sql/driver"
  15 + "math"
  16 + "strings"
  17 + "sync"
  18 + "sync/atomic"
  19 + "testing"
  20 + "time"
  21 +)
  22 +
  23 +type TB testing.B
  24 +
  25 +func (tb *TB) check(err error) {
  26 + if err != nil {
  27 + tb.Fatal(err)
  28 + }
  29 +}
  30 +
  31 +func (tb *TB) checkDB(db *sql.DB, err error) *sql.DB {
  32 + tb.check(err)
  33 + return db
  34 +}
  35 +
  36 +func (tb *TB) checkRows(rows *sql.Rows, err error) *sql.Rows {
  37 + tb.check(err)
  38 + return rows
  39 +}
  40 +
  41 +func (tb *TB) checkStmt(stmt *sql.Stmt, err error) *sql.Stmt {
  42 + tb.check(err)
  43 + return stmt
  44 +}
  45 +
  46 +func initDB(b *testing.B, queries ...string) *sql.DB {
  47 + tb := (*TB)(b)
  48 + db := tb.checkDB(sql.Open("mysql", dsn))
  49 + for _, query := range queries {
  50 + if _, err := db.Exec(query); err != nil {
  51 + if w, ok := err.(MySQLWarnings); ok {
  52 + b.Logf("warning on %q: %v", query, w)
  53 + } else {
  54 + b.Fatalf("error on %q: %v", query, err)
  55 + }
  56 + }
  57 + }
  58 + return db
  59 +}
  60 +
  61 +const concurrencyLevel = 10
  62 +
  63 +func BenchmarkQuery(b *testing.B) {
  64 + tb := (*TB)(b)
  65 + b.StopTimer()
  66 + b.ReportAllocs()
  67 + db := initDB(b,
  68 + "DROP TABLE IF EXISTS foo",
  69 + "CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
  70 + `INSERT INTO foo VALUES (1, "one")`,
  71 + `INSERT INTO foo VALUES (2, "two")`,
  72 + )
  73 + db.SetMaxIdleConns(concurrencyLevel)
  74 + defer db.Close()
  75 +
  76 + stmt := tb.checkStmt(db.Prepare("SELECT val FROM foo WHERE id=?"))
  77 + defer stmt.Close()
  78 +
  79 + remain := int64(b.N)
  80 + var wg sync.WaitGroup
  81 + wg.Add(concurrencyLevel)
  82 + defer wg.Wait()
  83 + b.StartTimer()
  84 +
  85 + for i := 0; i < concurrencyLevel; i++ {
  86 + go func() {
  87 + for {
  88 + if atomic.AddInt64(&remain, -1) < 0 {
  89 + wg.Done()
  90 + return
  91 + }
  92 +
  93 + var got string
  94 + tb.check(stmt.QueryRow(1).Scan(&got))
  95 + if got != "one" {
  96 + b.Errorf("query = %q; want one", got)
  97 + wg.Done()
  98 + return
  99 + }
  100 + }
  101 + }()
  102 + }
  103 +}
  104 +
  105 +func BenchmarkExec(b *testing.B) {
  106 + tb := (*TB)(b)
  107 + b.StopTimer()
  108 + b.ReportAllocs()
  109 + db := tb.checkDB(sql.Open("mysql", dsn))
  110 + db.SetMaxIdleConns(concurrencyLevel)
  111 + defer db.Close()
  112 +
  113 + stmt := tb.checkStmt(db.Prepare("DO 1"))
  114 + defer stmt.Close()
  115 +
  116 + remain := int64(b.N)
  117 + var wg sync.WaitGroup
  118 + wg.Add(concurrencyLevel)
  119 + defer wg.Wait()
  120 + b.StartTimer()
  121 +
  122 + for i := 0; i < concurrencyLevel; i++ {
  123 + go func() {
  124 + for {
  125 + if atomic.AddInt64(&remain, -1) < 0 {
  126 + wg.Done()
  127 + return
  128 + }
  129 +
  130 + if _, err := stmt.Exec(); err != nil {
  131 + b.Fatal(err.Error())
  132 + }
  133 + }
  134 + }()
  135 + }
  136 +}
  137 +
  138 +// data, but no db writes
  139 +var roundtripSample []byte
  140 +
  141 +func initRoundtripBenchmarks() ([]byte, int, int) {
  142 + if roundtripSample == nil {
  143 + roundtripSample = []byte(strings.Repeat("0123456789abcdef", 1024*1024))
  144 + }
  145 + return roundtripSample, 16, len(roundtripSample)
  146 +}
  147 +
  148 +func BenchmarkRoundtripTxt(b *testing.B) {
  149 + b.StopTimer()
  150 + sample, min, max := initRoundtripBenchmarks()
  151 + sampleString := string(sample)
  152 + b.ReportAllocs()
  153 + tb := (*TB)(b)
  154 + db := tb.checkDB(sql.Open("mysql", dsn))
  155 + defer db.Close()
  156 + b.StartTimer()
  157 + var result string
  158 + for i := 0; i < b.N; i++ {
  159 + length := min + i
  160 + if length > max {
  161 + length = max
  162 + }
  163 + test := sampleString[0:length]
  164 + rows := tb.checkRows(db.Query(`SELECT "` + test + `"`))
  165 + if !rows.Next() {
  166 + rows.Close()
  167 + b.Fatalf("crashed")
  168 + }
  169 + err := rows.Scan(&result)
  170 + if err != nil {
  171 + rows.Close()
  172 + b.Fatalf("crashed")
  173 + }
  174 + if result != test {
  175 + rows.Close()
  176 + b.Errorf("mismatch")
  177 + }
  178 + rows.Close()
  179 + }
  180 +}
  181 +
  182 +func BenchmarkRoundtripBin(b *testing.B) {
  183 + b.StopTimer()
  184 + sample, min, max := initRoundtripBenchmarks()
  185 + b.ReportAllocs()
  186 + tb := (*TB)(b)
  187 + db := tb.checkDB(sql.Open("mysql", dsn))
  188 + defer db.Close()
  189 + stmt := tb.checkStmt(db.Prepare("SELECT ?"))
  190 + defer stmt.Close()
  191 + b.StartTimer()
  192 + var result sql.RawBytes
  193 + for i := 0; i < b.N; i++ {
  194 + length := min + i
  195 + if length > max {
  196 + length = max
  197 + }
  198 + test := sample[0:length]
  199 + rows := tb.checkRows(stmt.Query(test))
  200 + if !rows.Next() {
  201 + rows.Close()
  202 + b.Fatalf("crashed")
  203 + }
  204 + err := rows.Scan(&result)
  205 + if err != nil {
  206 + rows.Close()
  207 + b.Fatalf("crashed")
  208 + }
  209 + if !bytes.Equal(result, test) {
  210 + rows.Close()
  211 + b.Errorf("mismatch")
  212 + }
  213 + rows.Close()
  214 + }
  215 +}
  216 +
  217 +func BenchmarkInterpolation(b *testing.B) {
  218 + mc := &mysqlConn{
  219 + cfg: &Config{
  220 + InterpolateParams: true,
  221 + Loc: time.UTC,
  222 + },
  223 + maxAllowedPacket: maxPacketSize,
  224 + maxWriteSize: maxPacketSize - 1,
  225 + buf: newBuffer(nil),
  226 + }
  227 +
  228 + args := []driver.Value{
  229 + int64(42424242),
  230 + float64(math.Pi),
  231 + false,
  232 + time.Unix(1423411542, 807015000),
  233 + []byte("bytes containing special chars ' \" \a \x00"),
  234 + "string containing special chars ' \" \a \x00",
  235 + }
  236 + q := "SELECT ?, ?, ?, ?, ?, ?"
  237 +
  238 + b.ReportAllocs()
  239 + b.ResetTimer()
  240 + for i := 0; i < b.N; i++ {
  241 + _, err := mc.interpolateParams(q, args)
  242 + if err != nil {
  243 + b.Fatal(err)
  244 + }
  245 + }
  246 +}
... ...
src/github.com/go-sql-driver/mysql/buffer.go 0 → 100644
... ... @@ -0,0 +1,147 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +import (
  12 + "io"
  13 + "net"
  14 + "time"
  15 +)
  16 +
  17 +const defaultBufSize = 4096
  18 +
  19 +// A buffer which is used for both reading and writing.
  20 +// This is possible since communication on each connection is synchronous.
  21 +// In other words, we can't write and read simultaneously on the same connection.
  22 +// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
  23 +// Also highly optimized for this particular use case.
  24 +type buffer struct {
  25 + buf []byte
  26 + nc net.Conn
  27 + idx int
  28 + length int
  29 + timeout time.Duration
  30 +}
  31 +
  32 +func newBuffer(nc net.Conn) buffer {
  33 + var b [defaultBufSize]byte
  34 + return buffer{
  35 + buf: b[:],
  36 + nc: nc,
  37 + }
  38 +}
  39 +
  40 +// fill reads into the buffer until at least _need_ bytes are in it
  41 +func (b *buffer) fill(need int) error {
  42 + n := b.length
  43 +
  44 + // move existing data to the beginning
  45 + if n > 0 && b.idx > 0 {
  46 + copy(b.buf[0:n], b.buf[b.idx:])
  47 + }
  48 +
  49 + // grow buffer if necessary
  50 + // TODO: let the buffer shrink again at some point
  51 + // Maybe keep the org buf slice and swap back?
  52 + if need > len(b.buf) {
  53 + // Round up to the next multiple of the default size
  54 + newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
  55 + copy(newBuf, b.buf)
  56 + b.buf = newBuf
  57 + }
  58 +
  59 + b.idx = 0
  60 +
  61 + for {
  62 + if b.timeout > 0 {
  63 + if err := b.nc.SetReadDeadline(time.Now().Add(b.timeout)); err != nil {
  64 + return err
  65 + }
  66 + }
  67 +
  68 + nn, err := b.nc.Read(b.buf[n:])
  69 + n += nn
  70 +
  71 + switch err {
  72 + case nil:
  73 + if n < need {
  74 + continue
  75 + }
  76 + b.length = n
  77 + return nil
  78 +
  79 + case io.EOF:
  80 + if n >= need {
  81 + b.length = n
  82 + return nil
  83 + }
  84 + return io.ErrUnexpectedEOF
  85 +
  86 + default:
  87 + return err
  88 + }
  89 + }
  90 +}
  91 +
  92 +// returns next N bytes from buffer.
  93 +// The returned slice is only guaranteed to be valid until the next read
  94 +func (b *buffer) readNext(need int) ([]byte, error) {
  95 + if b.length < need {
  96 + // refill
  97 + if err := b.fill(need); err != nil {
  98 + return nil, err
  99 + }
  100 + }
  101 +
  102 + offset := b.idx
  103 + b.idx += need
  104 + b.length -= need
  105 + return b.buf[offset:b.idx], nil
  106 +}
  107 +
  108 +// returns a buffer with the requested size.
  109 +// If possible, a slice from the existing buffer is returned.
  110 +// Otherwise a bigger buffer is made.
  111 +// Only one buffer (total) can be used at a time.
  112 +func (b *buffer) takeBuffer(length int) []byte {
  113 + if b.length > 0 {
  114 + return nil
  115 + }
  116 +
  117 + // test (cheap) general case first
  118 + if length <= defaultBufSize || length <= cap(b.buf) {
  119 + return b.buf[:length]
  120 + }
  121 +
  122 + if length < maxPacketSize {
  123 + b.buf = make([]byte, length)
  124 + return b.buf
  125 + }
  126 + return make([]byte, length)
  127 +}
  128 +
  129 +// shortcut which can be used if the requested buffer is guaranteed to be
  130 +// smaller than defaultBufSize
  131 +// Only one buffer (total) can be used at a time.
  132 +func (b *buffer) takeSmallBuffer(length int) []byte {
  133 + if b.length == 0 {
  134 + return b.buf[:length]
  135 + }
  136 + return nil
  137 +}
  138 +
  139 +// takeCompleteBuffer returns the complete existing buffer.
  140 +// This can be used if the necessary buffer size is unknown.
  141 +// Only one buffer (total) can be used at a time.
  142 +func (b *buffer) takeCompleteBuffer() []byte {
  143 + if b.length == 0 {
  144 + return b.buf
  145 + }
  146 + return nil
  147 +}
... ...
src/github.com/go-sql-driver/mysql/collations.go 0 → 100644
... ... @@ -0,0 +1,250 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +const defaultCollation = "utf8_general_ci"
  12 +
  13 +// A list of available collations mapped to the internal ID.
  14 +// To update this map use the following MySQL query:
  15 +// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS
  16 +var collations = map[string]byte{
  17 + "big5_chinese_ci": 1,
  18 + "latin2_czech_cs": 2,
  19 + "dec8_swedish_ci": 3,
  20 + "cp850_general_ci": 4,
  21 + "latin1_german1_ci": 5,
  22 + "hp8_english_ci": 6,
  23 + "koi8r_general_ci": 7,
  24 + "latin1_swedish_ci": 8,
  25 + "latin2_general_ci": 9,
  26 + "swe7_swedish_ci": 10,
  27 + "ascii_general_ci": 11,
  28 + "ujis_japanese_ci": 12,
  29 + "sjis_japanese_ci": 13,
  30 + "cp1251_bulgarian_ci": 14,
  31 + "latin1_danish_ci": 15,
  32 + "hebrew_general_ci": 16,
  33 + "tis620_thai_ci": 18,
  34 + "euckr_korean_ci": 19,
  35 + "latin7_estonian_cs": 20,
  36 + "latin2_hungarian_ci": 21,
  37 + "koi8u_general_ci": 22,
  38 + "cp1251_ukrainian_ci": 23,
  39 + "gb2312_chinese_ci": 24,
  40 + "greek_general_ci": 25,
  41 + "cp1250_general_ci": 26,
  42 + "latin2_croatian_ci": 27,
  43 + "gbk_chinese_ci": 28,
  44 + "cp1257_lithuanian_ci": 29,
  45 + "latin5_turkish_ci": 30,
  46 + "latin1_german2_ci": 31,
  47 + "armscii8_general_ci": 32,
  48 + "utf8_general_ci": 33,
  49 + "cp1250_czech_cs": 34,
  50 + "ucs2_general_ci": 35,
  51 + "cp866_general_ci": 36,
  52 + "keybcs2_general_ci": 37,
  53 + "macce_general_ci": 38,
  54 + "macroman_general_ci": 39,
  55 + "cp852_general_ci": 40,
  56 + "latin7_general_ci": 41,
  57 + "latin7_general_cs": 42,
  58 + "macce_bin": 43,
  59 + "cp1250_croatian_ci": 44,
  60 + "utf8mb4_general_ci": 45,
  61 + "utf8mb4_bin": 46,
  62 + "latin1_bin": 47,
  63 + "latin1_general_ci": 48,
  64 + "latin1_general_cs": 49,
  65 + "cp1251_bin": 50,
  66 + "cp1251_general_ci": 51,
  67 + "cp1251_general_cs": 52,
  68 + "macroman_bin": 53,
  69 + "utf16_general_ci": 54,
  70 + "utf16_bin": 55,
  71 + "utf16le_general_ci": 56,
  72 + "cp1256_general_ci": 57,
  73 + "cp1257_bin": 58,
  74 + "cp1257_general_ci": 59,
  75 + "utf32_general_ci": 60,
  76 + "utf32_bin": 61,
  77 + "utf16le_bin": 62,
  78 + "binary": 63,
  79 + "armscii8_bin": 64,
  80 + "ascii_bin": 65,
  81 + "cp1250_bin": 66,
  82 + "cp1256_bin": 67,
  83 + "cp866_bin": 68,
  84 + "dec8_bin": 69,
  85 + "greek_bin": 70,
  86 + "hebrew_bin": 71,
  87 + "hp8_bin": 72,
  88 + "keybcs2_bin": 73,
  89 + "koi8r_bin": 74,
  90 + "koi8u_bin": 75,
  91 + "latin2_bin": 77,
  92 + "latin5_bin": 78,
  93 + "latin7_bin": 79,
  94 + "cp850_bin": 80,
  95 + "cp852_bin": 81,
  96 + "swe7_bin": 82,
  97 + "utf8_bin": 83,
  98 + "big5_bin": 84,
  99 + "euckr_bin": 85,
  100 + "gb2312_bin": 86,
  101 + "gbk_bin": 87,
  102 + "sjis_bin": 88,
  103 + "tis620_bin": 89,
  104 + "ucs2_bin": 90,
  105 + "ujis_bin": 91,
  106 + "geostd8_general_ci": 92,
  107 + "geostd8_bin": 93,
  108 + "latin1_spanish_ci": 94,
  109 + "cp932_japanese_ci": 95,
  110 + "cp932_bin": 96,
  111 + "eucjpms_japanese_ci": 97,
  112 + "eucjpms_bin": 98,
  113 + "cp1250_polish_ci": 99,
  114 + "utf16_unicode_ci": 101,
  115 + "utf16_icelandic_ci": 102,
  116 + "utf16_latvian_ci": 103,
  117 + "utf16_romanian_ci": 104,
  118 + "utf16_slovenian_ci": 105,
  119 + "utf16_polish_ci": 106,
  120 + "utf16_estonian_ci": 107,
  121 + "utf16_spanish_ci": 108,
  122 + "utf16_swedish_ci": 109,
  123 + "utf16_turkish_ci": 110,
  124 + "utf16_czech_ci": 111,
  125 + "utf16_danish_ci": 112,
  126 + "utf16_lithuanian_ci": 113,
  127 + "utf16_slovak_ci": 114,
  128 + "utf16_spanish2_ci": 115,
  129 + "utf16_roman_ci": 116,
  130 + "utf16_persian_ci": 117,
  131 + "utf16_esperanto_ci": 118,
  132 + "utf16_hungarian_ci": 119,
  133 + "utf16_sinhala_ci": 120,
  134 + "utf16_german2_ci": 121,
  135 + "utf16_croatian_ci": 122,
  136 + "utf16_unicode_520_ci": 123,
  137 + "utf16_vietnamese_ci": 124,
  138 + "ucs2_unicode_ci": 128,
  139 + "ucs2_icelandic_ci": 129,
  140 + "ucs2_latvian_ci": 130,
  141 + "ucs2_romanian_ci": 131,
  142 + "ucs2_slovenian_ci": 132,
  143 + "ucs2_polish_ci": 133,
  144 + "ucs2_estonian_ci": 134,
  145 + "ucs2_spanish_ci": 135,
  146 + "ucs2_swedish_ci": 136,
  147 + "ucs2_turkish_ci": 137,
  148 + "ucs2_czech_ci": 138,
  149 + "ucs2_danish_ci": 139,
  150 + "ucs2_lithuanian_ci": 140,
  151 + "ucs2_slovak_ci": 141,
  152 + "ucs2_spanish2_ci": 142,
  153 + "ucs2_roman_ci": 143,
  154 + "ucs2_persian_ci": 144,
  155 + "ucs2_esperanto_ci": 145,
  156 + "ucs2_hungarian_ci": 146,
  157 + "ucs2_sinhala_ci": 147,
  158 + "ucs2_german2_ci": 148,
  159 + "ucs2_croatian_ci": 149,
  160 + "ucs2_unicode_520_ci": 150,
  161 + "ucs2_vietnamese_ci": 151,
  162 + "ucs2_general_mysql500_ci": 159,
  163 + "utf32_unicode_ci": 160,
  164 + "utf32_icelandic_ci": 161,
  165 + "utf32_latvian_ci": 162,
  166 + "utf32_romanian_ci": 163,
  167 + "utf32_slovenian_ci": 164,
  168 + "utf32_polish_ci": 165,
  169 + "utf32_estonian_ci": 166,
  170 + "utf32_spanish_ci": 167,
  171 + "utf32_swedish_ci": 168,
  172 + "utf32_turkish_ci": 169,
  173 + "utf32_czech_ci": 170,
  174 + "utf32_danish_ci": 171,
  175 + "utf32_lithuanian_ci": 172,
  176 + "utf32_slovak_ci": 173,
  177 + "utf32_spanish2_ci": 174,
  178 + "utf32_roman_ci": 175,
  179 + "utf32_persian_ci": 176,
  180 + "utf32_esperanto_ci": 177,
  181 + "utf32_hungarian_ci": 178,
  182 + "utf32_sinhala_ci": 179,
  183 + "utf32_german2_ci": 180,
  184 + "utf32_croatian_ci": 181,
  185 + "utf32_unicode_520_ci": 182,
  186 + "utf32_vietnamese_ci": 183,
  187 + "utf8_unicode_ci": 192,
  188 + "utf8_icelandic_ci": 193,
  189 + "utf8_latvian_ci": 194,
  190 + "utf8_romanian_ci": 195,
  191 + "utf8_slovenian_ci": 196,
  192 + "utf8_polish_ci": 197,
  193 + "utf8_estonian_ci": 198,
  194 + "utf8_spanish_ci": 199,
  195 + "utf8_swedish_ci": 200,
  196 + "utf8_turkish_ci": 201,
  197 + "utf8_czech_ci": 202,
  198 + "utf8_danish_ci": 203,
  199 + "utf8_lithuanian_ci": 204,
  200 + "utf8_slovak_ci": 205,
  201 + "utf8_spanish2_ci": 206,
  202 + "utf8_roman_ci": 207,
  203 + "utf8_persian_ci": 208,
  204 + "utf8_esperanto_ci": 209,
  205 + "utf8_hungarian_ci": 210,
  206 + "utf8_sinhala_ci": 211,
  207 + "utf8_german2_ci": 212,
  208 + "utf8_croatian_ci": 213,
  209 + "utf8_unicode_520_ci": 214,
  210 + "utf8_vietnamese_ci": 215,
  211 + "utf8_general_mysql500_ci": 223,
  212 + "utf8mb4_unicode_ci": 224,
  213 + "utf8mb4_icelandic_ci": 225,
  214 + "utf8mb4_latvian_ci": 226,
  215 + "utf8mb4_romanian_ci": 227,
  216 + "utf8mb4_slovenian_ci": 228,
  217 + "utf8mb4_polish_ci": 229,
  218 + "utf8mb4_estonian_ci": 230,
  219 + "utf8mb4_spanish_ci": 231,
  220 + "utf8mb4_swedish_ci": 232,
  221 + "utf8mb4_turkish_ci": 233,
  222 + "utf8mb4_czech_ci": 234,
  223 + "utf8mb4_danish_ci": 235,
  224 + "utf8mb4_lithuanian_ci": 236,
  225 + "utf8mb4_slovak_ci": 237,
  226 + "utf8mb4_spanish2_ci": 238,
  227 + "utf8mb4_roman_ci": 239,
  228 + "utf8mb4_persian_ci": 240,
  229 + "utf8mb4_esperanto_ci": 241,
  230 + "utf8mb4_hungarian_ci": 242,
  231 + "utf8mb4_sinhala_ci": 243,
  232 + "utf8mb4_german2_ci": 244,
  233 + "utf8mb4_croatian_ci": 245,
  234 + "utf8mb4_unicode_520_ci": 246,
  235 + "utf8mb4_vietnamese_ci": 247,
  236 +}
  237 +
  238 +// A blacklist of collations which is unsafe to interpolate parameters.
  239 +// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
  240 +var unsafeCollations = map[string]bool{
  241 + "big5_chinese_ci": true,
  242 + "sjis_japanese_ci": true,
  243 + "gbk_chinese_ci": true,
  244 + "big5_bin": true,
  245 + "gb2312_bin": true,
  246 + "gbk_bin": true,
  247 + "sjis_bin": true,
  248 + "cp932_japanese_ci": true,
  249 + "cp932_bin": true,
  250 +}
... ...
src/github.com/go-sql-driver/mysql/connection.go 0 → 100644
... ... @@ -0,0 +1,377 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +import (
  12 + "database/sql/driver"
  13 + "net"
  14 + "strconv"
  15 + "strings"
  16 + "time"
  17 +)
  18 +
  19 +type mysqlConn struct {
  20 + buf buffer
  21 + netConn net.Conn
  22 + affectedRows uint64
  23 + insertId uint64
  24 + cfg *Config
  25 + maxAllowedPacket int
  26 + maxWriteSize int
  27 + writeTimeout time.Duration
  28 + flags clientFlag
  29 + status statusFlag
  30 + sequence uint8
  31 + parseTime bool
  32 + strict bool
  33 +}
  34 +
  35 +// Handles parameters set in DSN after the connection is established
  36 +func (mc *mysqlConn) handleParams() (err error) {
  37 + for param, val := range mc.cfg.Params {
  38 + switch param {
  39 + // Charset
  40 + case "charset":
  41 + charsets := strings.Split(val, ",")
  42 + for i := range charsets {
  43 + // ignore errors here - a charset may not exist
  44 + err = mc.exec("SET NAMES " + charsets[i])
  45 + if err == nil {
  46 + break
  47 + }
  48 + }
  49 + if err != nil {
  50 + return
  51 + }
  52 +
  53 + // System Vars
  54 + default:
  55 + err = mc.exec("SET " + param + "=" + val + "")
  56 + if err != nil {
  57 + return
  58 + }
  59 + }
  60 + }
  61 +
  62 + return
  63 +}
  64 +
  65 +func (mc *mysqlConn) Begin() (driver.Tx, error) {
  66 + if mc.netConn == nil {
  67 + errLog.Print(ErrInvalidConn)
  68 + return nil, driver.ErrBadConn
  69 + }
  70 + err := mc.exec("START TRANSACTION")
  71 + if err == nil {
  72 + return &mysqlTx{mc}, err
  73 + }
  74 +
  75 + return nil, err
  76 +}
  77 +
  78 +func (mc *mysqlConn) Close() (err error) {
  79 + // Makes Close idempotent
  80 + if mc.netConn != nil {
  81 + err = mc.writeCommandPacket(comQuit)
  82 + }
  83 +
  84 + mc.cleanup()
  85 +
  86 + return
  87 +}
  88 +
  89 +// Closes the network connection and unsets internal variables. Do not call this
  90 +// function after successfully authentication, call Close instead. This function
  91 +// is called before auth or on auth failure because MySQL will have already
  92 +// closed the network connection.
  93 +func (mc *mysqlConn) cleanup() {
  94 + // Makes cleanup idempotent
  95 + if mc.netConn != nil {
  96 + if err := mc.netConn.Close(); err != nil {
  97 + errLog.Print(err)
  98 + }
  99 + mc.netConn = nil
  100 + }
  101 + mc.cfg = nil
  102 + mc.buf.nc = nil
  103 +}
  104 +
  105 +func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
  106 + if mc.netConn == nil {
  107 + errLog.Print(ErrInvalidConn)
  108 + return nil, driver.ErrBadConn
  109 + }
  110 + // Send command
  111 + err := mc.writeCommandPacketStr(comStmtPrepare, query)
  112 + if err != nil {
  113 + return nil, err
  114 + }
  115 +
  116 + stmt := &mysqlStmt{
  117 + mc: mc,
  118 + }
  119 +
  120 + // Read Result
  121 + columnCount, err := stmt.readPrepareResultPacket()
  122 + if err == nil {
  123 + if stmt.paramCount > 0 {
  124 + if err = mc.readUntilEOF(); err != nil {
  125 + return nil, err
  126 + }
  127 + }
  128 +
  129 + if columnCount > 0 {
  130 + err = mc.readUntilEOF()
  131 + }
  132 + }
  133 +
  134 + return stmt, err
  135 +}
  136 +
  137 +func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) {
  138 + // Number of ? should be same to len(args)
  139 + if strings.Count(query, "?") != len(args) {
  140 + return "", driver.ErrSkip
  141 + }
  142 +
  143 + buf := mc.buf.takeCompleteBuffer()
  144 + if buf == nil {
  145 + // can not take the buffer. Something must be wrong with the connection
  146 + errLog.Print(ErrBusyBuffer)
  147 + return "", driver.ErrBadConn
  148 + }
  149 + buf = buf[:0]
  150 + argPos := 0
  151 +
  152 + for i := 0; i < len(query); i++ {
  153 + q := strings.IndexByte(query[i:], '?')
  154 + if q == -1 {
  155 + buf = append(buf, query[i:]...)
  156 + break
  157 + }
  158 + buf = append(buf, query[i:i+q]...)
  159 + i += q
  160 +
  161 + arg := args[argPos]
  162 + argPos++
  163 +
  164 + if arg == nil {
  165 + buf = append(buf, "NULL"...)
  166 + continue
  167 + }
  168 +
  169 + switch v := arg.(type) {
  170 + case int64:
  171 + buf = strconv.AppendInt(buf, v, 10)
  172 + case float64:
  173 + buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
  174 + case bool:
  175 + if v {
  176 + buf = append(buf, '1')
  177 + } else {
  178 + buf = append(buf, '0')
  179 + }
  180 + case time.Time:
  181 + if v.IsZero() {
  182 + buf = append(buf, "'0000-00-00'"...)
  183 + } else {
  184 + v := v.In(mc.cfg.Loc)
  185 + v = v.Add(time.Nanosecond * 500) // To round under microsecond
  186 + year := v.Year()
  187 + year100 := year / 100
  188 + year1 := year % 100
  189 + month := v.Month()
  190 + day := v.Day()
  191 + hour := v.Hour()
  192 + minute := v.Minute()
  193 + second := v.Second()
  194 + micro := v.Nanosecond() / 1000
  195 +
  196 + buf = append(buf, []byte{
  197 + '\'',
  198 + digits10[year100], digits01[year100],
  199 + digits10[year1], digits01[year1],
  200 + '-',
  201 + digits10[month], digits01[month],
  202 + '-',
  203 + digits10[day], digits01[day],
  204 + ' ',
  205 + digits10[hour], digits01[hour],
  206 + ':',
  207 + digits10[minute], digits01[minute],
  208 + ':',
  209 + digits10[second], digits01[second],
  210 + }...)
  211 +
  212 + if micro != 0 {
  213 + micro10000 := micro / 10000
  214 + micro100 := micro / 100 % 100
  215 + micro1 := micro % 100
  216 + buf = append(buf, []byte{
  217 + '.',
  218 + digits10[micro10000], digits01[micro10000],
  219 + digits10[micro100], digits01[micro100],
  220 + digits10[micro1], digits01[micro1],
  221 + }...)
  222 + }
  223 + buf = append(buf, '\'')
  224 + }
  225 + case []byte:
  226 + if v == nil {
  227 + buf = append(buf, "NULL"...)
  228 + } else {
  229 + buf = append(buf, "_binary'"...)
  230 + if mc.status&statusNoBackslashEscapes == 0 {
  231 + buf = escapeBytesBackslash(buf, v)
  232 + } else {
  233 + buf = escapeBytesQuotes(buf, v)
  234 + }
  235 + buf = append(buf, '\'')
  236 + }
  237 + case string:
  238 + buf = append(buf, '\'')
  239 + if mc.status&statusNoBackslashEscapes == 0 {
  240 + buf = escapeStringBackslash(buf, v)
  241 + } else {
  242 + buf = escapeStringQuotes(buf, v)
  243 + }
  244 + buf = append(buf, '\'')
  245 + default:
  246 + return "", driver.ErrSkip
  247 + }
  248 +
  249 + if len(buf)+4 > mc.maxAllowedPacket {
  250 + return "", driver.ErrSkip
  251 + }
  252 + }
  253 + if argPos != len(args) {
  254 + return "", driver.ErrSkip
  255 + }
  256 + return string(buf), nil
  257 +}
  258 +
  259 +func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
  260 + if mc.netConn == nil {
  261 + errLog.Print(ErrInvalidConn)
  262 + return nil, driver.ErrBadConn
  263 + }
  264 + if len(args) != 0 {
  265 + if !mc.cfg.InterpolateParams {
  266 + return nil, driver.ErrSkip
  267 + }
  268 + // try to interpolate the parameters to save extra roundtrips for preparing and closing a statement
  269 + prepared, err := mc.interpolateParams(query, args)
  270 + if err != nil {
  271 + return nil, err
  272 + }
  273 + query = prepared
  274 + args = nil
  275 + }
  276 + mc.affectedRows = 0
  277 + mc.insertId = 0
  278 +
  279 + err := mc.exec(query)
  280 + if err == nil {
  281 + return &mysqlResult{
  282 + affectedRows: int64(mc.affectedRows),
  283 + insertId: int64(mc.insertId),
  284 + }, err
  285 + }
  286 + return nil, err
  287 +}
  288 +
  289 +// Internal function to execute commands
  290 +func (mc *mysqlConn) exec(query string) error {
  291 + // Send command
  292 + err := mc.writeCommandPacketStr(comQuery, query)
  293 + if err != nil {
  294 + return err
  295 + }
  296 +
  297 + // Read Result
  298 + resLen, err := mc.readResultSetHeaderPacket()
  299 + if err == nil && resLen > 0 {
  300 + if err = mc.readUntilEOF(); err != nil {
  301 + return err
  302 + }
  303 +
  304 + err = mc.readUntilEOF()
  305 + }
  306 +
  307 + return err
  308 +}
  309 +
  310 +func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
  311 + if mc.netConn == nil {
  312 + errLog.Print(ErrInvalidConn)
  313 + return nil, driver.ErrBadConn
  314 + }
  315 + if len(args) != 0 {
  316 + if !mc.cfg.InterpolateParams {
  317 + return nil, driver.ErrSkip
  318 + }
  319 + // try client-side prepare to reduce roundtrip
  320 + prepared, err := mc.interpolateParams(query, args)
  321 + if err != nil {
  322 + return nil, err
  323 + }
  324 + query = prepared
  325 + args = nil
  326 + }
  327 + // Send command
  328 + err := mc.writeCommandPacketStr(comQuery, query)
  329 + if err == nil {
  330 + // Read Result
  331 + var resLen int
  332 + resLen, err = mc.readResultSetHeaderPacket()
  333 + if err == nil {
  334 + rows := new(textRows)
  335 + rows.mc = mc
  336 +
  337 + if resLen == 0 {
  338 + // no columns, no more data
  339 + return emptyRows{}, nil
  340 + }
  341 + // Columns
  342 + rows.columns, err = mc.readColumns(resLen)
  343 + return rows, err
  344 + }
  345 + }
  346 + return nil, err
  347 +}
  348 +
  349 +// Gets the value of the given MySQL System Variable
  350 +// The returned byte slice is only valid until the next read
  351 +func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
  352 + // Send command
  353 + if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil {
  354 + return nil, err
  355 + }
  356 +
  357 + // Read Result
  358 + resLen, err := mc.readResultSetHeaderPacket()
  359 + if err == nil {
  360 + rows := new(textRows)
  361 + rows.mc = mc
  362 + rows.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
  363 +
  364 + if resLen > 0 {
  365 + // Columns
  366 + if err := mc.readUntilEOF(); err != nil {
  367 + return nil, err
  368 + }
  369 + }
  370 +
  371 + dest := make([]driver.Value, resLen)
  372 + if err = rows.readRow(dest); err == nil {
  373 + return dest[0].([]byte), mc.readUntilEOF()
  374 + }
  375 + }
  376 + return nil, err
  377 +}
... ...
src/github.com/go-sql-driver/mysql/connection_test.go 0 → 100644
... ... @@ -0,0 +1,67 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2016 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +import (
  12 + "database/sql/driver"
  13 + "testing"
  14 +)
  15 +
  16 +func TestInterpolateParams(t *testing.T) {
  17 + mc := &mysqlConn{
  18 + buf: newBuffer(nil),
  19 + maxAllowedPacket: maxPacketSize,
  20 + cfg: &Config{
  21 + InterpolateParams: true,
  22 + },
  23 + }
  24 +
  25 + q, err := mc.interpolateParams("SELECT ?+?", []driver.Value{int64(42), "gopher"})
  26 + if err != nil {
  27 + t.Errorf("Expected err=nil, got %#v", err)
  28 + return
  29 + }
  30 + expected := `SELECT 42+'gopher'`
  31 + if q != expected {
  32 + t.Errorf("Expected: %q\nGot: %q", expected, q)
  33 + }
  34 +}
  35 +
  36 +func TestInterpolateParamsTooManyPlaceholders(t *testing.T) {
  37 + mc := &mysqlConn{
  38 + buf: newBuffer(nil),
  39 + maxAllowedPacket: maxPacketSize,
  40 + cfg: &Config{
  41 + InterpolateParams: true,
  42 + },
  43 + }
  44 +
  45 + q, err := mc.interpolateParams("SELECT ?+?", []driver.Value{int64(42)})
  46 + if err != driver.ErrSkip {
  47 + t.Errorf("Expected err=driver.ErrSkip, got err=%#v, q=%#v", err, q)
  48 + }
  49 +}
  50 +
  51 +// We don't support placeholder in string literal for now.
  52 +// https://github.com/go-sql-driver/mysql/pull/490
  53 +func TestInterpolateParamsPlaceholderInString(t *testing.T) {
  54 + mc := &mysqlConn{
  55 + buf: newBuffer(nil),
  56 + maxAllowedPacket: maxPacketSize,
  57 + cfg: &Config{
  58 + InterpolateParams: true,
  59 + },
  60 + }
  61 +
  62 + q, err := mc.interpolateParams("SELECT 'abc?xyz',?", []driver.Value{int64(42)})
  63 + // When InterpolateParams support string literal, this should return `"SELECT 'abc?xyz', 42`
  64 + if err != driver.ErrSkip {
  65 + t.Errorf("Expected err=driver.ErrSkip, got err=%#v, q=%#v", err, q)
  66 + }
  67 +}
... ...
src/github.com/go-sql-driver/mysql/const.go 0 → 100644
... ... @@ -0,0 +1,163 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +const (
  12 + minProtocolVersion byte = 10
  13 + maxPacketSize = 1<<24 - 1
  14 + timeFormat = "2006-01-02 15:04:05.999999"
  15 +)
  16 +
  17 +// MySQL constants documentation:
  18 +// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
  19 +
  20 +const (
  21 + iOK byte = 0x00
  22 + iLocalInFile byte = 0xfb
  23 + iEOF byte = 0xfe
  24 + iERR byte = 0xff
  25 +)
  26 +
  27 +// https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
  28 +type clientFlag uint32
  29 +
  30 +const (
  31 + clientLongPassword clientFlag = 1 << iota
  32 + clientFoundRows
  33 + clientLongFlag
  34 + clientConnectWithDB
  35 + clientNoSchema
  36 + clientCompress
  37 + clientODBC
  38 + clientLocalFiles
  39 + clientIgnoreSpace
  40 + clientProtocol41
  41 + clientInteractive
  42 + clientSSL
  43 + clientIgnoreSIGPIPE
  44 + clientTransactions
  45 + clientReserved
  46 + clientSecureConn
  47 + clientMultiStatements
  48 + clientMultiResults
  49 + clientPSMultiResults
  50 + clientPluginAuth
  51 + clientConnectAttrs
  52 + clientPluginAuthLenEncClientData
  53 + clientCanHandleExpiredPasswords
  54 + clientSessionTrack
  55 + clientDeprecateEOF
  56 +)
  57 +
  58 +const (
  59 + comQuit byte = iota + 1
  60 + comInitDB
  61 + comQuery
  62 + comFieldList
  63 + comCreateDB
  64 + comDropDB
  65 + comRefresh
  66 + comShutdown
  67 + comStatistics
  68 + comProcessInfo
  69 + comConnect
  70 + comProcessKill
  71 + comDebug
  72 + comPing
  73 + comTime
  74 + comDelayedInsert
  75 + comChangeUser
  76 + comBinlogDump
  77 + comTableDump
  78 + comConnectOut
  79 + comRegisterSlave
  80 + comStmtPrepare
  81 + comStmtExecute
  82 + comStmtSendLongData
  83 + comStmtClose
  84 + comStmtReset
  85 + comSetOption
  86 + comStmtFetch
  87 +)
  88 +
  89 +// https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType
  90 +const (
  91 + fieldTypeDecimal byte = iota
  92 + fieldTypeTiny
  93 + fieldTypeShort
  94 + fieldTypeLong
  95 + fieldTypeFloat
  96 + fieldTypeDouble
  97 + fieldTypeNULL
  98 + fieldTypeTimestamp
  99 + fieldTypeLongLong
  100 + fieldTypeInt24
  101 + fieldTypeDate
  102 + fieldTypeTime
  103 + fieldTypeDateTime
  104 + fieldTypeYear
  105 + fieldTypeNewDate
  106 + fieldTypeVarChar
  107 + fieldTypeBit
  108 +)
  109 +const (
  110 + fieldTypeJSON byte = iota + 0xf5
  111 + fieldTypeNewDecimal
  112 + fieldTypeEnum
  113 + fieldTypeSet
  114 + fieldTypeTinyBLOB
  115 + fieldTypeMediumBLOB
  116 + fieldTypeLongBLOB
  117 + fieldTypeBLOB
  118 + fieldTypeVarString
  119 + fieldTypeString
  120 + fieldTypeGeometry
  121 +)
  122 +
  123 +type fieldFlag uint16
  124 +
  125 +const (
  126 + flagNotNULL fieldFlag = 1 << iota
  127 + flagPriKey
  128 + flagUniqueKey
  129 + flagMultipleKey
  130 + flagBLOB
  131 + flagUnsigned
  132 + flagZeroFill
  133 + flagBinary
  134 + flagEnum
  135 + flagAutoIncrement
  136 + flagTimestamp
  137 + flagSet
  138 + flagUnknown1
  139 + flagUnknown2
  140 + flagUnknown3
  141 + flagUnknown4
  142 +)
  143 +
  144 +// http://dev.mysql.com/doc/internals/en/status-flags.html
  145 +type statusFlag uint16
  146 +
  147 +const (
  148 + statusInTrans statusFlag = 1 << iota
  149 + statusInAutocommit
  150 + statusReserved // Not in documentation
  151 + statusMoreResultsExists
  152 + statusNoGoodIndexUsed
  153 + statusNoIndexUsed
  154 + statusCursorExists
  155 + statusLastRowSent
  156 + statusDbDropped
  157 + statusNoBackslashEscapes
  158 + statusMetadataChanged
  159 + statusQueryWasSlow
  160 + statusPsOutParams
  161 + statusInTransReadonly
  162 + statusSessionStateChanged
  163 +)
... ...
src/github.com/go-sql-driver/mysql/driver.go 0 → 100644
... ... @@ -0,0 +1,176 @@
  1 +// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
  2 +//
  3 +// This Source Code Form is subject to the terms of the Mozilla Public
  4 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  6 +
  7 +// Package mysql provides a MySQL driver for Go's database/sql package
  8 +//
  9 +// The driver should be used via the database/sql package:
  10 +//
  11 +// import "database/sql"
  12 +// import _ "github.com/go-sql-driver/mysql"
  13 +//
  14 +// db, err := sql.Open("mysql", "user:password@/dbname")
  15 +//
  16 +// See https://github.com/go-sql-driver/mysql#usage for details
  17 +package mysql
  18 +
  19 +import (
  20 + "database/sql"
  21 + "database/sql/driver"
  22 + "net"
  23 +)
  24 +
  25 +// MySQLDriver is exported to make the driver directly accessible.
  26 +// In general the driver is used via the database/sql package.
  27 +type MySQLDriver struct{}
  28 +
  29 +// DialFunc is a function which can be used to establish the network connection.
  30 +// Custom dial functions must be registered with RegisterDial
  31 +type DialFunc func(addr string) (net.Conn, error)
  32 +
  33 +var dials map[string]DialFunc
  34 +
  35 +// RegisterDial registers a custom dial function. It can then be used by the
  36 +// network address mynet(addr), where mynet is the registered new network.
  37 +// addr is passed as a parameter to the dial function.
  38 +func RegisterDial(net string, dial DialFunc) {
  39 + if dials == nil {
  40 + dials = make(map[string]DialFunc)
  41 + }
  42 + dials[net] = dial
  43 +}
  44 +
  45 +// Open new Connection.
  46 +// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
  47 +// the DSN string is formated
  48 +func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
  49 + var err error
  50 +
  51 + // New mysqlConn
  52 + mc := &mysqlConn{
  53 + maxAllowedPacket: maxPacketSize,
  54 + maxWriteSize: maxPacketSize - 1,
  55 + }
  56 + mc.cfg, err = ParseDSN(dsn)
  57 + if err != nil {
  58 + return nil, err
  59 + }
  60 + mc.parseTime = mc.cfg.ParseTime
  61 + mc.strict = mc.cfg.Strict
  62 +
  63 + // Connect to Server
  64 + if dial, ok := dials[mc.cfg.Net]; ok {
  65 + mc.netConn, err = dial(mc.cfg.Addr)
  66 + } else {
  67 + nd := net.Dialer{Timeout: mc.cfg.Timeout}
  68 + mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr)
  69 + }
  70 + if err != nil {
  71 + return nil, err
  72 + }
  73 +
  74 + // Enable TCP Keepalives on TCP connections
  75 + if tc, ok := mc.netConn.(*net.TCPConn); ok {
  76 + if err := tc.SetKeepAlive(true); err != nil {
  77 + // Don't send COM_QUIT before handshake.
  78 + mc.netConn.Close()
  79 + mc.netConn = nil
  80 + return nil, err
  81 + }
  82 + }
  83 +
  84 + mc.buf = newBuffer(mc.netConn)
  85 +
  86 + // Set I/O timeouts
  87 + mc.buf.timeout = mc.cfg.ReadTimeout
  88 + mc.writeTimeout = mc.cfg.WriteTimeout
  89 +
  90 + // Reading Handshake Initialization Packet
  91 + cipher, err := mc.readInitPacket()
  92 + if err != nil {
  93 + mc.cleanup()
  94 + return nil, err
  95 + }
  96 +
  97 + // Send Client Authentication Packet
  98 + if err = mc.writeAuthPacket(cipher); err != nil {
  99 + mc.cleanup()
  100 + return nil, err
  101 + }
  102 +
  103 + // Handle response to auth packet, switch methods if possible
  104 + if err = handleAuthResult(mc); err != nil {
  105 + // Authentication failed and MySQL has already closed the connection
  106 + // (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
  107 + // Do not send COM_QUIT, just cleanup and return the error.
  108 + mc.cleanup()
  109 + return nil, err
  110 + }
  111 +
  112 + if mc.cfg.MaxAllowedPacket > 0 {
  113 + mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket
  114 + } else {
  115 + // Get max allowed packet size
  116 + maxap, err := mc.getSystemVar("max_allowed_packet")
  117 + if err != nil {
  118 + mc.Close()
  119 + return nil, err
  120 + }
  121 + mc.maxAllowedPacket = stringToInt(maxap) - 1
  122 + }
  123 + if mc.maxAllowedPacket < maxPacketSize {
  124 + mc.maxWriteSize = mc.maxAllowedPacket
  125 + }
  126 +
  127 + // Handle DSN Params
  128 + err = mc.handleParams()
  129 + if err != nil {
  130 + mc.Close()
  131 + return nil, err
  132 + }
  133 +
  134 + return mc, nil
  135 +}
  136 +
  137 +func handleAuthResult(mc *mysqlConn) error {
  138 + // Read Result Packet
  139 + cipher, err := mc.readResultOK()
  140 + if err == nil {
  141 + return nil // auth successful
  142 + }
  143 +
  144 + if mc.cfg == nil {
  145 + return err // auth failed and retry not possible
  146 + }
  147 +
  148 + // Retry auth if configured to do so.
  149 + if mc.cfg.AllowOldPasswords && err == ErrOldPassword {
  150 + // Retry with old authentication method. Note: there are edge cases
  151 + // where this should work but doesn't; this is currently "wontfix":
  152 + // https://github.com/go-sql-driver/mysql/issues/184
  153 + if err = mc.writeOldAuthPacket(cipher); err != nil {
  154 + return err
  155 + }
  156 + _, err = mc.readResultOK()
  157 + } else if mc.cfg.AllowCleartextPasswords && err == ErrCleartextPassword {
  158 + // Retry with clear text password for
  159 + // http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
  160 + // http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
  161 + if err = mc.writeClearAuthPacket(); err != nil {
  162 + return err
  163 + }
  164 + _, err = mc.readResultOK()
  165 + } else if mc.cfg.AllowNativePasswords && err == ErrNativePassword {
  166 + if err = mc.writeNativeAuthPacket(cipher); err != nil {
  167 + return err
  168 + }
  169 + _, err = mc.readResultOK()
  170 + }
  171 + return err
  172 +}
  173 +
  174 +func init() {
  175 + sql.Register("mysql", &MySQLDriver{})
  176 +}
... ...
src/github.com/go-sql-driver/mysql/driver_test.go 0 → 100644
... ... @@ -0,0 +1,1904 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +import (
  12 + "bytes"
  13 + "crypto/tls"
  14 + "database/sql"
  15 + "database/sql/driver"
  16 + "fmt"
  17 + "io"
  18 + "io/ioutil"
  19 + "log"
  20 + "net"
  21 + "net/url"
  22 + "os"
  23 + "strings"
  24 + "sync"
  25 + "sync/atomic"
  26 + "testing"
  27 + "time"
  28 +)
  29 +
  30 +var (
  31 + user string
  32 + pass string
  33 + prot string
  34 + addr string
  35 + dbname string
  36 + dsn string
  37 + netAddr string
  38 + available bool
  39 +)
  40 +
  41 +var (
  42 + tDate = time.Date(2012, 6, 14, 0, 0, 0, 0, time.UTC)
  43 + sDate = "2012-06-14"
  44 + tDateTime = time.Date(2011, 11, 20, 21, 27, 37, 0, time.UTC)
  45 + sDateTime = "2011-11-20 21:27:37"
  46 + tDate0 = time.Time{}
  47 + sDate0 = "0000-00-00"
  48 + sDateTime0 = "0000-00-00 00:00:00"
  49 +)
  50 +
  51 +// See https://github.com/go-sql-driver/mysql/wiki/Testing
  52 +func init() {
  53 + // get environment variables
  54 + env := func(key, defaultValue string) string {
  55 + if value := os.Getenv(key); value != "" {
  56 + return value
  57 + }
  58 + return defaultValue
  59 + }
  60 + user = env("MYSQL_TEST_USER", "root")
  61 + pass = env("MYSQL_TEST_PASS", "")
  62 + prot = env("MYSQL_TEST_PROT", "tcp")
  63 + addr = env("MYSQL_TEST_ADDR", "localhost:3306")
  64 + dbname = env("MYSQL_TEST_DBNAME", "gotest")
  65 + netAddr = fmt.Sprintf("%s(%s)", prot, addr)
  66 + dsn = fmt.Sprintf("%s:%s@%s/%s?timeout=30s&strict=true", user, pass, netAddr, dbname)
  67 + c, err := net.Dial(prot, addr)
  68 + if err == nil {
  69 + available = true
  70 + c.Close()
  71 + }
  72 +}
  73 +
  74 +type DBTest struct {
  75 + *testing.T
  76 + db *sql.DB
  77 +}
  78 +
  79 +func runTestsWithMultiStatement(t *testing.T, dsn string, tests ...func(dbt *DBTest)) {
  80 + if !available {
  81 + t.Skipf("MySQL server not running on %s", netAddr)
  82 + }
  83 +
  84 + dsn += "&multiStatements=true"
  85 + var db *sql.DB
  86 + if _, err := ParseDSN(dsn); err != errInvalidDSNUnsafeCollation {
  87 + db, err = sql.Open("mysql", dsn)
  88 + if err != nil {
  89 + t.Fatalf("error connecting: %s", err.Error())
  90 + }
  91 + defer db.Close()
  92 + }
  93 +
  94 + dbt := &DBTest{t, db}
  95 + for _, test := range tests {
  96 + test(dbt)
  97 + dbt.db.Exec("DROP TABLE IF EXISTS test")
  98 + }
  99 +}
  100 +
  101 +func runTests(t *testing.T, dsn string, tests ...func(dbt *DBTest)) {
  102 + if !available {
  103 + t.Skipf("MySQL server not running on %s", netAddr)
  104 + }
  105 +
  106 + db, err := sql.Open("mysql", dsn)
  107 + if err != nil {
  108 + t.Fatalf("error connecting: %s", err.Error())
  109 + }
  110 + defer db.Close()
  111 +
  112 + db.Exec("DROP TABLE IF EXISTS test")
  113 +
  114 + dsn2 := dsn + "&interpolateParams=true"
  115 + var db2 *sql.DB
  116 + if _, err := ParseDSN(dsn2); err != errInvalidDSNUnsafeCollation {
  117 + db2, err = sql.Open("mysql", dsn2)
  118 + if err != nil {
  119 + t.Fatalf("error connecting: %s", err.Error())
  120 + }
  121 + defer db2.Close()
  122 + }
  123 +
  124 + dsn3 := dsn + "&multiStatements=true"
  125 + var db3 *sql.DB
  126 + if _, err := ParseDSN(dsn3); err != errInvalidDSNUnsafeCollation {
  127 + db3, err = sql.Open("mysql", dsn3)
  128 + if err != nil {
  129 + t.Fatalf("error connecting: %s", err.Error())
  130 + }
  131 + defer db3.Close()
  132 + }
  133 +
  134 + dbt := &DBTest{t, db}
  135 + dbt2 := &DBTest{t, db2}
  136 + dbt3 := &DBTest{t, db3}
  137 + for _, test := range tests {
  138 + test(dbt)
  139 + dbt.db.Exec("DROP TABLE IF EXISTS test")
  140 + if db2 != nil {
  141 + test(dbt2)
  142 + dbt2.db.Exec("DROP TABLE IF EXISTS test")
  143 + }
  144 + if db3 != nil {
  145 + test(dbt3)
  146 + dbt3.db.Exec("DROP TABLE IF EXISTS test")
  147 + }
  148 + }
  149 +}
  150 +
  151 +func (dbt *DBTest) fail(method, query string, err error) {
  152 + if len(query) > 300 {
  153 + query = "[query too large to print]"
  154 + }
  155 + dbt.Fatalf("error on %s %s: %s", method, query, err.Error())
  156 +}
  157 +
  158 +func (dbt *DBTest) mustExec(query string, args ...interface{}) (res sql.Result) {
  159 + res, err := dbt.db.Exec(query, args...)
  160 + if err != nil {
  161 + dbt.fail("exec", query, err)
  162 + }
  163 + return res
  164 +}
  165 +
  166 +func (dbt *DBTest) mustQuery(query string, args ...interface{}) (rows *sql.Rows) {
  167 + rows, err := dbt.db.Query(query, args...)
  168 + if err != nil {
  169 + dbt.fail("query", query, err)
  170 + }
  171 + return rows
  172 +}
  173 +
  174 +func TestEmptyQuery(t *testing.T) {
  175 + runTests(t, dsn, func(dbt *DBTest) {
  176 + // just a comment, no query
  177 + rows := dbt.mustQuery("--")
  178 + // will hang before #255
  179 + if rows.Next() {
  180 + dbt.Errorf("next on rows must be false")
  181 + }
  182 + })
  183 +}
  184 +
  185 +func TestCRUD(t *testing.T) {
  186 + runTests(t, dsn, func(dbt *DBTest) {
  187 + // Create Table
  188 + dbt.mustExec("CREATE TABLE test (value BOOL)")
  189 +
  190 + // Test for unexpected data
  191 + var out bool
  192 + rows := dbt.mustQuery("SELECT * FROM test")
  193 + if rows.Next() {
  194 + dbt.Error("unexpected data in empty table")
  195 + }
  196 +
  197 + // Create Data
  198 + res := dbt.mustExec("INSERT INTO test VALUES (1)")
  199 + count, err := res.RowsAffected()
  200 + if err != nil {
  201 + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
  202 + }
  203 + if count != 1 {
  204 + dbt.Fatalf("expected 1 affected row, got %d", count)
  205 + }
  206 +
  207 + id, err := res.LastInsertId()
  208 + if err != nil {
  209 + dbt.Fatalf("res.LastInsertId() returned error: %s", err.Error())
  210 + }
  211 + if id != 0 {
  212 + dbt.Fatalf("expected InsertId 0, got %d", id)
  213 + }
  214 +
  215 + // Read
  216 + rows = dbt.mustQuery("SELECT value FROM test")
  217 + if rows.Next() {
  218 + rows.Scan(&out)
  219 + if true != out {
  220 + dbt.Errorf("true != %t", out)
  221 + }
  222 +
  223 + if rows.Next() {
  224 + dbt.Error("unexpected data")
  225 + }
  226 + } else {
  227 + dbt.Error("no data")
  228 + }
  229 +
  230 + // Update
  231 + res = dbt.mustExec("UPDATE test SET value = ? WHERE value = ?", false, true)
  232 + count, err = res.RowsAffected()
  233 + if err != nil {
  234 + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
  235 + }
  236 + if count != 1 {
  237 + dbt.Fatalf("expected 1 affected row, got %d", count)
  238 + }
  239 +
  240 + // Check Update
  241 + rows = dbt.mustQuery("SELECT value FROM test")
  242 + if rows.Next() {
  243 + rows.Scan(&out)
  244 + if false != out {
  245 + dbt.Errorf("false != %t", out)
  246 + }
  247 +
  248 + if rows.Next() {
  249 + dbt.Error("unexpected data")
  250 + }
  251 + } else {
  252 + dbt.Error("no data")
  253 + }
  254 +
  255 + // Delete
  256 + res = dbt.mustExec("DELETE FROM test WHERE value = ?", false)
  257 + count, err = res.RowsAffected()
  258 + if err != nil {
  259 + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
  260 + }
  261 + if count != 1 {
  262 + dbt.Fatalf("expected 1 affected row, got %d", count)
  263 + }
  264 +
  265 + // Check for unexpected rows
  266 + res = dbt.mustExec("DELETE FROM test")
  267 + count, err = res.RowsAffected()
  268 + if err != nil {
  269 + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
  270 + }
  271 + if count != 0 {
  272 + dbt.Fatalf("expected 0 affected row, got %d", count)
  273 + }
  274 + })
  275 +}
  276 +
  277 +func TestMultiQuery(t *testing.T) {
  278 + runTestsWithMultiStatement(t, dsn, func(dbt *DBTest) {
  279 + // Create Table
  280 + dbt.mustExec("CREATE TABLE `test` (`id` int(11) NOT NULL, `value` int(11) NOT NULL) ")
  281 +
  282 + // Create Data
  283 + res := dbt.mustExec("INSERT INTO test VALUES (1, 1)")
  284 + count, err := res.RowsAffected()
  285 + if err != nil {
  286 + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
  287 + }
  288 + if count != 1 {
  289 + dbt.Fatalf("expected 1 affected row, got %d", count)
  290 + }
  291 +
  292 + // Update
  293 + res = dbt.mustExec("UPDATE test SET value = 3 WHERE id = 1; UPDATE test SET value = 4 WHERE id = 1; UPDATE test SET value = 5 WHERE id = 1;")
  294 + count, err = res.RowsAffected()
  295 + if err != nil {
  296 + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
  297 + }
  298 + if count != 1 {
  299 + dbt.Fatalf("expected 1 affected row, got %d", count)
  300 + }
  301 +
  302 + // Read
  303 + var out int
  304 + rows := dbt.mustQuery("SELECT value FROM test WHERE id=1;")
  305 + if rows.Next() {
  306 + rows.Scan(&out)
  307 + if 5 != out {
  308 + dbt.Errorf("5 != %d", out)
  309 + }
  310 +
  311 + if rows.Next() {
  312 + dbt.Error("unexpected data")
  313 + }
  314 + } else {
  315 + dbt.Error("no data")
  316 + }
  317 +
  318 + })
  319 +}
  320 +
  321 +func TestInt(t *testing.T) {
  322 + runTests(t, dsn, func(dbt *DBTest) {
  323 + types := [5]string{"TINYINT", "SMALLINT", "MEDIUMINT", "INT", "BIGINT"}
  324 + in := int64(42)
  325 + var out int64
  326 + var rows *sql.Rows
  327 +
  328 + // SIGNED
  329 + for _, v := range types {
  330 + dbt.mustExec("CREATE TABLE test (value " + v + ")")
  331 +
  332 + dbt.mustExec("INSERT INTO test VALUES (?)", in)
  333 +
  334 + rows = dbt.mustQuery("SELECT value FROM test")
  335 + if rows.Next() {
  336 + rows.Scan(&out)
  337 + if in != out {
  338 + dbt.Errorf("%s: %d != %d", v, in, out)
  339 + }
  340 + } else {
  341 + dbt.Errorf("%s: no data", v)
  342 + }
  343 +
  344 + dbt.mustExec("DROP TABLE IF EXISTS test")
  345 + }
  346 +
  347 + // UNSIGNED ZEROFILL
  348 + for _, v := range types {
  349 + dbt.mustExec("CREATE TABLE test (value " + v + " ZEROFILL)")
  350 +
  351 + dbt.mustExec("INSERT INTO test VALUES (?)", in)
  352 +
  353 + rows = dbt.mustQuery("SELECT value FROM test")
  354 + if rows.Next() {
  355 + rows.Scan(&out)
  356 + if in != out {
  357 + dbt.Errorf("%s ZEROFILL: %d != %d", v, in, out)
  358 + }
  359 + } else {
  360 + dbt.Errorf("%s ZEROFILL: no data", v)
  361 + }
  362 +
  363 + dbt.mustExec("DROP TABLE IF EXISTS test")
  364 + }
  365 + })
  366 +}
  367 +
  368 +func TestFloat32(t *testing.T) {
  369 + runTests(t, dsn, func(dbt *DBTest) {
  370 + types := [2]string{"FLOAT", "DOUBLE"}
  371 + in := float32(42.23)
  372 + var out float32
  373 + var rows *sql.Rows
  374 + for _, v := range types {
  375 + dbt.mustExec("CREATE TABLE test (value " + v + ")")
  376 + dbt.mustExec("INSERT INTO test VALUES (?)", in)
  377 + rows = dbt.mustQuery("SELECT value FROM test")
  378 + if rows.Next() {
  379 + rows.Scan(&out)
  380 + if in != out {
  381 + dbt.Errorf("%s: %g != %g", v, in, out)
  382 + }
  383 + } else {
  384 + dbt.Errorf("%s: no data", v)
  385 + }
  386 + dbt.mustExec("DROP TABLE IF EXISTS test")
  387 + }
  388 + })
  389 +}
  390 +
  391 +func TestFloat64(t *testing.T) {
  392 + runTests(t, dsn, func(dbt *DBTest) {
  393 + types := [2]string{"FLOAT", "DOUBLE"}
  394 + var expected float64 = 42.23
  395 + var out float64
  396 + var rows *sql.Rows
  397 + for _, v := range types {
  398 + dbt.mustExec("CREATE TABLE test (value " + v + ")")
  399 + dbt.mustExec("INSERT INTO test VALUES (42.23)")
  400 + rows = dbt.mustQuery("SELECT value FROM test")
  401 + if rows.Next() {
  402 + rows.Scan(&out)
  403 + if expected != out {
  404 + dbt.Errorf("%s: %g != %g", v, expected, out)
  405 + }
  406 + } else {
  407 + dbt.Errorf("%s: no data", v)
  408 + }
  409 + dbt.mustExec("DROP TABLE IF EXISTS test")
  410 + }
  411 + })
  412 +}
  413 +
  414 +func TestFloat64Placeholder(t *testing.T) {
  415 + runTests(t, dsn, func(dbt *DBTest) {
  416 + types := [2]string{"FLOAT", "DOUBLE"}
  417 + var expected float64 = 42.23
  418 + var out float64
  419 + var rows *sql.Rows
  420 + for _, v := range types {
  421 + dbt.mustExec("CREATE TABLE test (id int, value " + v + ")")
  422 + dbt.mustExec("INSERT INTO test VALUES (1, 42.23)")
  423 + rows = dbt.mustQuery("SELECT value FROM test WHERE id = ?", 1)
  424 + if rows.Next() {
  425 + rows.Scan(&out)
  426 + if expected != out {
  427 + dbt.Errorf("%s: %g != %g", v, expected, out)
  428 + }
  429 + } else {
  430 + dbt.Errorf("%s: no data", v)
  431 + }
  432 + dbt.mustExec("DROP TABLE IF EXISTS test")
  433 + }
  434 + })
  435 +}
  436 +
  437 +func TestString(t *testing.T) {
  438 + runTests(t, dsn, func(dbt *DBTest) {
  439 + types := [6]string{"CHAR(255)", "VARCHAR(255)", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT"}
  440 + in := "κόσμε üöäßñóùéàâÿœ'îë Árvíztűrő いろはにほへとちりぬるを イロハニホヘト דג סקרן чащах น่าฟังเอย"
  441 + var out string
  442 + var rows *sql.Rows
  443 +
  444 + for _, v := range types {
  445 + dbt.mustExec("CREATE TABLE test (value " + v + ") CHARACTER SET utf8")
  446 +
  447 + dbt.mustExec("INSERT INTO test VALUES (?)", in)
  448 +
  449 + rows = dbt.mustQuery("SELECT value FROM test")
  450 + if rows.Next() {
  451 + rows.Scan(&out)
  452 + if in != out {
  453 + dbt.Errorf("%s: %s != %s", v, in, out)
  454 + }
  455 + } else {
  456 + dbt.Errorf("%s: no data", v)
  457 + }
  458 +
  459 + dbt.mustExec("DROP TABLE IF EXISTS test")
  460 + }
  461 +
  462 + // BLOB
  463 + dbt.mustExec("CREATE TABLE test (id int, value BLOB) CHARACTER SET utf8")
  464 +
  465 + id := 2
  466 + in = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " +
  467 + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, " +
  468 + "sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. " +
  469 + "Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. " +
  470 + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " +
  471 + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, " +
  472 + "sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. " +
  473 + "Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
  474 + dbt.mustExec("INSERT INTO test VALUES (?, ?)", id, in)
  475 +
  476 + err := dbt.db.QueryRow("SELECT value FROM test WHERE id = ?", id).Scan(&out)
  477 + if err != nil {
  478 + dbt.Fatalf("Error on BLOB-Query: %s", err.Error())
  479 + } else if out != in {
  480 + dbt.Errorf("BLOB: %s != %s", in, out)
  481 + }
  482 + })
  483 +}
  484 +
  485 +type timeTests struct {
  486 + dbtype string
  487 + tlayout string
  488 + tests []timeTest
  489 +}
  490 +
  491 +type timeTest struct {
  492 + s string // leading "!": do not use t as value in queries
  493 + t time.Time
  494 +}
  495 +
  496 +type timeMode byte
  497 +
  498 +func (t timeMode) String() string {
  499 + switch t {
  500 + case binaryString:
  501 + return "binary:string"
  502 + case binaryTime:
  503 + return "binary:time.Time"
  504 + case textString:
  505 + return "text:string"
  506 + }
  507 + panic("unsupported timeMode")
  508 +}
  509 +
  510 +func (t timeMode) Binary() bool {
  511 + switch t {
  512 + case binaryString, binaryTime:
  513 + return true
  514 + }
  515 + return false
  516 +}
  517 +
  518 +const (
  519 + binaryString timeMode = iota
  520 + binaryTime
  521 + textString
  522 +)
  523 +
  524 +func (t timeTest) genQuery(dbtype string, mode timeMode) string {
  525 + var inner string
  526 + if mode.Binary() {
  527 + inner = "?"
  528 + } else {
  529 + inner = `"%s"`
  530 + }
  531 + return `SELECT cast(` + inner + ` as ` + dbtype + `)`
  532 +}
  533 +
  534 +func (t timeTest) run(dbt *DBTest, dbtype, tlayout string, mode timeMode) {
  535 + var rows *sql.Rows
  536 + query := t.genQuery(dbtype, mode)
  537 + switch mode {
  538 + case binaryString:
  539 + rows = dbt.mustQuery(query, t.s)
  540 + case binaryTime:
  541 + rows = dbt.mustQuery(query, t.t)
  542 + case textString:
  543 + query = fmt.Sprintf(query, t.s)
  544 + rows = dbt.mustQuery(query)
  545 + default:
  546 + panic("unsupported mode")
  547 + }
  548 + defer rows.Close()
  549 + var err error
  550 + if !rows.Next() {
  551 + err = rows.Err()
  552 + if err == nil {
  553 + err = fmt.Errorf("no data")
  554 + }
  555 + dbt.Errorf("%s [%s]: %s", dbtype, mode, err)
  556 + return
  557 + }
  558 + var dst interface{}
  559 + err = rows.Scan(&dst)
  560 + if err != nil {
  561 + dbt.Errorf("%s [%s]: %s", dbtype, mode, err)
  562 + return
  563 + }
  564 + switch val := dst.(type) {
  565 + case []uint8:
  566 + str := string(val)
  567 + if str == t.s {
  568 + return
  569 + }
  570 + if mode.Binary() && dbtype == "DATETIME" && len(str) == 26 && str[:19] == t.s {
  571 + // a fix mainly for TravisCI:
  572 + // accept full microsecond resolution in result for DATETIME columns
  573 + // where the binary protocol was used
  574 + return
  575 + }
  576 + dbt.Errorf("%s [%s] to string: expected %q, got %q",
  577 + dbtype, mode,
  578 + t.s, str,
  579 + )
  580 + case time.Time:
  581 + if val == t.t {
  582 + return
  583 + }
  584 + dbt.Errorf("%s [%s] to string: expected %q, got %q",
  585 + dbtype, mode,
  586 + t.s, val.Format(tlayout),
  587 + )
  588 + default:
  589 + fmt.Printf("%#v\n", []interface{}{dbtype, tlayout, mode, t.s, t.t})
  590 + dbt.Errorf("%s [%s]: unhandled type %T (is '%v')",
  591 + dbtype, mode,
  592 + val, val,
  593 + )
  594 + }
  595 +}
  596 +
  597 +func TestDateTime(t *testing.T) {
  598 + afterTime := func(t time.Time, d string) time.Time {
  599 + dur, err := time.ParseDuration(d)
  600 + if err != nil {
  601 + panic(err)
  602 + }
  603 + return t.Add(dur)
  604 + }
  605 + // NOTE: MySQL rounds DATETIME(x) up - but that's not included in the tests
  606 + format := "2006-01-02 15:04:05.999999"
  607 + t0 := time.Time{}
  608 + tstr0 := "0000-00-00 00:00:00.000000"
  609 + testcases := []timeTests{
  610 + {"DATE", format[:10], []timeTest{
  611 + {t: time.Date(2011, 11, 20, 0, 0, 0, 0, time.UTC)},
  612 + {t: t0, s: tstr0[:10]},
  613 + }},
  614 + {"DATETIME", format[:19], []timeTest{
  615 + {t: time.Date(2011, 11, 20, 21, 27, 37, 0, time.UTC)},
  616 + {t: t0, s: tstr0[:19]},
  617 + }},
  618 + {"DATETIME(0)", format[:21], []timeTest{
  619 + {t: time.Date(2011, 11, 20, 21, 27, 37, 0, time.UTC)},
  620 + {t: t0, s: tstr0[:19]},
  621 + }},
  622 + {"DATETIME(1)", format[:21], []timeTest{
  623 + {t: time.Date(2011, 11, 20, 21, 27, 37, 100000000, time.UTC)},
  624 + {t: t0, s: tstr0[:21]},
  625 + }},
  626 + {"DATETIME(6)", format, []timeTest{
  627 + {t: time.Date(2011, 11, 20, 21, 27, 37, 123456000, time.UTC)},
  628 + {t: t0, s: tstr0},
  629 + }},
  630 + {"TIME", format[11:19], []timeTest{
  631 + {t: afterTime(t0, "12345s")},
  632 + {s: "!-12:34:56"},
  633 + {s: "!-838:59:59"},
  634 + {s: "!838:59:59"},
  635 + {t: t0, s: tstr0[11:19]},
  636 + }},
  637 + {"TIME(0)", format[11:19], []timeTest{
  638 + {t: afterTime(t0, "12345s")},
  639 + {s: "!-12:34:56"},
  640 + {s: "!-838:59:59"},
  641 + {s: "!838:59:59"},
  642 + {t: t0, s: tstr0[11:19]},
  643 + }},
  644 + {"TIME(1)", format[11:21], []timeTest{
  645 + {t: afterTime(t0, "12345600ms")},
  646 + {s: "!-12:34:56.7"},
  647 + {s: "!-838:59:58.9"},
  648 + {s: "!838:59:58.9"},
  649 + {t: t0, s: tstr0[11:21]},
  650 + }},
  651 + {"TIME(6)", format[11:], []timeTest{
  652 + {t: afterTime(t0, "1234567890123000ns")},
  653 + {s: "!-12:34:56.789012"},
  654 + {s: "!-838:59:58.999999"},
  655 + {s: "!838:59:58.999999"},
  656 + {t: t0, s: tstr0[11:]},
  657 + }},
  658 + }
  659 + dsns := []string{
  660 + dsn + "&parseTime=true",
  661 + dsn + "&parseTime=false",
  662 + }
  663 + for _, testdsn := range dsns {
  664 + runTests(t, testdsn, func(dbt *DBTest) {
  665 + microsecsSupported := false
  666 + zeroDateSupported := false
  667 + var rows *sql.Rows
  668 + var err error
  669 + rows, err = dbt.db.Query(`SELECT cast("00:00:00.1" as TIME(1)) = "00:00:00.1"`)
  670 + if err == nil {
  671 + rows.Scan(&microsecsSupported)
  672 + rows.Close()
  673 + }
  674 + rows, err = dbt.db.Query(`SELECT cast("0000-00-00" as DATE) = "0000-00-00"`)
  675 + if err == nil {
  676 + rows.Scan(&zeroDateSupported)
  677 + rows.Close()
  678 + }
  679 + for _, setups := range testcases {
  680 + if t := setups.dbtype; !microsecsSupported && t[len(t)-1:] == ")" {
  681 + // skip fractional second tests if unsupported by server
  682 + continue
  683 + }
  684 + for _, setup := range setups.tests {
  685 + allowBinTime := true
  686 + if setup.s == "" {
  687 + // fill time string whereever Go can reliable produce it
  688 + setup.s = setup.t.Format(setups.tlayout)
  689 + } else if setup.s[0] == '!' {
  690 + // skip tests using setup.t as source in queries
  691 + allowBinTime = false
  692 + // fix setup.s - remove the "!"
  693 + setup.s = setup.s[1:]
  694 + }
  695 + if !zeroDateSupported && setup.s == tstr0[:len(setup.s)] {
  696 + // skip disallowed 0000-00-00 date
  697 + continue
  698 + }
  699 + setup.run(dbt, setups.dbtype, setups.tlayout, textString)
  700 + setup.run(dbt, setups.dbtype, setups.tlayout, binaryString)
  701 + if allowBinTime {
  702 + setup.run(dbt, setups.dbtype, setups.tlayout, binaryTime)
  703 + }
  704 + }
  705 + }
  706 + })
  707 + }
  708 +}
  709 +
  710 +func TestTimestampMicros(t *testing.T) {
  711 + format := "2006-01-02 15:04:05.999999"
  712 + f0 := format[:19]
  713 + f1 := format[:21]
  714 + f6 := format[:26]
  715 + runTests(t, dsn, func(dbt *DBTest) {
  716 + // check if microseconds are supported.
  717 + // Do not use timestamp(x) for that check - before 5.5.6, x would mean display width
  718 + // and not precision.
  719 + // Se last paragraph at http://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html
  720 + microsecsSupported := false
  721 + if rows, err := dbt.db.Query(`SELECT cast("00:00:00.1" as TIME(1)) = "00:00:00.1"`); err == nil {
  722 + rows.Scan(&microsecsSupported)
  723 + rows.Close()
  724 + }
  725 + if !microsecsSupported {
  726 + // skip test
  727 + return
  728 + }
  729 + _, err := dbt.db.Exec(`
  730 + CREATE TABLE test (
  731 + value0 TIMESTAMP NOT NULL DEFAULT '` + f0 + `',
  732 + value1 TIMESTAMP(1) NOT NULL DEFAULT '` + f1 + `',
  733 + value6 TIMESTAMP(6) NOT NULL DEFAULT '` + f6 + `'
  734 + )`,
  735 + )
  736 + if err != nil {
  737 + dbt.Error(err)
  738 + }
  739 + defer dbt.mustExec("DROP TABLE IF EXISTS test")
  740 + dbt.mustExec("INSERT INTO test SET value0=?, value1=?, value6=?", f0, f1, f6)
  741 + var res0, res1, res6 string
  742 + rows := dbt.mustQuery("SELECT * FROM test")
  743 + if !rows.Next() {
  744 + dbt.Errorf("test contained no selectable values")
  745 + }
  746 + err = rows.Scan(&res0, &res1, &res6)
  747 + if err != nil {
  748 + dbt.Error(err)
  749 + }
  750 + if res0 != f0 {
  751 + dbt.Errorf("expected %q, got %q", f0, res0)
  752 + }
  753 + if res1 != f1 {
  754 + dbt.Errorf("expected %q, got %q", f1, res1)
  755 + }
  756 + if res6 != f6 {
  757 + dbt.Errorf("expected %q, got %q", f6, res6)
  758 + }
  759 + })
  760 +}
  761 +
  762 +func TestNULL(t *testing.T) {
  763 + runTests(t, dsn, func(dbt *DBTest) {
  764 + nullStmt, err := dbt.db.Prepare("SELECT NULL")
  765 + if err != nil {
  766 + dbt.Fatal(err)
  767 + }
  768 + defer nullStmt.Close()
  769 +
  770 + nonNullStmt, err := dbt.db.Prepare("SELECT 1")
  771 + if err != nil {
  772 + dbt.Fatal(err)
  773 + }
  774 + defer nonNullStmt.Close()
  775 +
  776 + // NullBool
  777 + var nb sql.NullBool
  778 + // Invalid
  779 + if err = nullStmt.QueryRow().Scan(&nb); err != nil {
  780 + dbt.Fatal(err)
  781 + }
  782 + if nb.Valid {
  783 + dbt.Error("valid NullBool which should be invalid")
  784 + }
  785 + // Valid
  786 + if err = nonNullStmt.QueryRow().Scan(&nb); err != nil {
  787 + dbt.Fatal(err)
  788 + }
  789 + if !nb.Valid {
  790 + dbt.Error("invalid NullBool which should be valid")
  791 + } else if nb.Bool != true {
  792 + dbt.Errorf("Unexpected NullBool value: %t (should be true)", nb.Bool)
  793 + }
  794 +
  795 + // NullFloat64
  796 + var nf sql.NullFloat64
  797 + // Invalid
  798 + if err = nullStmt.QueryRow().Scan(&nf); err != nil {
  799 + dbt.Fatal(err)
  800 + }
  801 + if nf.Valid {
  802 + dbt.Error("valid NullFloat64 which should be invalid")
  803 + }
  804 + // Valid
  805 + if err = nonNullStmt.QueryRow().Scan(&nf); err != nil {
  806 + dbt.Fatal(err)
  807 + }
  808 + if !nf.Valid {
  809 + dbt.Error("invalid NullFloat64 which should be valid")
  810 + } else if nf.Float64 != float64(1) {
  811 + dbt.Errorf("unexpected NullFloat64 value: %f (should be 1.0)", nf.Float64)
  812 + }
  813 +
  814 + // NullInt64
  815 + var ni sql.NullInt64
  816 + // Invalid
  817 + if err = nullStmt.QueryRow().Scan(&ni); err != nil {
  818 + dbt.Fatal(err)
  819 + }
  820 + if ni.Valid {
  821 + dbt.Error("valid NullInt64 which should be invalid")
  822 + }
  823 + // Valid
  824 + if err = nonNullStmt.QueryRow().Scan(&ni); err != nil {
  825 + dbt.Fatal(err)
  826 + }
  827 + if !ni.Valid {
  828 + dbt.Error("invalid NullInt64 which should be valid")
  829 + } else if ni.Int64 != int64(1) {
  830 + dbt.Errorf("unexpected NullInt64 value: %d (should be 1)", ni.Int64)
  831 + }
  832 +
  833 + // NullString
  834 + var ns sql.NullString
  835 + // Invalid
  836 + if err = nullStmt.QueryRow().Scan(&ns); err != nil {
  837 + dbt.Fatal(err)
  838 + }
  839 + if ns.Valid {
  840 + dbt.Error("valid NullString which should be invalid")
  841 + }
  842 + // Valid
  843 + if err = nonNullStmt.QueryRow().Scan(&ns); err != nil {
  844 + dbt.Fatal(err)
  845 + }
  846 + if !ns.Valid {
  847 + dbt.Error("invalid NullString which should be valid")
  848 + } else if ns.String != `1` {
  849 + dbt.Error("unexpected NullString value:" + ns.String + " (should be `1`)")
  850 + }
  851 +
  852 + // nil-bytes
  853 + var b []byte
  854 + // Read nil
  855 + if err = nullStmt.QueryRow().Scan(&b); err != nil {
  856 + dbt.Fatal(err)
  857 + }
  858 + if b != nil {
  859 + dbt.Error("non-nil []byte wich should be nil")
  860 + }
  861 + // Read non-nil
  862 + if err = nonNullStmt.QueryRow().Scan(&b); err != nil {
  863 + dbt.Fatal(err)
  864 + }
  865 + if b == nil {
  866 + dbt.Error("nil []byte wich should be non-nil")
  867 + }
  868 + // Insert nil
  869 + b = nil
  870 + success := false
  871 + if err = dbt.db.QueryRow("SELECT ? IS NULL", b).Scan(&success); err != nil {
  872 + dbt.Fatal(err)
  873 + }
  874 + if !success {
  875 + dbt.Error("inserting []byte(nil) as NULL failed")
  876 + }
  877 + // Check input==output with input==nil
  878 + b = nil
  879 + if err = dbt.db.QueryRow("SELECT ?", b).Scan(&b); err != nil {
  880 + dbt.Fatal(err)
  881 + }
  882 + if b != nil {
  883 + dbt.Error("non-nil echo from nil input")
  884 + }
  885 + // Check input==output with input!=nil
  886 + b = []byte("")
  887 + if err = dbt.db.QueryRow("SELECT ?", b).Scan(&b); err != nil {
  888 + dbt.Fatal(err)
  889 + }
  890 + if b == nil {
  891 + dbt.Error("nil echo from non-nil input")
  892 + }
  893 +
  894 + // Insert NULL
  895 + dbt.mustExec("CREATE TABLE test (dummmy1 int, value int, dummy2 int)")
  896 +
  897 + dbt.mustExec("INSERT INTO test VALUES (?, ?, ?)", 1, nil, 2)
  898 +
  899 + var out interface{}
  900 + rows := dbt.mustQuery("SELECT * FROM test")
  901 + if rows.Next() {
  902 + rows.Scan(&out)
  903 + if out != nil {
  904 + dbt.Errorf("%v != nil", out)
  905 + }
  906 + } else {
  907 + dbt.Error("no data")
  908 + }
  909 + })
  910 +}
  911 +
  912 +func TestUint64(t *testing.T) {
  913 + const (
  914 + u0 = uint64(0)
  915 + uall = ^u0
  916 + uhigh = uall >> 1
  917 + utop = ^uhigh
  918 + s0 = int64(0)
  919 + sall = ^s0
  920 + shigh = int64(uhigh)
  921 + stop = ^shigh
  922 + )
  923 + runTests(t, dsn, func(dbt *DBTest) {
  924 + stmt, err := dbt.db.Prepare(`SELECT ?, ?, ? ,?, ?, ?, ?, ?`)
  925 + if err != nil {
  926 + dbt.Fatal(err)
  927 + }
  928 + defer stmt.Close()
  929 + row := stmt.QueryRow(
  930 + u0, uhigh, utop, uall,
  931 + s0, shigh, stop, sall,
  932 + )
  933 +
  934 + var ua, ub, uc, ud uint64
  935 + var sa, sb, sc, sd int64
  936 +
  937 + err = row.Scan(&ua, &ub, &uc, &ud, &sa, &sb, &sc, &sd)
  938 + if err != nil {
  939 + dbt.Fatal(err)
  940 + }
  941 + switch {
  942 + case ua != u0,
  943 + ub != uhigh,
  944 + uc != utop,
  945 + ud != uall,
  946 + sa != s0,
  947 + sb != shigh,
  948 + sc != stop,
  949 + sd != sall:
  950 + dbt.Fatal("unexpected result value")
  951 + }
  952 + })
  953 +}
  954 +
  955 +func TestLongData(t *testing.T) {
  956 + runTests(t, dsn, func(dbt *DBTest) {
  957 + var maxAllowedPacketSize int
  958 + err := dbt.db.QueryRow("select @@max_allowed_packet").Scan(&maxAllowedPacketSize)
  959 + if err != nil {
  960 + dbt.Fatal(err)
  961 + }
  962 + maxAllowedPacketSize--
  963 +
  964 + // don't get too ambitious
  965 + if maxAllowedPacketSize > 1<<25 {
  966 + maxAllowedPacketSize = 1 << 25
  967 + }
  968 +
  969 + dbt.mustExec("CREATE TABLE test (value LONGBLOB)")
  970 +
  971 + in := strings.Repeat(`a`, maxAllowedPacketSize+1)
  972 + var out string
  973 + var rows *sql.Rows
  974 +
  975 + // Long text data
  976 + const nonDataQueryLen = 28 // length query w/o value
  977 + inS := in[:maxAllowedPacketSize-nonDataQueryLen]
  978 + dbt.mustExec("INSERT INTO test VALUES('" + inS + "')")
  979 + rows = dbt.mustQuery("SELECT value FROM test")
  980 + if rows.Next() {
  981 + rows.Scan(&out)
  982 + if inS != out {
  983 + dbt.Fatalf("LONGBLOB: length in: %d, length out: %d", len(inS), len(out))
  984 + }
  985 + if rows.Next() {
  986 + dbt.Error("LONGBLOB: unexpexted row")
  987 + }
  988 + } else {
  989 + dbt.Fatalf("LONGBLOB: no data")
  990 + }
  991 +
  992 + // Empty table
  993 + dbt.mustExec("TRUNCATE TABLE test")
  994 +
  995 + // Long binary data
  996 + dbt.mustExec("INSERT INTO test VALUES(?)", in)
  997 + rows = dbt.mustQuery("SELECT value FROM test WHERE 1=?", 1)
  998 + if rows.Next() {
  999 + rows.Scan(&out)
  1000 + if in != out {
  1001 + dbt.Fatalf("LONGBLOB: length in: %d, length out: %d", len(in), len(out))
  1002 + }
  1003 + if rows.Next() {
  1004 + dbt.Error("LONGBLOB: unexpexted row")
  1005 + }
  1006 + } else {
  1007 + if err = rows.Err(); err != nil {
  1008 + dbt.Fatalf("LONGBLOB: no data (err: %s)", err.Error())
  1009 + } else {
  1010 + dbt.Fatal("LONGBLOB: no data (err: <nil>)")
  1011 + }
  1012 + }
  1013 + })
  1014 +}
  1015 +
  1016 +func TestLoadData(t *testing.T) {
  1017 + runTests(t, dsn, func(dbt *DBTest) {
  1018 + verifyLoadDataResult := func() {
  1019 + rows, err := dbt.db.Query("SELECT * FROM test")
  1020 + if err != nil {
  1021 + dbt.Fatal(err.Error())
  1022 + }
  1023 +
  1024 + i := 0
  1025 + values := [4]string{
  1026 + "a string",
  1027 + "a string containing a \t",
  1028 + "a string containing a \n",
  1029 + "a string containing both \t\n",
  1030 + }
  1031 +
  1032 + var id int
  1033 + var value string
  1034 +
  1035 + for rows.Next() {
  1036 + i++
  1037 + err = rows.Scan(&id, &value)
  1038 + if err != nil {
  1039 + dbt.Fatal(err.Error())
  1040 + }
  1041 + if i != id {
  1042 + dbt.Fatalf("%d != %d", i, id)
  1043 + }
  1044 + if values[i-1] != value {
  1045 + dbt.Fatalf("%q != %q", values[i-1], value)
  1046 + }
  1047 + }
  1048 + err = rows.Err()
  1049 + if err != nil {
  1050 + dbt.Fatal(err.Error())
  1051 + }
  1052 +
  1053 + if i != 4 {
  1054 + dbt.Fatalf("rows count mismatch. Got %d, want 4", i)
  1055 + }
  1056 + }
  1057 + file, err := ioutil.TempFile("", "gotest")
  1058 + defer os.Remove(file.Name())
  1059 + if err != nil {
  1060 + dbt.Fatal(err)
  1061 + }
  1062 + file.WriteString("1\ta string\n2\ta string containing a \\t\n3\ta string containing a \\n\n4\ta string containing both \\t\\n\n")
  1063 + file.Close()
  1064 +
  1065 + dbt.db.Exec("DROP TABLE IF EXISTS test")
  1066 + dbt.mustExec("CREATE TABLE test (id INT NOT NULL PRIMARY KEY, value TEXT NOT NULL) CHARACTER SET utf8")
  1067 +
  1068 + // Local File
  1069 + RegisterLocalFile(file.Name())
  1070 + dbt.mustExec(fmt.Sprintf("LOAD DATA LOCAL INFILE %q INTO TABLE test", file.Name()))
  1071 + verifyLoadDataResult()
  1072 + // negative test
  1073 + _, err = dbt.db.Exec("LOAD DATA LOCAL INFILE 'doesnotexist' INTO TABLE test")
  1074 + if err == nil {
  1075 + dbt.Fatal("load non-existent file didn't fail")
  1076 + } else if err.Error() != "local file 'doesnotexist' is not registered" {
  1077 + dbt.Fatal(err.Error())
  1078 + }
  1079 +
  1080 + // Empty table
  1081 + dbt.mustExec("TRUNCATE TABLE test")
  1082 +
  1083 + // Reader
  1084 + RegisterReaderHandler("test", func() io.Reader {
  1085 + file, err = os.Open(file.Name())
  1086 + if err != nil {
  1087 + dbt.Fatal(err)
  1088 + }
  1089 + return file
  1090 + })
  1091 + dbt.mustExec("LOAD DATA LOCAL INFILE 'Reader::test' INTO TABLE test")
  1092 + verifyLoadDataResult()
  1093 + // negative test
  1094 + _, err = dbt.db.Exec("LOAD DATA LOCAL INFILE 'Reader::doesnotexist' INTO TABLE test")
  1095 + if err == nil {
  1096 + dbt.Fatal("load non-existent Reader didn't fail")
  1097 + } else if err.Error() != "Reader 'doesnotexist' is not registered" {
  1098 + dbt.Fatal(err.Error())
  1099 + }
  1100 + })
  1101 +}
  1102 +
  1103 +func TestFoundRows(t *testing.T) {
  1104 + runTests(t, dsn, func(dbt *DBTest) {
  1105 + dbt.mustExec("CREATE TABLE test (id INT NOT NULL ,data INT NOT NULL)")
  1106 + dbt.mustExec("INSERT INTO test (id, data) VALUES (0, 0),(0, 0),(1, 0),(1, 0),(1, 1)")
  1107 +
  1108 + res := dbt.mustExec("UPDATE test SET data = 1 WHERE id = 0")
  1109 + count, err := res.RowsAffected()
  1110 + if err != nil {
  1111 + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
  1112 + }
  1113 + if count != 2 {
  1114 + dbt.Fatalf("Expected 2 affected rows, got %d", count)
  1115 + }
  1116 + res = dbt.mustExec("UPDATE test SET data = 1 WHERE id = 1")
  1117 + count, err = res.RowsAffected()
  1118 + if err != nil {
  1119 + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
  1120 + }
  1121 + if count != 2 {
  1122 + dbt.Fatalf("Expected 2 affected rows, got %d", count)
  1123 + }
  1124 + })
  1125 + runTests(t, dsn+"&clientFoundRows=true", func(dbt *DBTest) {
  1126 + dbt.mustExec("CREATE TABLE test (id INT NOT NULL ,data INT NOT NULL)")
  1127 + dbt.mustExec("INSERT INTO test (id, data) VALUES (0, 0),(0, 0),(1, 0),(1, 0),(1, 1)")
  1128 +
  1129 + res := dbt.mustExec("UPDATE test SET data = 1 WHERE id = 0")
  1130 + count, err := res.RowsAffected()
  1131 + if err != nil {
  1132 + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
  1133 + }
  1134 + if count != 2 {
  1135 + dbt.Fatalf("Expected 2 matched rows, got %d", count)
  1136 + }
  1137 + res = dbt.mustExec("UPDATE test SET data = 1 WHERE id = 1")
  1138 + count, err = res.RowsAffected()
  1139 + if err != nil {
  1140 + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
  1141 + }
  1142 + if count != 3 {
  1143 + dbt.Fatalf("Expected 3 matched rows, got %d", count)
  1144 + }
  1145 + })
  1146 +}
  1147 +
  1148 +func TestStrict(t *testing.T) {
  1149 + // ALLOW_INVALID_DATES to get rid of stricter modes - we want to test for warnings, not errors
  1150 + relaxedDsn := dsn + "&sql_mode='ALLOW_INVALID_DATES,NO_AUTO_CREATE_USER'"
  1151 + // make sure the MySQL version is recent enough with a separate connection
  1152 + // before running the test
  1153 + conn, err := MySQLDriver{}.Open(relaxedDsn)
  1154 + if conn != nil {
  1155 + conn.Close()
  1156 + }
  1157 + if me, ok := err.(*MySQLError); ok && me.Number == 1231 {
  1158 + // Error 1231: Variable 'sql_mode' can't be set to the value of 'ALLOW_INVALID_DATES'
  1159 + // => skip test, MySQL server version is too old
  1160 + return
  1161 + }
  1162 + runTests(t, relaxedDsn, func(dbt *DBTest) {
  1163 + dbt.mustExec("CREATE TABLE test (a TINYINT NOT NULL, b CHAR(4))")
  1164 +
  1165 + var queries = [...]struct {
  1166 + in string
  1167 + codes []string
  1168 + }{
  1169 + {"DROP TABLE IF EXISTS no_such_table", []string{"1051"}},
  1170 + {"INSERT INTO test VALUES(10,'mysql'),(NULL,'test'),(300,'Open Source')", []string{"1265", "1048", "1264", "1265"}},
  1171 + }
  1172 + var err error
  1173 +
  1174 + var checkWarnings = func(err error, mode string, idx int) {
  1175 + if err == nil {
  1176 + dbt.Errorf("expected STRICT error on query [%s] %s", mode, queries[idx].in)
  1177 + }
  1178 +
  1179 + if warnings, ok := err.(MySQLWarnings); ok {
  1180 + var codes = make([]string, len(warnings))
  1181 + for i := range warnings {
  1182 + codes[i] = warnings[i].Code
  1183 + }
  1184 + if len(codes) != len(queries[idx].codes) {
  1185 + dbt.Errorf("unexpected STRICT error count on query [%s] %s: Wanted %v, Got %v", mode, queries[idx].in, queries[idx].codes, codes)
  1186 + }
  1187 +
  1188 + for i := range warnings {
  1189 + if codes[i] != queries[idx].codes[i] {
  1190 + dbt.Errorf("unexpected STRICT error codes on query [%s] %s: Wanted %v, Got %v", mode, queries[idx].in, queries[idx].codes, codes)
  1191 + return
  1192 + }
  1193 + }
  1194 +
  1195 + } else {
  1196 + dbt.Errorf("unexpected error on query [%s] %s: %s", mode, queries[idx].in, err.Error())
  1197 + }
  1198 + }
  1199 +
  1200 + // text protocol
  1201 + for i := range queries {
  1202 + _, err = dbt.db.Exec(queries[i].in)
  1203 + checkWarnings(err, "text", i)
  1204 + }
  1205 +
  1206 + var stmt *sql.Stmt
  1207 +
  1208 + // binary protocol
  1209 + for i := range queries {
  1210 + stmt, err = dbt.db.Prepare(queries[i].in)
  1211 + if err != nil {
  1212 + dbt.Errorf("error on preparing query %s: %s", queries[i].in, err.Error())
  1213 + }
  1214 +
  1215 + _, err = stmt.Exec()
  1216 + checkWarnings(err, "binary", i)
  1217 +
  1218 + err = stmt.Close()
  1219 + if err != nil {
  1220 + dbt.Errorf("error on closing stmt for query %s: %s", queries[i].in, err.Error())
  1221 + }
  1222 + }
  1223 + })
  1224 +}
  1225 +
  1226 +func TestTLS(t *testing.T) {
  1227 + tlsTest := func(dbt *DBTest) {
  1228 + if err := dbt.db.Ping(); err != nil {
  1229 + if err == ErrNoTLS {
  1230 + dbt.Skip("server does not support TLS")
  1231 + } else {
  1232 + dbt.Fatalf("error on Ping: %s", err.Error())
  1233 + }
  1234 + }
  1235 +
  1236 + rows := dbt.mustQuery("SHOW STATUS LIKE 'Ssl_cipher'")
  1237 +
  1238 + var variable, value *sql.RawBytes
  1239 + for rows.Next() {
  1240 + if err := rows.Scan(&variable, &value); err != nil {
  1241 + dbt.Fatal(err.Error())
  1242 + }
  1243 +
  1244 + if value == nil {
  1245 + dbt.Fatal("no Cipher")
  1246 + }
  1247 + }
  1248 + }
  1249 +
  1250 + runTests(t, dsn+"&tls=skip-verify", tlsTest)
  1251 +
  1252 + // Verify that registering / using a custom cfg works
  1253 + RegisterTLSConfig("custom-skip-verify", &tls.Config{
  1254 + InsecureSkipVerify: true,
  1255 + })
  1256 + runTests(t, dsn+"&tls=custom-skip-verify", tlsTest)
  1257 +}
  1258 +
  1259 +func TestReuseClosedConnection(t *testing.T) {
  1260 + // this test does not use sql.database, it uses the driver directly
  1261 + if !available {
  1262 + t.Skipf("MySQL server not running on %s", netAddr)
  1263 + }
  1264 +
  1265 + md := &MySQLDriver{}
  1266 + conn, err := md.Open(dsn)
  1267 + if err != nil {
  1268 + t.Fatalf("error connecting: %s", err.Error())
  1269 + }
  1270 + stmt, err := conn.Prepare("DO 1")
  1271 + if err != nil {
  1272 + t.Fatalf("error preparing statement: %s", err.Error())
  1273 + }
  1274 + _, err = stmt.Exec(nil)
  1275 + if err != nil {
  1276 + t.Fatalf("error executing statement: %s", err.Error())
  1277 + }
  1278 + err = conn.Close()
  1279 + if err != nil {
  1280 + t.Fatalf("error closing connection: %s", err.Error())
  1281 + }
  1282 +
  1283 + defer func() {
  1284 + if err := recover(); err != nil {
  1285 + t.Errorf("panic after reusing a closed connection: %v", err)
  1286 + }
  1287 + }()
  1288 + _, err = stmt.Exec(nil)
  1289 + if err != nil && err != driver.ErrBadConn {
  1290 + t.Errorf("unexpected error '%s', expected '%s'",
  1291 + err.Error(), driver.ErrBadConn.Error())
  1292 + }
  1293 +}
  1294 +
  1295 +func TestCharset(t *testing.T) {
  1296 + if !available {
  1297 + t.Skipf("MySQL server not running on %s", netAddr)
  1298 + }
  1299 +
  1300 + mustSetCharset := func(charsetParam, expected string) {
  1301 + runTests(t, dsn+"&"+charsetParam, func(dbt *DBTest) {
  1302 + rows := dbt.mustQuery("SELECT @@character_set_connection")
  1303 + defer rows.Close()
  1304 +
  1305 + if !rows.Next() {
  1306 + dbt.Fatalf("error getting connection charset: %s", rows.Err())
  1307 + }
  1308 +
  1309 + var got string
  1310 + rows.Scan(&got)
  1311 +
  1312 + if got != expected {
  1313 + dbt.Fatalf("expected connection charset %s but got %s", expected, got)
  1314 + }
  1315 + })
  1316 + }
  1317 +
  1318 + // non utf8 test
  1319 + mustSetCharset("charset=ascii", "ascii")
  1320 +
  1321 + // when the first charset is invalid, use the second
  1322 + mustSetCharset("charset=none,utf8", "utf8")
  1323 +
  1324 + // when the first charset is valid, use it
  1325 + mustSetCharset("charset=ascii,utf8", "ascii")
  1326 + mustSetCharset("charset=utf8,ascii", "utf8")
  1327 +}
  1328 +
  1329 +func TestFailingCharset(t *testing.T) {
  1330 + runTests(t, dsn+"&charset=none", func(dbt *DBTest) {
  1331 + // run query to really establish connection...
  1332 + _, err := dbt.db.Exec("SELECT 1")
  1333 + if err == nil {
  1334 + dbt.db.Close()
  1335 + t.Fatalf("connection must not succeed without a valid charset")
  1336 + }
  1337 + })
  1338 +}
  1339 +
  1340 +func TestCollation(t *testing.T) {
  1341 + if !available {
  1342 + t.Skipf("MySQL server not running on %s", netAddr)
  1343 + }
  1344 +
  1345 + defaultCollation := "utf8_general_ci"
  1346 + testCollations := []string{
  1347 + "", // do not set
  1348 + defaultCollation, // driver default
  1349 + "latin1_general_ci",
  1350 + "binary",
  1351 + "utf8_unicode_ci",
  1352 + "cp1257_bin",
  1353 + }
  1354 +
  1355 + for _, collation := range testCollations {
  1356 + var expected, tdsn string
  1357 + if collation != "" {
  1358 + tdsn = dsn + "&collation=" + collation
  1359 + expected = collation
  1360 + } else {
  1361 + tdsn = dsn
  1362 + expected = defaultCollation
  1363 + }
  1364 +
  1365 + runTests(t, tdsn, func(dbt *DBTest) {
  1366 + var got string
  1367 + if err := dbt.db.QueryRow("SELECT @@collation_connection").Scan(&got); err != nil {
  1368 + dbt.Fatal(err)
  1369 + }
  1370 +
  1371 + if got != expected {
  1372 + dbt.Fatalf("expected connection collation %s but got %s", expected, got)
  1373 + }
  1374 + })
  1375 + }
  1376 +}
  1377 +
  1378 +func TestColumnsWithAlias(t *testing.T) {
  1379 + runTests(t, dsn+"&columnsWithAlias=true", func(dbt *DBTest) {
  1380 + rows := dbt.mustQuery("SELECT 1 AS A")
  1381 + defer rows.Close()
  1382 + cols, _ := rows.Columns()
  1383 + if len(cols) != 1 {
  1384 + t.Fatalf("expected 1 column, got %d", len(cols))
  1385 + }
  1386 + if cols[0] != "A" {
  1387 + t.Fatalf("expected column name \"A\", got \"%s\"", cols[0])
  1388 + }
  1389 + rows.Close()
  1390 +
  1391 + rows = dbt.mustQuery("SELECT * FROM (SELECT 1 AS one) AS A")
  1392 + cols, _ = rows.Columns()
  1393 + if len(cols) != 1 {
  1394 + t.Fatalf("expected 1 column, got %d", len(cols))
  1395 + }
  1396 + if cols[0] != "A.one" {
  1397 + t.Fatalf("expected column name \"A.one\", got \"%s\"", cols[0])
  1398 + }
  1399 + })
  1400 +}
  1401 +
  1402 +func TestRawBytesResultExceedsBuffer(t *testing.T) {
  1403 + runTests(t, dsn, func(dbt *DBTest) {
  1404 + // defaultBufSize from buffer.go
  1405 + expected := strings.Repeat("abc", defaultBufSize)
  1406 +
  1407 + rows := dbt.mustQuery("SELECT '" + expected + "'")
  1408 + defer rows.Close()
  1409 + if !rows.Next() {
  1410 + dbt.Error("expected result, got none")
  1411 + }
  1412 + var result sql.RawBytes
  1413 + rows.Scan(&result)
  1414 + if expected != string(result) {
  1415 + dbt.Error("result did not match expected value")
  1416 + }
  1417 + })
  1418 +}
  1419 +
  1420 +func TestTimezoneConversion(t *testing.T) {
  1421 + zones := []string{"UTC", "US/Central", "US/Pacific", "Local"}
  1422 +
  1423 + // Regression test for timezone handling
  1424 + tzTest := func(dbt *DBTest) {
  1425 +
  1426 + // Create table
  1427 + dbt.mustExec("CREATE TABLE test (ts TIMESTAMP)")
  1428 +
  1429 + // Insert local time into database (should be converted)
  1430 + usCentral, _ := time.LoadLocation("US/Central")
  1431 + reftime := time.Date(2014, 05, 30, 18, 03, 17, 0, time.UTC).In(usCentral)
  1432 + dbt.mustExec("INSERT INTO test VALUE (?)", reftime)
  1433 +
  1434 + // Retrieve time from DB
  1435 + rows := dbt.mustQuery("SELECT ts FROM test")
  1436 + if !rows.Next() {
  1437 + dbt.Fatal("did not get any rows out")
  1438 + }
  1439 +
  1440 + var dbTime time.Time
  1441 + err := rows.Scan(&dbTime)
  1442 + if err != nil {
  1443 + dbt.Fatal("Err", err)
  1444 + }
  1445 +
  1446 + // Check that dates match
  1447 + if reftime.Unix() != dbTime.Unix() {
  1448 + dbt.Errorf("times do not match.\n")
  1449 + dbt.Errorf(" Now(%v)=%v\n", usCentral, reftime)
  1450 + dbt.Errorf(" Now(UTC)=%v\n", dbTime)
  1451 + }
  1452 + }
  1453 +
  1454 + for _, tz := range zones {
  1455 + runTests(t, dsn+"&parseTime=true&loc="+url.QueryEscape(tz), tzTest)
  1456 + }
  1457 +}
  1458 +
  1459 +// Special cases
  1460 +
  1461 +func TestRowsClose(t *testing.T) {
  1462 + runTests(t, dsn, func(dbt *DBTest) {
  1463 + rows, err := dbt.db.Query("SELECT 1")
  1464 + if err != nil {
  1465 + dbt.Fatal(err)
  1466 + }
  1467 +
  1468 + err = rows.Close()
  1469 + if err != nil {
  1470 + dbt.Fatal(err)
  1471 + }
  1472 +
  1473 + if rows.Next() {
  1474 + dbt.Fatal("unexpected row after rows.Close()")
  1475 + }
  1476 +
  1477 + err = rows.Err()
  1478 + if err != nil {
  1479 + dbt.Fatal(err)
  1480 + }
  1481 + })
  1482 +}
  1483 +
  1484 +// dangling statements
  1485 +// http://code.google.com/p/go/issues/detail?id=3865
  1486 +func TestCloseStmtBeforeRows(t *testing.T) {
  1487 + runTests(t, dsn, func(dbt *DBTest) {
  1488 + stmt, err := dbt.db.Prepare("SELECT 1")
  1489 + if err != nil {
  1490 + dbt.Fatal(err)
  1491 + }
  1492 +
  1493 + rows, err := stmt.Query()
  1494 + if err != nil {
  1495 + stmt.Close()
  1496 + dbt.Fatal(err)
  1497 + }
  1498 + defer rows.Close()
  1499 +
  1500 + err = stmt.Close()
  1501 + if err != nil {
  1502 + dbt.Fatal(err)
  1503 + }
  1504 +
  1505 + if !rows.Next() {
  1506 + dbt.Fatal("getting row failed")
  1507 + } else {
  1508 + err = rows.Err()
  1509 + if err != nil {
  1510 + dbt.Fatal(err)
  1511 + }
  1512 +
  1513 + var out bool
  1514 + err = rows.Scan(&out)
  1515 + if err != nil {
  1516 + dbt.Fatalf("error on rows.Scan(): %s", err.Error())
  1517 + }
  1518 + if out != true {
  1519 + dbt.Errorf("true != %t", out)
  1520 + }
  1521 + }
  1522 + })
  1523 +}
  1524 +
  1525 +// It is valid to have multiple Rows for the same Stmt
  1526 +// http://code.google.com/p/go/issues/detail?id=3734
  1527 +func TestStmtMultiRows(t *testing.T) {
  1528 + runTests(t, dsn, func(dbt *DBTest) {
  1529 + stmt, err := dbt.db.Prepare("SELECT 1 UNION SELECT 0")
  1530 + if err != nil {
  1531 + dbt.Fatal(err)
  1532 + }
  1533 +
  1534 + rows1, err := stmt.Query()
  1535 + if err != nil {
  1536 + stmt.Close()
  1537 + dbt.Fatal(err)
  1538 + }
  1539 + defer rows1.Close()
  1540 +
  1541 + rows2, err := stmt.Query()
  1542 + if err != nil {
  1543 + stmt.Close()
  1544 + dbt.Fatal(err)
  1545 + }
  1546 + defer rows2.Close()
  1547 +
  1548 + var out bool
  1549 +
  1550 + // 1
  1551 + if !rows1.Next() {
  1552 + dbt.Fatal("first rows1.Next failed")
  1553 + } else {
  1554 + err = rows1.Err()
  1555 + if err != nil {
  1556 + dbt.Fatal(err)
  1557 + }
  1558 +
  1559 + err = rows1.Scan(&out)
  1560 + if err != nil {
  1561 + dbt.Fatalf("error on rows.Scan(): %s", err.Error())
  1562 + }
  1563 + if out != true {
  1564 + dbt.Errorf("true != %t", out)
  1565 + }
  1566 + }
  1567 +
  1568 + if !rows2.Next() {
  1569 + dbt.Fatal("first rows2.Next failed")
  1570 + } else {
  1571 + err = rows2.Err()
  1572 + if err != nil {
  1573 + dbt.Fatal(err)
  1574 + }
  1575 +
  1576 + err = rows2.Scan(&out)
  1577 + if err != nil {
  1578 + dbt.Fatalf("error on rows.Scan(): %s", err.Error())
  1579 + }
  1580 + if out != true {
  1581 + dbt.Errorf("true != %t", out)
  1582 + }
  1583 + }
  1584 +
  1585 + // 2
  1586 + if !rows1.Next() {
  1587 + dbt.Fatal("second rows1.Next failed")
  1588 + } else {
  1589 + err = rows1.Err()
  1590 + if err != nil {
  1591 + dbt.Fatal(err)
  1592 + }
  1593 +
  1594 + err = rows1.Scan(&out)
  1595 + if err != nil {
  1596 + dbt.Fatalf("error on rows.Scan(): %s", err.Error())
  1597 + }
  1598 + if out != false {
  1599 + dbt.Errorf("false != %t", out)
  1600 + }
  1601 +
  1602 + if rows1.Next() {
  1603 + dbt.Fatal("unexpected row on rows1")
  1604 + }
  1605 + err = rows1.Close()
  1606 + if err != nil {
  1607 + dbt.Fatal(err)
  1608 + }
  1609 + }
  1610 +
  1611 + if !rows2.Next() {
  1612 + dbt.Fatal("second rows2.Next failed")
  1613 + } else {
  1614 + err = rows2.Err()
  1615 + if err != nil {
  1616 + dbt.Fatal(err)
  1617 + }
  1618 +
  1619 + err = rows2.Scan(&out)
  1620 + if err != nil {
  1621 + dbt.Fatalf("error on rows.Scan(): %s", err.Error())
  1622 + }
  1623 + if out != false {
  1624 + dbt.Errorf("false != %t", out)
  1625 + }
  1626 +
  1627 + if rows2.Next() {
  1628 + dbt.Fatal("unexpected row on rows2")
  1629 + }
  1630 + err = rows2.Close()
  1631 + if err != nil {
  1632 + dbt.Fatal(err)
  1633 + }
  1634 + }
  1635 + })
  1636 +}
  1637 +
  1638 +// Regression test for
  1639 +// * more than 32 NULL parameters (issue 209)
  1640 +// * more parameters than fit into the buffer (issue 201)
  1641 +func TestPreparedManyCols(t *testing.T) {
  1642 + const numParams = defaultBufSize
  1643 + runTests(t, dsn, func(dbt *DBTest) {
  1644 + query := "SELECT ?" + strings.Repeat(",?", numParams-1)
  1645 + stmt, err := dbt.db.Prepare(query)
  1646 + if err != nil {
  1647 + dbt.Fatal(err)
  1648 + }
  1649 + defer stmt.Close()
  1650 + // create more parameters than fit into the buffer
  1651 + // which will take nil-values
  1652 + params := make([]interface{}, numParams)
  1653 + rows, err := stmt.Query(params...)
  1654 + if err != nil {
  1655 + stmt.Close()
  1656 + dbt.Fatal(err)
  1657 + }
  1658 + defer rows.Close()
  1659 + })
  1660 +}
  1661 +
  1662 +func TestConcurrent(t *testing.T) {
  1663 + if enabled, _ := readBool(os.Getenv("MYSQL_TEST_CONCURRENT")); !enabled {
  1664 + t.Skip("MYSQL_TEST_CONCURRENT env var not set")
  1665 + }
  1666 +
  1667 + runTests(t, dsn, func(dbt *DBTest) {
  1668 + var max int
  1669 + err := dbt.db.QueryRow("SELECT @@max_connections").Scan(&max)
  1670 + if err != nil {
  1671 + dbt.Fatalf("%s", err.Error())
  1672 + }
  1673 + dbt.Logf("testing up to %d concurrent connections \r\n", max)
  1674 +
  1675 + var remaining, succeeded int32 = int32(max), 0
  1676 +
  1677 + var wg sync.WaitGroup
  1678 + wg.Add(max)
  1679 +
  1680 + var fatalError string
  1681 + var once sync.Once
  1682 + fatalf := func(s string, vals ...interface{}) {
  1683 + once.Do(func() {
  1684 + fatalError = fmt.Sprintf(s, vals...)
  1685 + })
  1686 + }
  1687 +
  1688 + for i := 0; i < max; i++ {
  1689 + go func(id int) {
  1690 + defer wg.Done()
  1691 +
  1692 + tx, err := dbt.db.Begin()
  1693 + atomic.AddInt32(&remaining, -1)
  1694 +
  1695 + if err != nil {
  1696 + if err.Error() != "Error 1040: Too many connections" {
  1697 + fatalf("error on conn %d: %s", id, err.Error())
  1698 + }
  1699 + return
  1700 + }
  1701 +
  1702 + // keep the connection busy until all connections are open
  1703 + for remaining > 0 {
  1704 + if _, err = tx.Exec("DO 1"); err != nil {
  1705 + fatalf("error on conn %d: %s", id, err.Error())
  1706 + return
  1707 + }
  1708 + }
  1709 +
  1710 + if err = tx.Commit(); err != nil {
  1711 + fatalf("error on conn %d: %s", id, err.Error())
  1712 + return
  1713 + }
  1714 +
  1715 + // everything went fine with this connection
  1716 + atomic.AddInt32(&succeeded, 1)
  1717 + }(i)
  1718 + }
  1719 +
  1720 + // wait until all conections are open
  1721 + wg.Wait()
  1722 +
  1723 + if fatalError != "" {
  1724 + dbt.Fatal(fatalError)
  1725 + }
  1726 +
  1727 + dbt.Logf("reached %d concurrent connections\r\n", succeeded)
  1728 + })
  1729 +}
  1730 +
  1731 +// Tests custom dial functions
  1732 +func TestCustomDial(t *testing.T) {
  1733 + if !available {
  1734 + t.Skipf("MySQL server not running on %s", netAddr)
  1735 + }
  1736 +
  1737 + // our custom dial function which justs wraps net.Dial here
  1738 + RegisterDial("mydial", func(addr string) (net.Conn, error) {
  1739 + return net.Dial(prot, addr)
  1740 + })
  1741 +
  1742 + db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@mydial(%s)/%s?timeout=30s&strict=true", user, pass, addr, dbname))
  1743 + if err != nil {
  1744 + t.Fatalf("error connecting: %s", err.Error())
  1745 + }
  1746 + defer db.Close()
  1747 +
  1748 + if _, err = db.Exec("DO 1"); err != nil {
  1749 + t.Fatalf("connection failed: %s", err.Error())
  1750 + }
  1751 +}
  1752 +
  1753 +func TestSQLInjection(t *testing.T) {
  1754 + createTest := func(arg string) func(dbt *DBTest) {
  1755 + return func(dbt *DBTest) {
  1756 + dbt.mustExec("CREATE TABLE test (v INTEGER)")
  1757 + dbt.mustExec("INSERT INTO test VALUES (?)", 1)
  1758 +
  1759 + var v int
  1760 + // NULL can't be equal to anything, the idea here is to inject query so it returns row
  1761 + // This test verifies that escapeQuotes and escapeBackslash are working properly
  1762 + err := dbt.db.QueryRow("SELECT v FROM test WHERE NULL = ?", arg).Scan(&v)
  1763 + if err == sql.ErrNoRows {
  1764 + return // success, sql injection failed
  1765 + } else if err == nil {
  1766 + dbt.Errorf("sql injection successful with arg: %s", arg)
  1767 + } else {
  1768 + dbt.Errorf("error running query with arg: %s; err: %s", arg, err.Error())
  1769 + }
  1770 + }
  1771 + }
  1772 +
  1773 + dsns := []string{
  1774 + dsn,
  1775 + dsn + "&sql_mode='NO_BACKSLASH_ESCAPES,NO_AUTO_CREATE_USER'",
  1776 + }
  1777 + for _, testdsn := range dsns {
  1778 + runTests(t, testdsn, createTest("1 OR 1=1"))
  1779 + runTests(t, testdsn, createTest("' OR '1'='1"))
  1780 + }
  1781 +}
  1782 +
  1783 +// Test if inserted data is correctly retrieved after being escaped
  1784 +func TestInsertRetrieveEscapedData(t *testing.T) {
  1785 + testData := func(dbt *DBTest) {
  1786 + dbt.mustExec("CREATE TABLE test (v VARCHAR(255))")
  1787 +
  1788 + // All sequences that are escaped by escapeQuotes and escapeBackslash
  1789 + v := "foo \x00\n\r\x1a\"'\\"
  1790 + dbt.mustExec("INSERT INTO test VALUES (?)", v)
  1791 +
  1792 + var out string
  1793 + err := dbt.db.QueryRow("SELECT v FROM test").Scan(&out)
  1794 + if err != nil {
  1795 + dbt.Fatalf("%s", err.Error())
  1796 + }
  1797 +
  1798 + if out != v {
  1799 + dbt.Errorf("%q != %q", out, v)
  1800 + }
  1801 + }
  1802 +
  1803 + dsns := []string{
  1804 + dsn,
  1805 + dsn + "&sql_mode='NO_BACKSLASH_ESCAPES,NO_AUTO_CREATE_USER'",
  1806 + }
  1807 + for _, testdsn := range dsns {
  1808 + runTests(t, testdsn, testData)
  1809 + }
  1810 +}
  1811 +
  1812 +func TestUnixSocketAuthFail(t *testing.T) {
  1813 + runTests(t, dsn, func(dbt *DBTest) {
  1814 + // Save the current logger so we can restore it.
  1815 + oldLogger := errLog
  1816 +
  1817 + // Set a new logger so we can capture its output.
  1818 + buffer := bytes.NewBuffer(make([]byte, 0, 64))
  1819 + newLogger := log.New(buffer, "prefix: ", 0)
  1820 + SetLogger(newLogger)
  1821 +
  1822 + // Restore the logger.
  1823 + defer SetLogger(oldLogger)
  1824 +
  1825 + // Make a new DSN that uses the MySQL socket file and a bad password, which
  1826 + // we can make by simply appending any character to the real password.
  1827 + badPass := pass + "x"
  1828 + socket := ""
  1829 + if prot == "unix" {
  1830 + socket = addr
  1831 + } else {
  1832 + // Get socket file from MySQL.
  1833 + err := dbt.db.QueryRow("SELECT @@socket").Scan(&socket)
  1834 + if err != nil {
  1835 + t.Fatalf("error on SELECT @@socket: %s", err.Error())
  1836 + }
  1837 + }
  1838 + t.Logf("socket: %s", socket)
  1839 + badDSN := fmt.Sprintf("%s:%s@unix(%s)/%s?timeout=30s&strict=true", user, badPass, socket, dbname)
  1840 + db, err := sql.Open("mysql", badDSN)
  1841 + if err != nil {
  1842 + t.Fatalf("error connecting: %s", err.Error())
  1843 + }
  1844 + defer db.Close()
  1845 +
  1846 + // Connect to MySQL for real. This will cause an auth failure.
  1847 + err = db.Ping()
  1848 + if err == nil {
  1849 + t.Error("expected Ping() to return an error")
  1850 + }
  1851 +
  1852 + // The driver should not log anything.
  1853 + if actual := buffer.String(); actual != "" {
  1854 + t.Errorf("expected no output, got %q", actual)
  1855 + }
  1856 + })
  1857 +}
  1858 +
  1859 +// See Issue #422
  1860 +func TestInterruptBySignal(t *testing.T) {
  1861 + runTestsWithMultiStatement(t, dsn, func(dbt *DBTest) {
  1862 + dbt.mustExec(`
  1863 + DROP PROCEDURE IF EXISTS test_signal;
  1864 + CREATE PROCEDURE test_signal(ret INT)
  1865 + BEGIN
  1866 + SELECT ret;
  1867 + SIGNAL SQLSTATE
  1868 + '45001'
  1869 + SET
  1870 + MESSAGE_TEXT = "an error",
  1871 + MYSQL_ERRNO = 45001;
  1872 + END
  1873 + `)
  1874 + defer dbt.mustExec("DROP PROCEDURE test_signal")
  1875 +
  1876 + var val int
  1877 +
  1878 + // text protocol
  1879 + rows, err := dbt.db.Query("CALL test_signal(42)")
  1880 + if err != nil {
  1881 + dbt.Fatalf("error on text query: %s", err.Error())
  1882 + }
  1883 + for rows.Next() {
  1884 + if err := rows.Scan(&val); err != nil {
  1885 + dbt.Error(err)
  1886 + } else if val != 42 {
  1887 + dbt.Errorf("expected val to be 42")
  1888 + }
  1889 + }
  1890 +
  1891 + // binary protocol
  1892 + rows, err = dbt.db.Query("CALL test_signal(?)", 42)
  1893 + if err != nil {
  1894 + dbt.Fatalf("error on binary query: %s", err.Error())
  1895 + }
  1896 + for rows.Next() {
  1897 + if err := rows.Scan(&val); err != nil {
  1898 + dbt.Error(err)
  1899 + } else if val != 42 {
  1900 + dbt.Errorf("expected val to be 42")
  1901 + }
  1902 + }
  1903 + })
  1904 +}
... ...
src/github.com/go-sql-driver/mysql/dsn.go 0 → 100644
... ... @@ -0,0 +1,548 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2016 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +import (
  12 + "bytes"
  13 + "crypto/tls"
  14 + "errors"
  15 + "fmt"
  16 + "net"
  17 + "net/url"
  18 + "strconv"
  19 + "strings"
  20 + "time"
  21 +)
  22 +
  23 +var (
  24 + errInvalidDSNUnescaped = errors.New("invalid DSN: did you forget to escape a param value?")
  25 + errInvalidDSNAddr = errors.New("invalid DSN: network address not terminated (missing closing brace)")
  26 + errInvalidDSNNoSlash = errors.New("invalid DSN: missing the slash separating the database name")
  27 + errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations")
  28 +)
  29 +
  30 +// Config is a configuration parsed from a DSN string
  31 +type Config struct {
  32 + User string // Username
  33 + Passwd string // Password (requires User)
  34 + Net string // Network type
  35 + Addr string // Network address (requires Net)
  36 + DBName string // Database name
  37 + Params map[string]string // Connection parameters
  38 + Collation string // Connection collation
  39 + Loc *time.Location // Location for time.Time values
  40 + MaxAllowedPacket int // Max packet size allowed
  41 + TLSConfig string // TLS configuration name
  42 + tls *tls.Config // TLS configuration
  43 + Timeout time.Duration // Dial timeout
  44 + ReadTimeout time.Duration // I/O read timeout
  45 + WriteTimeout time.Duration // I/O write timeout
  46 +
  47 + AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
  48 + AllowCleartextPasswords bool // Allows the cleartext client side plugin
  49 + AllowNativePasswords bool // Allows the native password authentication method
  50 + AllowOldPasswords bool // Allows the old insecure password method
  51 + ClientFoundRows bool // Return number of matching rows instead of rows changed
  52 + ColumnsWithAlias bool // Prepend table alias to column names
  53 + InterpolateParams bool // Interpolate placeholders into query string
  54 + MultiStatements bool // Allow multiple statements in one query
  55 + ParseTime bool // Parse time values to time.Time
  56 + Strict bool // Return warnings as errors
  57 +}
  58 +
  59 +// FormatDSN formats the given Config into a DSN string which can be passed to
  60 +// the driver.
  61 +func (cfg *Config) FormatDSN() string {
  62 + var buf bytes.Buffer
  63 +
  64 + // [username[:password]@]
  65 + if len(cfg.User) > 0 {
  66 + buf.WriteString(cfg.User)
  67 + if len(cfg.Passwd) > 0 {
  68 + buf.WriteByte(':')
  69 + buf.WriteString(cfg.Passwd)
  70 + }
  71 + buf.WriteByte('@')
  72 + }
  73 +
  74 + // [protocol[(address)]]
  75 + if len(cfg.Net) > 0 {
  76 + buf.WriteString(cfg.Net)
  77 + if len(cfg.Addr) > 0 {
  78 + buf.WriteByte('(')
  79 + buf.WriteString(cfg.Addr)
  80 + buf.WriteByte(')')
  81 + }
  82 + }
  83 +
  84 + // /dbname
  85 + buf.WriteByte('/')
  86 + buf.WriteString(cfg.DBName)
  87 +
  88 + // [?param1=value1&...&paramN=valueN]
  89 + hasParam := false
  90 +
  91 + if cfg.AllowAllFiles {
  92 + hasParam = true
  93 + buf.WriteString("?allowAllFiles=true")
  94 + }
  95 +
  96 + if cfg.AllowCleartextPasswords {
  97 + if hasParam {
  98 + buf.WriteString("&allowCleartextPasswords=true")
  99 + } else {
  100 + hasParam = true
  101 + buf.WriteString("?allowCleartextPasswords=true")
  102 + }
  103 + }
  104 +
  105 + if cfg.AllowNativePasswords {
  106 + if hasParam {
  107 + buf.WriteString("&allowNativePasswords=true")
  108 + } else {
  109 + hasParam = true
  110 + buf.WriteString("?allowNativePasswords=true")
  111 + }
  112 + }
  113 +
  114 + if cfg.AllowOldPasswords {
  115 + if hasParam {
  116 + buf.WriteString("&allowOldPasswords=true")
  117 + } else {
  118 + hasParam = true
  119 + buf.WriteString("?allowOldPasswords=true")
  120 + }
  121 + }
  122 +
  123 + if cfg.ClientFoundRows {
  124 + if hasParam {
  125 + buf.WriteString("&clientFoundRows=true")
  126 + } else {
  127 + hasParam = true
  128 + buf.WriteString("?clientFoundRows=true")
  129 + }
  130 + }
  131 +
  132 + if col := cfg.Collation; col != defaultCollation && len(col) > 0 {
  133 + if hasParam {
  134 + buf.WriteString("&collation=")
  135 + } else {
  136 + hasParam = true
  137 + buf.WriteString("?collation=")
  138 + }
  139 + buf.WriteString(col)
  140 + }
  141 +
  142 + if cfg.ColumnsWithAlias {
  143 + if hasParam {
  144 + buf.WriteString("&columnsWithAlias=true")
  145 + } else {
  146 + hasParam = true
  147 + buf.WriteString("?columnsWithAlias=true")
  148 + }
  149 + }
  150 +
  151 + if cfg.InterpolateParams {
  152 + if hasParam {
  153 + buf.WriteString("&interpolateParams=true")
  154 + } else {
  155 + hasParam = true
  156 + buf.WriteString("?interpolateParams=true")
  157 + }
  158 + }
  159 +
  160 + if cfg.Loc != time.UTC && cfg.Loc != nil {
  161 + if hasParam {
  162 + buf.WriteString("&loc=")
  163 + } else {
  164 + hasParam = true
  165 + buf.WriteString("?loc=")
  166 + }
  167 + buf.WriteString(url.QueryEscape(cfg.Loc.String()))
  168 + }
  169 +
  170 + if cfg.MultiStatements {
  171 + if hasParam {
  172 + buf.WriteString("&multiStatements=true")
  173 + } else {
  174 + hasParam = true
  175 + buf.WriteString("?multiStatements=true")
  176 + }
  177 + }
  178 +
  179 + if cfg.ParseTime {
  180 + if hasParam {
  181 + buf.WriteString("&parseTime=true")
  182 + } else {
  183 + hasParam = true
  184 + buf.WriteString("?parseTime=true")
  185 + }
  186 + }
  187 +
  188 + if cfg.ReadTimeout > 0 {
  189 + if hasParam {
  190 + buf.WriteString("&readTimeout=")
  191 + } else {
  192 + hasParam = true
  193 + buf.WriteString("?readTimeout=")
  194 + }
  195 + buf.WriteString(cfg.ReadTimeout.String())
  196 + }
  197 +
  198 + if cfg.Strict {
  199 + if hasParam {
  200 + buf.WriteString("&strict=true")
  201 + } else {
  202 + hasParam = true
  203 + buf.WriteString("?strict=true")
  204 + }
  205 + }
  206 +
  207 + if cfg.Timeout > 0 {
  208 + if hasParam {
  209 + buf.WriteString("&timeout=")
  210 + } else {
  211 + hasParam = true
  212 + buf.WriteString("?timeout=")
  213 + }
  214 + buf.WriteString(cfg.Timeout.String())
  215 + }
  216 +
  217 + if len(cfg.TLSConfig) > 0 {
  218 + if hasParam {
  219 + buf.WriteString("&tls=")
  220 + } else {
  221 + hasParam = true
  222 + buf.WriteString("?tls=")
  223 + }
  224 + buf.WriteString(url.QueryEscape(cfg.TLSConfig))
  225 + }
  226 +
  227 + if cfg.WriteTimeout > 0 {
  228 + if hasParam {
  229 + buf.WriteString("&writeTimeout=")
  230 + } else {
  231 + hasParam = true
  232 + buf.WriteString("?writeTimeout=")
  233 + }
  234 + buf.WriteString(cfg.WriteTimeout.String())
  235 + }
  236 +
  237 + if cfg.MaxAllowedPacket > 0 {
  238 + if hasParam {
  239 + buf.WriteString("&maxAllowedPacket=")
  240 + } else {
  241 + hasParam = true
  242 + buf.WriteString("?maxAllowedPacket=")
  243 + }
  244 + buf.WriteString(strconv.Itoa(cfg.MaxAllowedPacket))
  245 +
  246 + }
  247 +
  248 + // other params
  249 + if cfg.Params != nil {
  250 + for param, value := range cfg.Params {
  251 + if hasParam {
  252 + buf.WriteByte('&')
  253 + } else {
  254 + hasParam = true
  255 + buf.WriteByte('?')
  256 + }
  257 +
  258 + buf.WriteString(param)
  259 + buf.WriteByte('=')
  260 + buf.WriteString(url.QueryEscape(value))
  261 + }
  262 + }
  263 +
  264 + return buf.String()
  265 +}
  266 +
  267 +// ParseDSN parses the DSN string to a Config
  268 +func ParseDSN(dsn string) (cfg *Config, err error) {
  269 + // New config with some default values
  270 + cfg = &Config{
  271 + Loc: time.UTC,
  272 + Collation: defaultCollation,
  273 + }
  274 +
  275 + // [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
  276 + // Find the last '/' (since the password or the net addr might contain a '/')
  277 + foundSlash := false
  278 + for i := len(dsn) - 1; i >= 0; i-- {
  279 + if dsn[i] == '/' {
  280 + foundSlash = true
  281 + var j, k int
  282 +
  283 + // left part is empty if i <= 0
  284 + if i > 0 {
  285 + // [username[:password]@][protocol[(address)]]
  286 + // Find the last '@' in dsn[:i]
  287 + for j = i; j >= 0; j-- {
  288 + if dsn[j] == '@' {
  289 + // username[:password]
  290 + // Find the first ':' in dsn[:j]
  291 + for k = 0; k < j; k++ {
  292 + if dsn[k] == ':' {
  293 + cfg.Passwd = dsn[k+1 : j]
  294 + break
  295 + }
  296 + }
  297 + cfg.User = dsn[:k]
  298 +
  299 + break
  300 + }
  301 + }
  302 +
  303 + // [protocol[(address)]]
  304 + // Find the first '(' in dsn[j+1:i]
  305 + for k = j + 1; k < i; k++ {
  306 + if dsn[k] == '(' {
  307 + // dsn[i-1] must be == ')' if an address is specified
  308 + if dsn[i-1] != ')' {
  309 + if strings.ContainsRune(dsn[k+1:i], ')') {
  310 + return nil, errInvalidDSNUnescaped
  311 + }
  312 + return nil, errInvalidDSNAddr
  313 + }
  314 + cfg.Addr = dsn[k+1 : i-1]
  315 + break
  316 + }
  317 + }
  318 + cfg.Net = dsn[j+1 : k]
  319 + }
  320 +
  321 + // dbname[?param1=value1&...&paramN=valueN]
  322 + // Find the first '?' in dsn[i+1:]
  323 + for j = i + 1; j < len(dsn); j++ {
  324 + if dsn[j] == '?' {
  325 + if err = parseDSNParams(cfg, dsn[j+1:]); err != nil {
  326 + return
  327 + }
  328 + break
  329 + }
  330 + }
  331 + cfg.DBName = dsn[i+1 : j]
  332 +
  333 + break
  334 + }
  335 + }
  336 +
  337 + if !foundSlash && len(dsn) > 0 {
  338 + return nil, errInvalidDSNNoSlash
  339 + }
  340 +
  341 + if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
  342 + return nil, errInvalidDSNUnsafeCollation
  343 + }
  344 +
  345 + // Set default network if empty
  346 + if cfg.Net == "" {
  347 + cfg.Net = "tcp"
  348 + }
  349 +
  350 + // Set default address if empty
  351 + if cfg.Addr == "" {
  352 + switch cfg.Net {
  353 + case "tcp":
  354 + cfg.Addr = "127.0.0.1:3306"
  355 + case "unix":
  356 + cfg.Addr = "/tmp/mysql.sock"
  357 + default:
  358 + return nil, errors.New("default addr for network '" + cfg.Net + "' unknown")
  359 + }
  360 +
  361 + }
  362 +
  363 + return
  364 +}
  365 +
  366 +// parseDSNParams parses the DSN "query string"
  367 +// Values must be url.QueryEscape'ed
  368 +func parseDSNParams(cfg *Config, params string) (err error) {
  369 + for _, v := range strings.Split(params, "&") {
  370 + param := strings.SplitN(v, "=", 2)
  371 + if len(param) != 2 {
  372 + continue
  373 + }
  374 +
  375 + // cfg params
  376 + switch value := param[1]; param[0] {
  377 +
  378 + // Disable INFILE whitelist / enable all files
  379 + case "allowAllFiles":
  380 + var isBool bool
  381 + cfg.AllowAllFiles, isBool = readBool(value)
  382 + if !isBool {
  383 + return errors.New("invalid bool value: " + value)
  384 + }
  385 +
  386 + // Use cleartext authentication mode (MySQL 5.5.10+)
  387 + case "allowCleartextPasswords":
  388 + var isBool bool
  389 + cfg.AllowCleartextPasswords, isBool = readBool(value)
  390 + if !isBool {
  391 + return errors.New("invalid bool value: " + value)
  392 + }
  393 +
  394 + // Use native password authentication
  395 + case "allowNativePasswords":
  396 + var isBool bool
  397 + cfg.AllowNativePasswords, isBool = readBool(value)
  398 + if !isBool {
  399 + return errors.New("invalid bool value: " + value)
  400 + }
  401 +
  402 + // Use old authentication mode (pre MySQL 4.1)
  403 + case "allowOldPasswords":
  404 + var isBool bool
  405 + cfg.AllowOldPasswords, isBool = readBool(value)
  406 + if !isBool {
  407 + return errors.New("invalid bool value: " + value)
  408 + }
  409 +
  410 + // Switch "rowsAffected" mode
  411 + case "clientFoundRows":
  412 + var isBool bool
  413 + cfg.ClientFoundRows, isBool = readBool(value)
  414 + if !isBool {
  415 + return errors.New("invalid bool value: " + value)
  416 + }
  417 +
  418 + // Collation
  419 + case "collation":
  420 + cfg.Collation = value
  421 + break
  422 +
  423 + case "columnsWithAlias":
  424 + var isBool bool
  425 + cfg.ColumnsWithAlias, isBool = readBool(value)
  426 + if !isBool {
  427 + return errors.New("invalid bool value: " + value)
  428 + }
  429 +
  430 + // Compression
  431 + case "compress":
  432 + return errors.New("compression not implemented yet")
  433 +
  434 + // Enable client side placeholder substitution
  435 + case "interpolateParams":
  436 + var isBool bool
  437 + cfg.InterpolateParams, isBool = readBool(value)
  438 + if !isBool {
  439 + return errors.New("invalid bool value: " + value)
  440 + }
  441 +
  442 + // Time Location
  443 + case "loc":
  444 + if value, err = url.QueryUnescape(value); err != nil {
  445 + return
  446 + }
  447 + cfg.Loc, err = time.LoadLocation(value)
  448 + if err != nil {
  449 + return
  450 + }
  451 +
  452 + // multiple statements in one query
  453 + case "multiStatements":
  454 + var isBool bool
  455 + cfg.MultiStatements, isBool = readBool(value)
  456 + if !isBool {
  457 + return errors.New("invalid bool value: " + value)
  458 + }
  459 +
  460 + // time.Time parsing
  461 + case "parseTime":
  462 + var isBool bool
  463 + cfg.ParseTime, isBool = readBool(value)
  464 + if !isBool {
  465 + return errors.New("invalid bool value: " + value)
  466 + }
  467 +
  468 + // I/O read Timeout
  469 + case "readTimeout":
  470 + cfg.ReadTimeout, err = time.ParseDuration(value)
  471 + if err != nil {
  472 + return
  473 + }
  474 +
  475 + // Strict mode
  476 + case "strict":
  477 + var isBool bool
  478 + cfg.Strict, isBool = readBool(value)
  479 + if !isBool {
  480 + return errors.New("invalid bool value: " + value)
  481 + }
  482 +
  483 + // Dial Timeout
  484 + case "timeout":
  485 + cfg.Timeout, err = time.ParseDuration(value)
  486 + if err != nil {
  487 + return
  488 + }
  489 +
  490 + // TLS-Encryption
  491 + case "tls":
  492 + boolValue, isBool := readBool(value)
  493 + if isBool {
  494 + if boolValue {
  495 + cfg.TLSConfig = "true"
  496 + cfg.tls = &tls.Config{}
  497 + } else {
  498 + cfg.TLSConfig = "false"
  499 + }
  500 + } else if vl := strings.ToLower(value); vl == "skip-verify" {
  501 + cfg.TLSConfig = vl
  502 + cfg.tls = &tls.Config{InsecureSkipVerify: true}
  503 + } else {
  504 + name, err := url.QueryUnescape(value)
  505 + if err != nil {
  506 + return fmt.Errorf("invalid value for TLS config name: %v", err)
  507 + }
  508 +
  509 + if tlsConfig, ok := tlsConfigRegister[name]; ok {
  510 + if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
  511 + host, _, err := net.SplitHostPort(cfg.Addr)
  512 + if err == nil {
  513 + tlsConfig.ServerName = host
  514 + }
  515 + }
  516 +
  517 + cfg.TLSConfig = name
  518 + cfg.tls = tlsConfig
  519 + } else {
  520 + return errors.New("invalid value / unknown config name: " + name)
  521 + }
  522 + }
  523 +
  524 + // I/O write Timeout
  525 + case "writeTimeout":
  526 + cfg.WriteTimeout, err = time.ParseDuration(value)
  527 + if err != nil {
  528 + return
  529 + }
  530 + case "maxAllowedPacket":
  531 + cfg.MaxAllowedPacket, err = strconv.Atoi(value)
  532 + if err != nil {
  533 + return
  534 + }
  535 + default:
  536 + // lazy init
  537 + if cfg.Params == nil {
  538 + cfg.Params = make(map[string]string)
  539 + }
  540 +
  541 + if cfg.Params[param[0]], err = url.QueryUnescape(value); err != nil {
  542 + return
  543 + }
  544 + }
  545 + }
  546 +
  547 + return
  548 +}
... ...
src/github.com/go-sql-driver/mysql/dsn_test.go 0 → 100644
... ... @@ -0,0 +1,231 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2016 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +import (
  12 + "crypto/tls"
  13 + "fmt"
  14 + "net/url"
  15 + "reflect"
  16 + "testing"
  17 + "time"
  18 +)
  19 +
  20 +var testDSNs = []struct {
  21 + in string
  22 + out *Config
  23 +}{{
  24 + "username:password@protocol(address)/dbname?param=value",
  25 + &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC},
  26 +}, {
  27 + "username:password@protocol(address)/dbname?param=value&columnsWithAlias=true",
  28 + &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, ColumnsWithAlias: true},
  29 +}, {
  30 + "username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true",
  31 + &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, ColumnsWithAlias: true, MultiStatements: true},
  32 +}, {
  33 + "user@unix(/path/to/socket)/dbname?charset=utf8",
  34 + &Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC},
  35 +}, {
  36 + "user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true",
  37 + &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, TLSConfig: "true"},
  38 +}, {
  39 + "user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify",
  40 + &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, TLSConfig: "skip-verify"},
  41 +}, {
  42 + "user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216",
  43 + &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, ClientFoundRows: true, MaxAllowedPacket: 16777216},
  44 +}, {
  45 + "user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local",
  46 + &Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.Local},
  47 +}, {
  48 + "/dbname",
  49 + &Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC},
  50 +}, {
  51 + "@/",
  52 + &Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC},
  53 +}, {
  54 + "/",
  55 + &Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC},
  56 +}, {
  57 + "",
  58 + &Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC},
  59 +}, {
  60 + "user:p@/ssword@/",
  61 + &Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC},
  62 +}, {
  63 + "unix/?arg=%2Fsome%2Fpath.ext",
  64 + &Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8_general_ci", Loc: time.UTC},
  65 +}}
  66 +
  67 +func TestDSNParser(t *testing.T) {
  68 + for i, tst := range testDSNs {
  69 + cfg, err := ParseDSN(tst.in)
  70 + if err != nil {
  71 + t.Error(err.Error())
  72 + }
  73 +
  74 + // pointer not static
  75 + cfg.tls = nil
  76 +
  77 + if !reflect.DeepEqual(cfg, tst.out) {
  78 + t.Errorf("%d. ParseDSN(%q) mismatch:\ngot %+v\nwant %+v", i, tst.in, cfg, tst.out)
  79 + }
  80 + }
  81 +}
  82 +
  83 +func TestDSNParserInvalid(t *testing.T) {
  84 + var invalidDSNs = []string{
  85 + "@net(addr/", // no closing brace
  86 + "@tcp(/", // no closing brace
  87 + "tcp(/", // no closing brace
  88 + "(/", // no closing brace
  89 + "net(addr)//", // unescaped
  90 + "User:pass@tcp(1.2.3.4:3306)", // no trailing slash
  91 + //"/dbname?arg=/some/unescaped/path",
  92 + }
  93 +
  94 + for i, tst := range invalidDSNs {
  95 + if _, err := ParseDSN(tst); err == nil {
  96 + t.Errorf("invalid DSN #%d. (%s) didn't error!", i, tst)
  97 + }
  98 + }
  99 +}
  100 +
  101 +func TestDSNReformat(t *testing.T) {
  102 + for i, tst := range testDSNs {
  103 + dsn1 := tst.in
  104 + cfg1, err := ParseDSN(dsn1)
  105 + if err != nil {
  106 + t.Error(err.Error())
  107 + continue
  108 + }
  109 + cfg1.tls = nil // pointer not static
  110 + res1 := fmt.Sprintf("%+v", cfg1)
  111 +
  112 + dsn2 := cfg1.FormatDSN()
  113 + cfg2, err := ParseDSN(dsn2)
  114 + if err != nil {
  115 + t.Error(err.Error())
  116 + continue
  117 + }
  118 + cfg2.tls = nil // pointer not static
  119 + res2 := fmt.Sprintf("%+v", cfg2)
  120 +
  121 + if res1 != res2 {
  122 + t.Errorf("%d. %q does not match %q", i, res2, res1)
  123 + }
  124 + }
  125 +}
  126 +
  127 +func TestDSNWithCustomTLS(t *testing.T) {
  128 + baseDSN := "User:password@tcp(localhost:5555)/dbname?tls="
  129 + tlsCfg := tls.Config{}
  130 +
  131 + RegisterTLSConfig("utils_test", &tlsCfg)
  132 +
  133 + // Custom TLS is missing
  134 + tst := baseDSN + "invalid_tls"
  135 + cfg, err := ParseDSN(tst)
  136 + if err == nil {
  137 + t.Errorf("invalid custom TLS in DSN (%s) but did not error. Got config: %#v", tst, cfg)
  138 + }
  139 +
  140 + tst = baseDSN + "utils_test"
  141 +
  142 + // Custom TLS with a server name
  143 + name := "foohost"
  144 + tlsCfg.ServerName = name
  145 + cfg, err = ParseDSN(tst)
  146 +
  147 + if err != nil {
  148 + t.Error(err.Error())
  149 + } else if cfg.tls.ServerName != name {
  150 + t.Errorf("did not get the correct TLS ServerName (%s) parsing DSN (%s).", name, tst)
  151 + }
  152 +
  153 + // Custom TLS without a server name
  154 + name = "localhost"
  155 + tlsCfg.ServerName = ""
  156 + cfg, err = ParseDSN(tst)
  157 +
  158 + if err != nil {
  159 + t.Error(err.Error())
  160 + } else if cfg.tls.ServerName != name {
  161 + t.Errorf("did not get the correct ServerName (%s) parsing DSN (%s).", name, tst)
  162 + }
  163 +
  164 + DeregisterTLSConfig("utils_test")
  165 +}
  166 +
  167 +func TestDSNWithCustomTLSQueryEscape(t *testing.T) {
  168 + const configKey = "&%!:"
  169 + dsn := "User:password@tcp(localhost:5555)/dbname?tls=" + url.QueryEscape(configKey)
  170 + name := "foohost"
  171 + tlsCfg := tls.Config{ServerName: name}
  172 +
  173 + RegisterTLSConfig(configKey, &tlsCfg)
  174 +
  175 + cfg, err := ParseDSN(dsn)
  176 +
  177 + if err != nil {
  178 + t.Error(err.Error())
  179 + } else if cfg.tls.ServerName != name {
  180 + t.Errorf("did not get the correct TLS ServerName (%s) parsing DSN (%s).", name, dsn)
  181 + }
  182 +}
  183 +
  184 +func TestDSNUnsafeCollation(t *testing.T) {
  185 + _, err := ParseDSN("/dbname?collation=gbk_chinese_ci&interpolateParams=true")
  186 + if err != errInvalidDSNUnsafeCollation {
  187 + t.Errorf("expected %v, got %v", errInvalidDSNUnsafeCollation, err)
  188 + }
  189 +
  190 + _, err = ParseDSN("/dbname?collation=gbk_chinese_ci&interpolateParams=false")
  191 + if err != nil {
  192 + t.Errorf("expected %v, got %v", nil, err)
  193 + }
  194 +
  195 + _, err = ParseDSN("/dbname?collation=gbk_chinese_ci")
  196 + if err != nil {
  197 + t.Errorf("expected %v, got %v", nil, err)
  198 + }
  199 +
  200 + _, err = ParseDSN("/dbname?collation=ascii_bin&interpolateParams=true")
  201 + if err != nil {
  202 + t.Errorf("expected %v, got %v", nil, err)
  203 + }
  204 +
  205 + _, err = ParseDSN("/dbname?collation=latin1_german1_ci&interpolateParams=true")
  206 + if err != nil {
  207 + t.Errorf("expected %v, got %v", nil, err)
  208 + }
  209 +
  210 + _, err = ParseDSN("/dbname?collation=utf8_general_ci&interpolateParams=true")
  211 + if err != nil {
  212 + t.Errorf("expected %v, got %v", nil, err)
  213 + }
  214 +
  215 + _, err = ParseDSN("/dbname?collation=utf8mb4_general_ci&interpolateParams=true")
  216 + if err != nil {
  217 + t.Errorf("expected %v, got %v", nil, err)
  218 + }
  219 +}
  220 +
  221 +func BenchmarkParseDSN(b *testing.B) {
  222 + b.ReportAllocs()
  223 +
  224 + for i := 0; i < b.N; i++ {
  225 + for _, tst := range testDSNs {
  226 + if _, err := ParseDSN(tst.in); err != nil {
  227 + b.Error(err.Error())
  228 + }
  229 + }
  230 + }
  231 +}
... ...
src/github.com/go-sql-driver/mysql/errors.go 0 → 100644
... ... @@ -0,0 +1,132 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +import (
  12 + "database/sql/driver"
  13 + "errors"
  14 + "fmt"
  15 + "io"
  16 + "log"
  17 + "os"
  18 +)
  19 +
  20 +// Various errors the driver might return. Can change between driver versions.
  21 +var (
  22 + ErrInvalidConn = errors.New("invalid connection")
  23 + ErrMalformPkt = errors.New("malformed packet")
  24 + ErrNoTLS = errors.New("TLS requested but server does not support TLS")
  25 + ErrCleartextPassword = errors.New("this user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN")
  26 + ErrNativePassword = errors.New("this user requires mysql native password authentication.")
  27 + 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")
  28 + ErrUnknownPlugin = errors.New("this authentication plugin is not supported")
  29 + ErrOldProtocol = errors.New("MySQL server does not support required protocol 41+")
  30 + ErrPktSync = errors.New("commands out of sync. You can't run this command now")
  31 + ErrPktSyncMul = errors.New("commands out of sync. Did you run multiple statements at once?")
  32 + ErrPktTooLarge = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server")
  33 + ErrBusyBuffer = errors.New("busy buffer")
  34 +)
  35 +
  36 +var errLog = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))
  37 +
  38 +// Logger is used to log critical error messages.
  39 +type Logger interface {
  40 + Print(v ...interface{})
  41 +}
  42 +
  43 +// SetLogger is used to set the logger for critical errors.
  44 +// The initial logger is os.Stderr.
  45 +func SetLogger(logger Logger) error {
  46 + if logger == nil {
  47 + return errors.New("logger is nil")
  48 + }
  49 + errLog = logger
  50 + return nil
  51 +}
  52 +
  53 +// MySQLError is an error type which represents a single MySQL error
  54 +type MySQLError struct {
  55 + Number uint16
  56 + Message string
  57 +}
  58 +
  59 +func (me *MySQLError) Error() string {
  60 + return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
  61 +}
  62 +
  63 +// MySQLWarnings is an error type which represents a group of one or more MySQL
  64 +// warnings
  65 +type MySQLWarnings []MySQLWarning
  66 +
  67 +func (mws MySQLWarnings) Error() string {
  68 + var msg string
  69 + for i, warning := range mws {
  70 + if i > 0 {
  71 + msg += "\r\n"
  72 + }
  73 + msg += fmt.Sprintf(
  74 + "%s %s: %s",
  75 + warning.Level,
  76 + warning.Code,
  77 + warning.Message,
  78 + )
  79 + }
  80 + return msg
  81 +}
  82 +
  83 +// MySQLWarning is an error type which represents a single MySQL warning.
  84 +// Warnings are returned in groups only. See MySQLWarnings
  85 +type MySQLWarning struct {
  86 + Level string
  87 + Code string
  88 + Message string
  89 +}
  90 +
  91 +func (mc *mysqlConn) getWarnings() (err error) {
  92 + rows, err := mc.Query("SHOW WARNINGS", nil)
  93 + if err != nil {
  94 + return
  95 + }
  96 +
  97 + var warnings = MySQLWarnings{}
  98 + var values = make([]driver.Value, 3)
  99 +
  100 + for {
  101 + err = rows.Next(values)
  102 + switch err {
  103 + case nil:
  104 + warning := MySQLWarning{}
  105 +
  106 + if raw, ok := values[0].([]byte); ok {
  107 + warning.Level = string(raw)
  108 + } else {
  109 + warning.Level = fmt.Sprintf("%s", values[0])
  110 + }
  111 + if raw, ok := values[1].([]byte); ok {
  112 + warning.Code = string(raw)
  113 + } else {
  114 + warning.Code = fmt.Sprintf("%s", values[1])
  115 + }
  116 + if raw, ok := values[2].([]byte); ok {
  117 + warning.Message = string(raw)
  118 + } else {
  119 + warning.Message = fmt.Sprintf("%s", values[0])
  120 + }
  121 +
  122 + warnings = append(warnings, warning)
  123 +
  124 + case io.EOF:
  125 + return warnings
  126 +
  127 + default:
  128 + rows.Close()
  129 + return
  130 + }
  131 + }
  132 +}
... ...
src/github.com/go-sql-driver/mysql/errors_test.go 0 → 100644
... ... @@ -0,0 +1,42 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +import (
  12 + "bytes"
  13 + "log"
  14 + "testing"
  15 +)
  16 +
  17 +func TestErrorsSetLogger(t *testing.T) {
  18 + previous := errLog
  19 + defer func() {
  20 + errLog = previous
  21 + }()
  22 +
  23 + // set up logger
  24 + const expected = "prefix: test\n"
  25 + buffer := bytes.NewBuffer(make([]byte, 0, 64))
  26 + logger := log.New(buffer, "prefix: ", 0)
  27 +
  28 + // print
  29 + SetLogger(logger)
  30 + errLog.Print("test")
  31 +
  32 + // check result
  33 + if actual := buffer.String(); actual != expected {
  34 + t.Errorf("expected %q, got %q", expected, actual)
  35 + }
  36 +}
  37 +
  38 +func TestErrorsStrictIgnoreNotes(t *testing.T) {
  39 + runTests(t, dsn+"&sql_notes=false", func(dbt *DBTest) {
  40 + dbt.mustExec("DROP TABLE IF EXISTS does_not_exist")
  41 + })
  42 +}
... ...
src/github.com/go-sql-driver/mysql/infile.go 0 → 100644
... ... @@ -0,0 +1,182 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +import (
  12 + "fmt"
  13 + "io"
  14 + "os"
  15 + "strings"
  16 + "sync"
  17 +)
  18 +
  19 +var (
  20 + fileRegister map[string]bool
  21 + fileRegisterLock sync.RWMutex
  22 + readerRegister map[string]func() io.Reader
  23 + readerRegisterLock sync.RWMutex
  24 +)
  25 +
  26 +// RegisterLocalFile adds the given file to the file whitelist,
  27 +// so that it can be used by "LOAD DATA LOCAL INFILE <filepath>".
  28 +// Alternatively you can allow the use of all local files with
  29 +// the DSN parameter 'allowAllFiles=true'
  30 +//
  31 +// filePath := "/home/gopher/data.csv"
  32 +// mysql.RegisterLocalFile(filePath)
  33 +// err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo")
  34 +// if err != nil {
  35 +// ...
  36 +//
  37 +func RegisterLocalFile(filePath string) {
  38 + fileRegisterLock.Lock()
  39 + // lazy map init
  40 + if fileRegister == nil {
  41 + fileRegister = make(map[string]bool)
  42 + }
  43 +
  44 + fileRegister[strings.Trim(filePath, `"`)] = true
  45 + fileRegisterLock.Unlock()
  46 +}
  47 +
  48 +// DeregisterLocalFile removes the given filepath from the whitelist.
  49 +func DeregisterLocalFile(filePath string) {
  50 + fileRegisterLock.Lock()
  51 + delete(fileRegister, strings.Trim(filePath, `"`))
  52 + fileRegisterLock.Unlock()
  53 +}
  54 +
  55 +// RegisterReaderHandler registers a handler function which is used
  56 +// to receive a io.Reader.
  57 +// The Reader can be used by "LOAD DATA LOCAL INFILE Reader::<name>".
  58 +// If the handler returns a io.ReadCloser Close() is called when the
  59 +// request is finished.
  60 +//
  61 +// mysql.RegisterReaderHandler("data", func() io.Reader {
  62 +// var csvReader io.Reader // Some Reader that returns CSV data
  63 +// ... // Open Reader here
  64 +// return csvReader
  65 +// })
  66 +// err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo")
  67 +// if err != nil {
  68 +// ...
  69 +//
  70 +func RegisterReaderHandler(name string, handler func() io.Reader) {
  71 + readerRegisterLock.Lock()
  72 + // lazy map init
  73 + if readerRegister == nil {
  74 + readerRegister = make(map[string]func() io.Reader)
  75 + }
  76 +
  77 + readerRegister[name] = handler
  78 + readerRegisterLock.Unlock()
  79 +}
  80 +
  81 +// DeregisterReaderHandler removes the ReaderHandler function with
  82 +// the given name from the registry.
  83 +func DeregisterReaderHandler(name string) {
  84 + readerRegisterLock.Lock()
  85 + delete(readerRegister, name)
  86 + readerRegisterLock.Unlock()
  87 +}
  88 +
  89 +func deferredClose(err *error, closer io.Closer) {
  90 + closeErr := closer.Close()
  91 + if *err == nil {
  92 + *err = closeErr
  93 + }
  94 +}
  95 +
  96 +func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
  97 + var rdr io.Reader
  98 + var data []byte
  99 + packetSize := 16 * 1024 // 16KB is small enough for disk readahead and large enough for TCP
  100 + if mc.maxWriteSize < packetSize {
  101 + packetSize = mc.maxWriteSize
  102 + }
  103 +
  104 + if idx := strings.Index(name, "Reader::"); idx == 0 || (idx > 0 && name[idx-1] == '/') { // io.Reader
  105 + // The server might return an an absolute path. See issue #355.
  106 + name = name[idx+8:]
  107 +
  108 + readerRegisterLock.RLock()
  109 + handler, inMap := readerRegister[name]
  110 + readerRegisterLock.RUnlock()
  111 +
  112 + if inMap {
  113 + rdr = handler()
  114 + if rdr != nil {
  115 + if cl, ok := rdr.(io.Closer); ok {
  116 + defer deferredClose(&err, cl)
  117 + }
  118 + } else {
  119 + err = fmt.Errorf("Reader '%s' is <nil>", name)
  120 + }
  121 + } else {
  122 + err = fmt.Errorf("Reader '%s' is not registered", name)
  123 + }
  124 + } else { // File
  125 + name = strings.Trim(name, `"`)
  126 + fileRegisterLock.RLock()
  127 + fr := fileRegister[name]
  128 + fileRegisterLock.RUnlock()
  129 + if mc.cfg.AllowAllFiles || fr {
  130 + var file *os.File
  131 + var fi os.FileInfo
  132 +
  133 + if file, err = os.Open(name); err == nil {
  134 + defer deferredClose(&err, file)
  135 +
  136 + // get file size
  137 + if fi, err = file.Stat(); err == nil {
  138 + rdr = file
  139 + if fileSize := int(fi.Size()); fileSize < packetSize {
  140 + packetSize = fileSize
  141 + }
  142 + }
  143 + }
  144 + } else {
  145 + err = fmt.Errorf("local file '%s' is not registered", name)
  146 + }
  147 + }
  148 +
  149 + // send content packets
  150 + if err == nil {
  151 + data := make([]byte, 4+packetSize)
  152 + var n int
  153 + for err == nil {
  154 + n, err = rdr.Read(data[4:])
  155 + if n > 0 {
  156 + if ioErr := mc.writePacket(data[:4+n]); ioErr != nil {
  157 + return ioErr
  158 + }
  159 + }
  160 + }
  161 + if err == io.EOF {
  162 + err = nil
  163 + }
  164 + }
  165 +
  166 + // send empty packet (termination)
  167 + if data == nil {
  168 + data = make([]byte, 4)
  169 + }
  170 + if ioErr := mc.writePacket(data[:4]); ioErr != nil {
  171 + return ioErr
  172 + }
  173 +
  174 + // read OK packet
  175 + if err == nil {
  176 + _, err = mc.readResultOK()
  177 + return err
  178 + }
  179 +
  180 + mc.readPacket()
  181 + return err
  182 +}
... ...
src/github.com/go-sql-driver/mysql/packets.go 0 → 100644
... ... @@ -0,0 +1,1280 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +import (
  12 + "bytes"
  13 + "crypto/tls"
  14 + "database/sql/driver"
  15 + "encoding/binary"
  16 + "errors"
  17 + "fmt"
  18 + "io"
  19 + "math"
  20 + "time"
  21 +)
  22 +
  23 +// Packets documentation:
  24 +// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
  25 +
  26 +// Read packet to buffer 'data'
  27 +func (mc *mysqlConn) readPacket() ([]byte, error) {
  28 + var payload []byte
  29 + for {
  30 + // Read packet header
  31 + data, err := mc.buf.readNext(4)
  32 + if err != nil {
  33 + errLog.Print(err)
  34 + mc.Close()
  35 + return nil, driver.ErrBadConn
  36 + }
  37 +
  38 + // Packet Length [24 bit]
  39 + pktLen := int(uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16)
  40 +
  41 + if pktLen < 1 {
  42 + errLog.Print(ErrMalformPkt)
  43 + mc.Close()
  44 + return nil, driver.ErrBadConn
  45 + }
  46 +
  47 + // Check Packet Sync [8 bit]
  48 + if data[3] != mc.sequence {
  49 + if data[3] > mc.sequence {
  50 + return nil, ErrPktSyncMul
  51 + }
  52 + return nil, ErrPktSync
  53 + }
  54 + mc.sequence++
  55 +
  56 + // Read packet body [pktLen bytes]
  57 + data, err = mc.buf.readNext(pktLen)
  58 + if err != nil {
  59 + errLog.Print(err)
  60 + mc.Close()
  61 + return nil, driver.ErrBadConn
  62 + }
  63 +
  64 + isLastPacket := (pktLen < maxPacketSize)
  65 +
  66 + // Zero allocations for non-splitting packets
  67 + if isLastPacket && payload == nil {
  68 + return data, nil
  69 + }
  70 +
  71 + payload = append(payload, data...)
  72 +
  73 + if isLastPacket {
  74 + return payload, nil
  75 + }
  76 + }
  77 +}
  78 +
  79 +// Write packet buffer 'data'
  80 +func (mc *mysqlConn) writePacket(data []byte) error {
  81 + pktLen := len(data) - 4
  82 +
  83 + if pktLen > mc.maxAllowedPacket {
  84 + return ErrPktTooLarge
  85 + }
  86 +
  87 + for {
  88 + var size int
  89 + if pktLen >= maxPacketSize {
  90 + data[0] = 0xff
  91 + data[1] = 0xff
  92 + data[2] = 0xff
  93 + size = maxPacketSize
  94 + } else {
  95 + data[0] = byte(pktLen)
  96 + data[1] = byte(pktLen >> 8)
  97 + data[2] = byte(pktLen >> 16)
  98 + size = pktLen
  99 + }
  100 + data[3] = mc.sequence
  101 +
  102 + // Write packet
  103 + if mc.writeTimeout > 0 {
  104 + if err := mc.netConn.SetWriteDeadline(time.Now().Add(mc.writeTimeout)); err != nil {
  105 + return err
  106 + }
  107 + }
  108 +
  109 + n, err := mc.netConn.Write(data[:4+size])
  110 + if err == nil && n == 4+size {
  111 + mc.sequence++
  112 + if size != maxPacketSize {
  113 + return nil
  114 + }
  115 + pktLen -= size
  116 + data = data[size:]
  117 + continue
  118 + }
  119 +
  120 + // Handle error
  121 + if err == nil { // n != len(data)
  122 + errLog.Print(ErrMalformPkt)
  123 + } else {
  124 + errLog.Print(err)
  125 + }
  126 + return driver.ErrBadConn
  127 + }
  128 +}
  129 +
  130 +/******************************************************************************
  131 +* Initialisation Process *
  132 +******************************************************************************/
  133 +
  134 +// Handshake Initialization Packet
  135 +// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake
  136 +func (mc *mysqlConn) readInitPacket() ([]byte, error) {
  137 + data, err := mc.readPacket()
  138 + if err != nil {
  139 + return nil, err
  140 + }
  141 +
  142 + if data[0] == iERR {
  143 + return nil, mc.handleErrorPacket(data)
  144 + }
  145 +
  146 + // protocol version [1 byte]
  147 + if data[0] < minProtocolVersion {
  148 + return nil, fmt.Errorf(
  149 + "unsupported protocol version %d. Version %d or higher is required",
  150 + data[0],
  151 + minProtocolVersion,
  152 + )
  153 + }
  154 +
  155 + // server version [null terminated string]
  156 + // connection id [4 bytes]
  157 + pos := 1 + bytes.IndexByte(data[1:], 0x00) + 1 + 4
  158 +
  159 + // first part of the password cipher [8 bytes]
  160 + cipher := data[pos : pos+8]
  161 +
  162 + // (filler) always 0x00 [1 byte]
  163 + pos += 8 + 1
  164 +
  165 + // capability flags (lower 2 bytes) [2 bytes]
  166 + mc.flags = clientFlag(binary.LittleEndian.Uint16(data[pos : pos+2]))
  167 + if mc.flags&clientProtocol41 == 0 {
  168 + return nil, ErrOldProtocol
  169 + }
  170 + if mc.flags&clientSSL == 0 && mc.cfg.tls != nil {
  171 + return nil, ErrNoTLS
  172 + }
  173 + pos += 2
  174 +
  175 + if len(data) > pos {
  176 + // character set [1 byte]
  177 + // status flags [2 bytes]
  178 + // capability flags (upper 2 bytes) [2 bytes]
  179 + // length of auth-plugin-data [1 byte]
  180 + // reserved (all [00]) [10 bytes]
  181 + pos += 1 + 2 + 2 + 1 + 10
  182 +
  183 + // second part of the password cipher [mininum 13 bytes],
  184 + // where len=MAX(13, length of auth-plugin-data - 8)
  185 + //
  186 + // The web documentation is ambiguous about the length. However,
  187 + // according to mysql-5.7/sql/auth/sql_authentication.cc line 538,
  188 + // the 13th byte is "\0 byte, terminating the second part of
  189 + // a scramble". So the second part of the password cipher is
  190 + // a NULL terminated string that's at least 13 bytes with the
  191 + // last byte being NULL.
  192 + //
  193 + // The official Python library uses the fixed length 12
  194 + // which seems to work but technically could have a hidden bug.
  195 + cipher = append(cipher, data[pos:pos+12]...)
  196 +
  197 + // TODO: Verify string termination
  198 + // EOF if version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2)
  199 + // \NUL otherwise
  200 + //
  201 + //if data[len(data)-1] == 0 {
  202 + // return
  203 + //}
  204 + //return ErrMalformPkt
  205 +
  206 + // make a memory safe copy of the cipher slice
  207 + var b [20]byte
  208 + copy(b[:], cipher)
  209 + return b[:], nil
  210 + }
  211 +
  212 + // make a memory safe copy of the cipher slice
  213 + var b [8]byte
  214 + copy(b[:], cipher)
  215 + return b[:], nil
  216 +}
  217 +
  218 +// Client Authentication Packet
  219 +// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse
  220 +func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
  221 + // Adjust client flags based on server support
  222 + clientFlags := clientProtocol41 |
  223 + clientSecureConn |
  224 + clientLongPassword |
  225 + clientTransactions |
  226 + clientLocalFiles |
  227 + clientPluginAuth |
  228 + clientMultiResults |
  229 + mc.flags&clientLongFlag
  230 +
  231 + if mc.cfg.ClientFoundRows {
  232 + clientFlags |= clientFoundRows
  233 + }
  234 +
  235 + // To enable TLS / SSL
  236 + if mc.cfg.tls != nil {
  237 + clientFlags |= clientSSL
  238 + }
  239 +
  240 + if mc.cfg.MultiStatements {
  241 + clientFlags |= clientMultiStatements
  242 + }
  243 +
  244 + // User Password
  245 + scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.Passwd))
  246 +
  247 + pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + 1 + len(scrambleBuff) + 21 + 1
  248 +
  249 + // To specify a db name
  250 + if n := len(mc.cfg.DBName); n > 0 {
  251 + clientFlags |= clientConnectWithDB
  252 + pktLen += n + 1
  253 + }
  254 +
  255 + // Calculate packet length and get buffer with that size
  256 + data := mc.buf.takeSmallBuffer(pktLen + 4)
  257 + if data == nil {
  258 + // can not take the buffer. Something must be wrong with the connection
  259 + errLog.Print(ErrBusyBuffer)
  260 + return driver.ErrBadConn
  261 + }
  262 +
  263 + // ClientFlags [32 bit]
  264 + data[4] = byte(clientFlags)
  265 + data[5] = byte(clientFlags >> 8)
  266 + data[6] = byte(clientFlags >> 16)
  267 + data[7] = byte(clientFlags >> 24)
  268 +
  269 + // MaxPacketSize [32 bit] (none)
  270 + data[8] = 0x00
  271 + data[9] = 0x00
  272 + data[10] = 0x00
  273 + data[11] = 0x00
  274 +
  275 + // Charset [1 byte]
  276 + var found bool
  277 + data[12], found = collations[mc.cfg.Collation]
  278 + if !found {
  279 + // Note possibility for false negatives:
  280 + // could be triggered although the collation is valid if the
  281 + // collations map does not contain entries the server supports.
  282 + return errors.New("unknown collation")
  283 + }
  284 +
  285 + // SSL Connection Request Packet
  286 + // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest
  287 + if mc.cfg.tls != nil {
  288 + // Send TLS / SSL request packet
  289 + if err := mc.writePacket(data[:(4+4+1+23)+4]); err != nil {
  290 + return err
  291 + }
  292 +
  293 + // Switch to TLS
  294 + tlsConn := tls.Client(mc.netConn, mc.cfg.tls)
  295 + if err := tlsConn.Handshake(); err != nil {
  296 + return err
  297 + }
  298 + mc.netConn = tlsConn
  299 + mc.buf.nc = tlsConn
  300 + }
  301 +
  302 + // Filler [23 bytes] (all 0x00)
  303 + pos := 13
  304 + for ; pos < 13+23; pos++ {
  305 + data[pos] = 0
  306 + }
  307 +
  308 + // User [null terminated string]
  309 + if len(mc.cfg.User) > 0 {
  310 + pos += copy(data[pos:], mc.cfg.User)
  311 + }
  312 + data[pos] = 0x00
  313 + pos++
  314 +
  315 + // ScrambleBuffer [length encoded integer]
  316 + data[pos] = byte(len(scrambleBuff))
  317 + pos += 1 + copy(data[pos+1:], scrambleBuff)
  318 +
  319 + // Databasename [null terminated string]
  320 + if len(mc.cfg.DBName) > 0 {
  321 + pos += copy(data[pos:], mc.cfg.DBName)
  322 + data[pos] = 0x00
  323 + pos++
  324 + }
  325 +
  326 + // Assume native client during response
  327 + pos += copy(data[pos:], "mysql_native_password")
  328 + data[pos] = 0x00
  329 +
  330 + // Send Auth packet
  331 + return mc.writePacket(data)
  332 +}
  333 +
  334 +// Client old authentication packet
  335 +// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
  336 +func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {
  337 + // User password
  338 + scrambleBuff := scrambleOldPassword(cipher, []byte(mc.cfg.Passwd))
  339 +
  340 + // Calculate the packet length and add a tailing 0
  341 + pktLen := len(scrambleBuff) + 1
  342 + data := mc.buf.takeSmallBuffer(4 + pktLen)
  343 + if data == nil {
  344 + // can not take the buffer. Something must be wrong with the connection
  345 + errLog.Print(ErrBusyBuffer)
  346 + return driver.ErrBadConn
  347 + }
  348 +
  349 + // Add the scrambled password [null terminated string]
  350 + copy(data[4:], scrambleBuff)
  351 + data[4+pktLen-1] = 0x00
  352 +
  353 + return mc.writePacket(data)
  354 +}
  355 +
  356 +// Client clear text authentication packet
  357 +// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
  358 +func (mc *mysqlConn) writeClearAuthPacket() error {
  359 + // Calculate the packet length and add a tailing 0
  360 + pktLen := len(mc.cfg.Passwd) + 1
  361 + data := mc.buf.takeSmallBuffer(4 + pktLen)
  362 + if data == nil {
  363 + // can not take the buffer. Something must be wrong with the connection
  364 + errLog.Print(ErrBusyBuffer)
  365 + return driver.ErrBadConn
  366 + }
  367 +
  368 + // Add the clear password [null terminated string]
  369 + copy(data[4:], mc.cfg.Passwd)
  370 + data[4+pktLen-1] = 0x00
  371 +
  372 + return mc.writePacket(data)
  373 +}
  374 +
  375 +// Native password authentication method
  376 +// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
  377 +func (mc *mysqlConn) writeNativeAuthPacket(cipher []byte) error {
  378 + scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.Passwd))
  379 +
  380 + // Calculate the packet length and add a tailing 0
  381 + pktLen := len(scrambleBuff)
  382 + data := mc.buf.takeSmallBuffer(4 + pktLen)
  383 + if data == nil {
  384 + // can not take the buffer. Something must be wrong with the connection
  385 + errLog.Print(ErrBusyBuffer)
  386 + return driver.ErrBadConn
  387 + }
  388 +
  389 + // Add the scramble
  390 + copy(data[4:], scrambleBuff)
  391 +
  392 + return mc.writePacket(data)
  393 +}
  394 +
  395 +/******************************************************************************
  396 +* Command Packets *
  397 +******************************************************************************/
  398 +
  399 +func (mc *mysqlConn) writeCommandPacket(command byte) error {
  400 + // Reset Packet Sequence
  401 + mc.sequence = 0
  402 +
  403 + data := mc.buf.takeSmallBuffer(4 + 1)
  404 + if data == nil {
  405 + // can not take the buffer. Something must be wrong with the connection
  406 + errLog.Print(ErrBusyBuffer)
  407 + return driver.ErrBadConn
  408 + }
  409 +
  410 + // Add command byte
  411 + data[4] = command
  412 +
  413 + // Send CMD packet
  414 + return mc.writePacket(data)
  415 +}
  416 +
  417 +func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
  418 + // Reset Packet Sequence
  419 + mc.sequence = 0
  420 +
  421 + pktLen := 1 + len(arg)
  422 + data := mc.buf.takeBuffer(pktLen + 4)
  423 + if data == nil {
  424 + // can not take the buffer. Something must be wrong with the connection
  425 + errLog.Print(ErrBusyBuffer)
  426 + return driver.ErrBadConn
  427 + }
  428 +
  429 + // Add command byte
  430 + data[4] = command
  431 +
  432 + // Add arg
  433 + copy(data[5:], arg)
  434 +
  435 + // Send CMD packet
  436 + return mc.writePacket(data)
  437 +}
  438 +
  439 +func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
  440 + // Reset Packet Sequence
  441 + mc.sequence = 0
  442 +
  443 + data := mc.buf.takeSmallBuffer(4 + 1 + 4)
  444 + if data == nil {
  445 + // can not take the buffer. Something must be wrong with the connection
  446 + errLog.Print(ErrBusyBuffer)
  447 + return driver.ErrBadConn
  448 + }
  449 +
  450 + // Add command byte
  451 + data[4] = command
  452 +
  453 + // Add arg [32 bit]
  454 + data[5] = byte(arg)
  455 + data[6] = byte(arg >> 8)
  456 + data[7] = byte(arg >> 16)
  457 + data[8] = byte(arg >> 24)
  458 +
  459 + // Send CMD packet
  460 + return mc.writePacket(data)
  461 +}
  462 +
  463 +/******************************************************************************
  464 +* Result Packets *
  465 +******************************************************************************/
  466 +
  467 +// Returns error if Packet is not an 'Result OK'-Packet
  468 +func (mc *mysqlConn) readResultOK() ([]byte, error) {
  469 + data, err := mc.readPacket()
  470 + if err == nil {
  471 + // packet indicator
  472 + switch data[0] {
  473 +
  474 + case iOK:
  475 + return nil, mc.handleOkPacket(data)
  476 +
  477 + case iEOF:
  478 + if len(data) > 1 {
  479 + pluginEndIndex := bytes.IndexByte(data, 0x00)
  480 + plugin := string(data[1:pluginEndIndex])
  481 + cipher := data[pluginEndIndex+1 : len(data)-1]
  482 +
  483 + if plugin == "mysql_old_password" {
  484 + // using old_passwords
  485 + return cipher, ErrOldPassword
  486 + } else if plugin == "mysql_clear_password" {
  487 + // using clear text password
  488 + return cipher, ErrCleartextPassword
  489 + } else if plugin == "mysql_native_password" {
  490 + // using mysql default authentication method
  491 + return cipher, ErrNativePassword
  492 + } else {
  493 + return cipher, ErrUnknownPlugin
  494 + }
  495 + } else {
  496 + return nil, ErrOldPassword
  497 + }
  498 +
  499 + default: // Error otherwise
  500 + return nil, mc.handleErrorPacket(data)
  501 + }
  502 + }
  503 + return nil, err
  504 +}
  505 +
  506 +// Result Set Header Packet
  507 +// http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-ProtocolText::Resultset
  508 +func (mc *mysqlConn) readResultSetHeaderPacket() (int, error) {
  509 + data, err := mc.readPacket()
  510 + if err == nil {
  511 + switch data[0] {
  512 +
  513 + case iOK:
  514 + return 0, mc.handleOkPacket(data)
  515 +
  516 + case iERR:
  517 + return 0, mc.handleErrorPacket(data)
  518 +
  519 + case iLocalInFile:
  520 + return 0, mc.handleInFileRequest(string(data[1:]))
  521 + }
  522 +
  523 + // column count
  524 + num, _, n := readLengthEncodedInteger(data)
  525 + if n-len(data) == 0 {
  526 + return int(num), nil
  527 + }
  528 +
  529 + return 0, ErrMalformPkt
  530 + }
  531 + return 0, err
  532 +}
  533 +
  534 +// Error Packet
  535 +// http://dev.mysql.com/doc/internals/en/generic-response-packets.html#packet-ERR_Packet
  536 +func (mc *mysqlConn) handleErrorPacket(data []byte) error {
  537 + if data[0] != iERR {
  538 + return ErrMalformPkt
  539 + }
  540 +
  541 + // 0xff [1 byte]
  542 +
  543 + // Error Number [16 bit uint]
  544 + errno := binary.LittleEndian.Uint16(data[1:3])
  545 +
  546 + pos := 3
  547 +
  548 + // SQL State [optional: # + 5bytes string]
  549 + if data[3] == 0x23 {
  550 + //sqlstate := string(data[4 : 4+5])
  551 + pos = 9
  552 + }
  553 +
  554 + // Error Message [string]
  555 + return &MySQLError{
  556 + Number: errno,
  557 + Message: string(data[pos:]),
  558 + }
  559 +}
  560 +
  561 +func readStatus(b []byte) statusFlag {
  562 + return statusFlag(b[0]) | statusFlag(b[1])<<8
  563 +}
  564 +
  565 +// Ok Packet
  566 +// http://dev.mysql.com/doc/internals/en/generic-response-packets.html#packet-OK_Packet
  567 +func (mc *mysqlConn) handleOkPacket(data []byte) error {
  568 + var n, m int
  569 +
  570 + // 0x00 [1 byte]
  571 +
  572 + // Affected rows [Length Coded Binary]
  573 + mc.affectedRows, _, n = readLengthEncodedInteger(data[1:])
  574 +
  575 + // Insert id [Length Coded Binary]
  576 + mc.insertId, _, m = readLengthEncodedInteger(data[1+n:])
  577 +
  578 + // server_status [2 bytes]
  579 + mc.status = readStatus(data[1+n+m : 1+n+m+2])
  580 + if err := mc.discardResults(); err != nil {
  581 + return err
  582 + }
  583 +
  584 + // warning count [2 bytes]
  585 + if !mc.strict {
  586 + return nil
  587 + }
  588 +
  589 + pos := 1 + n + m + 2
  590 + if binary.LittleEndian.Uint16(data[pos:pos+2]) > 0 {
  591 + return mc.getWarnings()
  592 + }
  593 + return nil
  594 +}
  595 +
  596 +// Read Packets as Field Packets until EOF-Packet or an Error appears
  597 +// http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnDefinition41
  598 +func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
  599 + columns := make([]mysqlField, count)
  600 +
  601 + for i := 0; ; i++ {
  602 + data, err := mc.readPacket()
  603 + if err != nil {
  604 + return nil, err
  605 + }
  606 +
  607 + // EOF Packet
  608 + if data[0] == iEOF && (len(data) == 5 || len(data) == 1) {
  609 + if i == count {
  610 + return columns, nil
  611 + }
  612 + return nil, fmt.Errorf("column count mismatch n:%d len:%d", count, len(columns))
  613 + }
  614 +
  615 + // Catalog
  616 + pos, err := skipLengthEncodedString(data)
  617 + if err != nil {
  618 + return nil, err
  619 + }
  620 +
  621 + // Database [len coded string]
  622 + n, err := skipLengthEncodedString(data[pos:])
  623 + if err != nil {
  624 + return nil, err
  625 + }
  626 + pos += n
  627 +
  628 + // Table [len coded string]
  629 + if mc.cfg.ColumnsWithAlias {
  630 + tableName, _, n, err := readLengthEncodedString(data[pos:])
  631 + if err != nil {
  632 + return nil, err
  633 + }
  634 + pos += n
  635 + columns[i].tableName = string(tableName)
  636 + } else {
  637 + n, err = skipLengthEncodedString(data[pos:])
  638 + if err != nil {
  639 + return nil, err
  640 + }
  641 + pos += n
  642 + }
  643 +
  644 + // Original table [len coded string]
  645 + n, err = skipLengthEncodedString(data[pos:])
  646 + if err != nil {
  647 + return nil, err
  648 + }
  649 + pos += n
  650 +
  651 + // Name [len coded string]
  652 + name, _, n, err := readLengthEncodedString(data[pos:])
  653 + if err != nil {
  654 + return nil, err
  655 + }
  656 + columns[i].name = string(name)
  657 + pos += n
  658 +
  659 + // Original name [len coded string]
  660 + n, err = skipLengthEncodedString(data[pos:])
  661 + if err != nil {
  662 + return nil, err
  663 + }
  664 +
  665 + // Filler [uint8]
  666 + // Charset [charset, collation uint8]
  667 + // Length [uint32]
  668 + pos += n + 1 + 2 + 4
  669 +
  670 + // Field type [uint8]
  671 + columns[i].fieldType = data[pos]
  672 + pos++
  673 +
  674 + // Flags [uint16]
  675 + columns[i].flags = fieldFlag(binary.LittleEndian.Uint16(data[pos : pos+2]))
  676 + pos += 2
  677 +
  678 + // Decimals [uint8]
  679 + columns[i].decimals = data[pos]
  680 + //pos++
  681 +
  682 + // Default value [len coded binary]
  683 + //if pos < len(data) {
  684 + // defaultVal, _, err = bytesToLengthCodedBinary(data[pos:])
  685 + //}
  686 + }
  687 +}
  688 +
  689 +// Read Packets as Field Packets until EOF-Packet or an Error appears
  690 +// http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-ProtocolText::ResultsetRow
  691 +func (rows *textRows) readRow(dest []driver.Value) error {
  692 + mc := rows.mc
  693 +
  694 + data, err := mc.readPacket()
  695 + if err != nil {
  696 + return err
  697 + }
  698 +
  699 + // EOF Packet
  700 + if data[0] == iEOF && len(data) == 5 {
  701 + // server_status [2 bytes]
  702 + rows.mc.status = readStatus(data[3:])
  703 + err = rows.mc.discardResults()
  704 + if err == nil {
  705 + err = io.EOF
  706 + } else {
  707 + // connection unusable
  708 + rows.mc.Close()
  709 + }
  710 + rows.mc = nil
  711 + return err
  712 + }
  713 + if data[0] == iERR {
  714 + rows.mc = nil
  715 + return mc.handleErrorPacket(data)
  716 + }
  717 +
  718 + // RowSet Packet
  719 + var n int
  720 + var isNull bool
  721 + pos := 0
  722 +
  723 + for i := range dest {
  724 + // Read bytes and convert to string
  725 + dest[i], isNull, n, err = readLengthEncodedString(data[pos:])
  726 + pos += n
  727 + if err == nil {
  728 + if !isNull {
  729 + if !mc.parseTime {
  730 + continue
  731 + } else {
  732 + switch rows.columns[i].fieldType {
  733 + case fieldTypeTimestamp, fieldTypeDateTime,
  734 + fieldTypeDate, fieldTypeNewDate:
  735 + dest[i], err = parseDateTime(
  736 + string(dest[i].([]byte)),
  737 + mc.cfg.Loc,
  738 + )
  739 + if err == nil {
  740 + continue
  741 + }
  742 + default:
  743 + continue
  744 + }
  745 + }
  746 +
  747 + } else {
  748 + dest[i] = nil
  749 + continue
  750 + }
  751 + }
  752 + return err // err != nil
  753 + }
  754 +
  755 + return nil
  756 +}
  757 +
  758 +// Reads Packets until EOF-Packet or an Error appears. Returns count of Packets read
  759 +func (mc *mysqlConn) readUntilEOF() error {
  760 + for {
  761 + data, err := mc.readPacket()
  762 + if err != nil {
  763 + return err
  764 + }
  765 +
  766 + switch data[0] {
  767 + case iERR:
  768 + return mc.handleErrorPacket(data)
  769 + case iEOF:
  770 + if len(data) == 5 {
  771 + mc.status = readStatus(data[3:])
  772 + }
  773 + return nil
  774 + }
  775 + }
  776 +}
  777 +
  778 +/******************************************************************************
  779 +* Prepared Statements *
  780 +******************************************************************************/
  781 +
  782 +// Prepare Result Packets
  783 +// http://dev.mysql.com/doc/internals/en/com-stmt-prepare-response.html
  784 +func (stmt *mysqlStmt) readPrepareResultPacket() (uint16, error) {
  785 + data, err := stmt.mc.readPacket()
  786 + if err == nil {
  787 + // packet indicator [1 byte]
  788 + if data[0] != iOK {
  789 + return 0, stmt.mc.handleErrorPacket(data)
  790 + }
  791 +
  792 + // statement id [4 bytes]
  793 + stmt.id = binary.LittleEndian.Uint32(data[1:5])
  794 +
  795 + // Column count [16 bit uint]
  796 + columnCount := binary.LittleEndian.Uint16(data[5:7])
  797 +
  798 + // Param count [16 bit uint]
  799 + stmt.paramCount = int(binary.LittleEndian.Uint16(data[7:9]))
  800 +
  801 + // Reserved [8 bit]
  802 +
  803 + // Warning count [16 bit uint]
  804 + if !stmt.mc.strict {
  805 + return columnCount, nil
  806 + }
  807 +
  808 + // Check for warnings count > 0, only available in MySQL > 4.1
  809 + if len(data) >= 12 && binary.LittleEndian.Uint16(data[10:12]) > 0 {
  810 + return columnCount, stmt.mc.getWarnings()
  811 + }
  812 + return columnCount, nil
  813 + }
  814 + return 0, err
  815 +}
  816 +
  817 +// http://dev.mysql.com/doc/internals/en/com-stmt-send-long-data.html
  818 +func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error {
  819 + maxLen := stmt.mc.maxAllowedPacket - 1
  820 + pktLen := maxLen
  821 +
  822 + // After the header (bytes 0-3) follows before the data:
  823 + // 1 byte command
  824 + // 4 bytes stmtID
  825 + // 2 bytes paramID
  826 + const dataOffset = 1 + 4 + 2
  827 +
  828 + // Can not use the write buffer since
  829 + // a) the buffer is too small
  830 + // b) it is in use
  831 + data := make([]byte, 4+1+4+2+len(arg))
  832 +
  833 + copy(data[4+dataOffset:], arg)
  834 +
  835 + for argLen := len(arg); argLen > 0; argLen -= pktLen - dataOffset {
  836 + if dataOffset+argLen < maxLen {
  837 + pktLen = dataOffset + argLen
  838 + }
  839 +
  840 + stmt.mc.sequence = 0
  841 + // Add command byte [1 byte]
  842 + data[4] = comStmtSendLongData
  843 +
  844 + // Add stmtID [32 bit]
  845 + data[5] = byte(stmt.id)
  846 + data[6] = byte(stmt.id >> 8)
  847 + data[7] = byte(stmt.id >> 16)
  848 + data[8] = byte(stmt.id >> 24)
  849 +
  850 + // Add paramID [16 bit]
  851 + data[9] = byte(paramID)
  852 + data[10] = byte(paramID >> 8)
  853 +
  854 + // Send CMD packet
  855 + err := stmt.mc.writePacket(data[:4+pktLen])
  856 + if err == nil {
  857 + data = data[pktLen-dataOffset:]
  858 + continue
  859 + }
  860 + return err
  861 +
  862 + }
  863 +
  864 + // Reset Packet Sequence
  865 + stmt.mc.sequence = 0
  866 + return nil
  867 +}
  868 +
  869 +// Execute Prepared Statement
  870 +// http://dev.mysql.com/doc/internals/en/com-stmt-execute.html
  871 +func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
  872 + if len(args) != stmt.paramCount {
  873 + return fmt.Errorf(
  874 + "argument count mismatch (got: %d; has: %d)",
  875 + len(args),
  876 + stmt.paramCount,
  877 + )
  878 + }
  879 +
  880 + const minPktLen = 4 + 1 + 4 + 1 + 4
  881 + mc := stmt.mc
  882 +
  883 + // Reset packet-sequence
  884 + mc.sequence = 0
  885 +
  886 + var data []byte
  887 +
  888 + if len(args) == 0 {
  889 + data = mc.buf.takeBuffer(minPktLen)
  890 + } else {
  891 + data = mc.buf.takeCompleteBuffer()
  892 + }
  893 + if data == nil {
  894 + // can not take the buffer. Something must be wrong with the connection
  895 + errLog.Print(ErrBusyBuffer)
  896 + return driver.ErrBadConn
  897 + }
  898 +
  899 + // command [1 byte]
  900 + data[4] = comStmtExecute
  901 +
  902 + // statement_id [4 bytes]
  903 + data[5] = byte(stmt.id)
  904 + data[6] = byte(stmt.id >> 8)
  905 + data[7] = byte(stmt.id >> 16)
  906 + data[8] = byte(stmt.id >> 24)
  907 +
  908 + // flags (0: CURSOR_TYPE_NO_CURSOR) [1 byte]
  909 + data[9] = 0x00
  910 +
  911 + // iteration_count (uint32(1)) [4 bytes]
  912 + data[10] = 0x01
  913 + data[11] = 0x00
  914 + data[12] = 0x00
  915 + data[13] = 0x00
  916 +
  917 + if len(args) > 0 {
  918 + pos := minPktLen
  919 +
  920 + var nullMask []byte
  921 + if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= len(data) {
  922 + // buffer has to be extended but we don't know by how much so
  923 + // we depend on append after all data with known sizes fit.
  924 + // We stop at that because we deal with a lot of columns here
  925 + // which makes the required allocation size hard to guess.
  926 + tmp := make([]byte, pos+maskLen+typesLen)
  927 + copy(tmp[:pos], data[:pos])
  928 + data = tmp
  929 + nullMask = data[pos : pos+maskLen]
  930 + pos += maskLen
  931 + } else {
  932 + nullMask = data[pos : pos+maskLen]
  933 + for i := 0; i < maskLen; i++ {
  934 + nullMask[i] = 0
  935 + }
  936 + pos += maskLen
  937 + }
  938 +
  939 + // newParameterBoundFlag 1 [1 byte]
  940 + data[pos] = 0x01
  941 + pos++
  942 +
  943 + // type of each parameter [len(args)*2 bytes]
  944 + paramTypes := data[pos:]
  945 + pos += len(args) * 2
  946 +
  947 + // value of each parameter [n bytes]
  948 + paramValues := data[pos:pos]
  949 + valuesCap := cap(paramValues)
  950 +
  951 + for i, arg := range args {
  952 + // build NULL-bitmap
  953 + if arg == nil {
  954 + nullMask[i/8] |= 1 << (uint(i) & 7)
  955 + paramTypes[i+i] = fieldTypeNULL
  956 + paramTypes[i+i+1] = 0x00
  957 + continue
  958 + }
  959 +
  960 + // cache types and values
  961 + switch v := arg.(type) {
  962 + case int64:
  963 + paramTypes[i+i] = fieldTypeLongLong
  964 + paramTypes[i+i+1] = 0x00
  965 +
  966 + if cap(paramValues)-len(paramValues)-8 >= 0 {
  967 + paramValues = paramValues[:len(paramValues)+8]
  968 + binary.LittleEndian.PutUint64(
  969 + paramValues[len(paramValues)-8:],
  970 + uint64(v),
  971 + )
  972 + } else {
  973 + paramValues = append(paramValues,
  974 + uint64ToBytes(uint64(v))...,
  975 + )
  976 + }
  977 +
  978 + case float64:
  979 + paramTypes[i+i] = fieldTypeDouble
  980 + paramTypes[i+i+1] = 0x00
  981 +
  982 + if cap(paramValues)-len(paramValues)-8 >= 0 {
  983 + paramValues = paramValues[:len(paramValues)+8]
  984 + binary.LittleEndian.PutUint64(
  985 + paramValues[len(paramValues)-8:],
  986 + math.Float64bits(v),
  987 + )
  988 + } else {
  989 + paramValues = append(paramValues,
  990 + uint64ToBytes(math.Float64bits(v))...,
  991 + )
  992 + }
  993 +
  994 + case bool:
  995 + paramTypes[i+i] = fieldTypeTiny
  996 + paramTypes[i+i+1] = 0x00
  997 +
  998 + if v {
  999 + paramValues = append(paramValues, 0x01)
  1000 + } else {
  1001 + paramValues = append(paramValues, 0x00)
  1002 + }
  1003 +
  1004 + case []byte:
  1005 + // Common case (non-nil value) first
  1006 + if v != nil {
  1007 + paramTypes[i+i] = fieldTypeString
  1008 + paramTypes[i+i+1] = 0x00
  1009 +
  1010 + if len(v) < mc.maxAllowedPacket-pos-len(paramValues)-(len(args)-(i+1))*64 {
  1011 + paramValues = appendLengthEncodedInteger(paramValues,
  1012 + uint64(len(v)),
  1013 + )
  1014 + paramValues = append(paramValues, v...)
  1015 + } else {
  1016 + if err := stmt.writeCommandLongData(i, v); err != nil {
  1017 + return err
  1018 + }
  1019 + }
  1020 + continue
  1021 + }
  1022 +
  1023 + // Handle []byte(nil) as a NULL value
  1024 + nullMask[i/8] |= 1 << (uint(i) & 7)
  1025 + paramTypes[i+i] = fieldTypeNULL
  1026 + paramTypes[i+i+1] = 0x00
  1027 +
  1028 + case string:
  1029 + paramTypes[i+i] = fieldTypeString
  1030 + paramTypes[i+i+1] = 0x00
  1031 +
  1032 + if len(v) < mc.maxAllowedPacket-pos-len(paramValues)-(len(args)-(i+1))*64 {
  1033 + paramValues = appendLengthEncodedInteger(paramValues,
  1034 + uint64(len(v)),
  1035 + )
  1036 + paramValues = append(paramValues, v...)
  1037 + } else {
  1038 + if err := stmt.writeCommandLongData(i, []byte(v)); err != nil {
  1039 + return err
  1040 + }
  1041 + }
  1042 +
  1043 + case time.Time:
  1044 + paramTypes[i+i] = fieldTypeString
  1045 + paramTypes[i+i+1] = 0x00
  1046 +
  1047 + var val []byte
  1048 + if v.IsZero() {
  1049 + val = []byte("0000-00-00")
  1050 + } else {
  1051 + val = []byte(v.In(mc.cfg.Loc).Format(timeFormat))
  1052 + }
  1053 +
  1054 + paramValues = appendLengthEncodedInteger(paramValues,
  1055 + uint64(len(val)),
  1056 + )
  1057 + paramValues = append(paramValues, val...)
  1058 +
  1059 + default:
  1060 + return fmt.Errorf("can not convert type: %T", arg)
  1061 + }
  1062 + }
  1063 +
  1064 + // Check if param values exceeded the available buffer
  1065 + // In that case we must build the data packet with the new values buffer
  1066 + if valuesCap != cap(paramValues) {
  1067 + data = append(data[:pos], paramValues...)
  1068 + mc.buf.buf = data
  1069 + }
  1070 +
  1071 + pos += len(paramValues)
  1072 + data = data[:pos]
  1073 + }
  1074 +
  1075 + return mc.writePacket(data)
  1076 +}
  1077 +
  1078 +func (mc *mysqlConn) discardResults() error {
  1079 + for mc.status&statusMoreResultsExists != 0 {
  1080 + resLen, err := mc.readResultSetHeaderPacket()
  1081 + if err != nil {
  1082 + return err
  1083 + }
  1084 + if resLen > 0 {
  1085 + // columns
  1086 + if err := mc.readUntilEOF(); err != nil {
  1087 + return err
  1088 + }
  1089 + // rows
  1090 + if err := mc.readUntilEOF(); err != nil {
  1091 + return err
  1092 + }
  1093 + } else {
  1094 + mc.status &^= statusMoreResultsExists
  1095 + }
  1096 + }
  1097 + return nil
  1098 +}
  1099 +
  1100 +// http://dev.mysql.com/doc/internals/en/binary-protocol-resultset-row.html
  1101 +func (rows *binaryRows) readRow(dest []driver.Value) error {
  1102 + data, err := rows.mc.readPacket()
  1103 + if err != nil {
  1104 + return err
  1105 + }
  1106 +
  1107 + // packet indicator [1 byte]
  1108 + if data[0] != iOK {
  1109 + // EOF Packet
  1110 + if data[0] == iEOF && len(data) == 5 {
  1111 + rows.mc.status = readStatus(data[3:])
  1112 + err = rows.mc.discardResults()
  1113 + if err == nil {
  1114 + err = io.EOF
  1115 + } else {
  1116 + // connection unusable
  1117 + rows.mc.Close()
  1118 + }
  1119 + rows.mc = nil
  1120 + return err
  1121 + }
  1122 + rows.mc = nil
  1123 +
  1124 + // Error otherwise
  1125 + return rows.mc.handleErrorPacket(data)
  1126 + }
  1127 +
  1128 + // NULL-bitmap, [(column-count + 7 + 2) / 8 bytes]
  1129 + pos := 1 + (len(dest)+7+2)>>3
  1130 + nullMask := data[1:pos]
  1131 +
  1132 + for i := range dest {
  1133 + // Field is NULL
  1134 + // (byte >> bit-pos) % 2 == 1
  1135 + if ((nullMask[(i+2)>>3] >> uint((i+2)&7)) & 1) == 1 {
  1136 + dest[i] = nil
  1137 + continue
  1138 + }
  1139 +
  1140 + // Convert to byte-coded string
  1141 + switch rows.columns[i].fieldType {
  1142 + case fieldTypeNULL:
  1143 + dest[i] = nil
  1144 + continue
  1145 +
  1146 + // Numeric Types
  1147 + case fieldTypeTiny:
  1148 + if rows.columns[i].flags&flagUnsigned != 0 {
  1149 + dest[i] = int64(data[pos])
  1150 + } else {
  1151 + dest[i] = int64(int8(data[pos]))
  1152 + }
  1153 + pos++
  1154 + continue
  1155 +
  1156 + case fieldTypeShort, fieldTypeYear:
  1157 + if rows.columns[i].flags&flagUnsigned != 0 {
  1158 + dest[i] = int64(binary.LittleEndian.Uint16(data[pos : pos+2]))
  1159 + } else {
  1160 + dest[i] = int64(int16(binary.LittleEndian.Uint16(data[pos : pos+2])))
  1161 + }
  1162 + pos += 2
  1163 + continue
  1164 +
  1165 + case fieldTypeInt24, fieldTypeLong:
  1166 + if rows.columns[i].flags&flagUnsigned != 0 {
  1167 + dest[i] = int64(binary.LittleEndian.Uint32(data[pos : pos+4]))
  1168 + } else {
  1169 + dest[i] = int64(int32(binary.LittleEndian.Uint32(data[pos : pos+4])))
  1170 + }
  1171 + pos += 4
  1172 + continue
  1173 +
  1174 + case fieldTypeLongLong:
  1175 + if rows.columns[i].flags&flagUnsigned != 0 {
  1176 + val := binary.LittleEndian.Uint64(data[pos : pos+8])
  1177 + if val > math.MaxInt64 {
  1178 + dest[i] = uint64ToString(val)
  1179 + } else {
  1180 + dest[i] = int64(val)
  1181 + }
  1182 + } else {
  1183 + dest[i] = int64(binary.LittleEndian.Uint64(data[pos : pos+8]))
  1184 + }
  1185 + pos += 8
  1186 + continue
  1187 +
  1188 + case fieldTypeFloat:
  1189 + dest[i] = float32(math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4])))
  1190 + pos += 4
  1191 + continue
  1192 +
  1193 + case fieldTypeDouble:
  1194 + dest[i] = math.Float64frombits(binary.LittleEndian.Uint64(data[pos : pos+8]))
  1195 + pos += 8
  1196 + continue
  1197 +
  1198 + // Length coded Binary Strings
  1199 + case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar,
  1200 + fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB,
  1201 + fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB,
  1202 + fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON:
  1203 + var isNull bool
  1204 + var n int
  1205 + dest[i], isNull, n, err = readLengthEncodedString(data[pos:])
  1206 + pos += n
  1207 + if err == nil {
  1208 + if !isNull {
  1209 + continue
  1210 + } else {
  1211 + dest[i] = nil
  1212 + continue
  1213 + }
  1214 + }
  1215 + return err
  1216 +
  1217 + case
  1218 + fieldTypeDate, fieldTypeNewDate, // Date YYYY-MM-DD
  1219 + fieldTypeTime, // Time [-][H]HH:MM:SS[.fractal]
  1220 + fieldTypeTimestamp, fieldTypeDateTime: // Timestamp YYYY-MM-DD HH:MM:SS[.fractal]
  1221 +
  1222 + num, isNull, n := readLengthEncodedInteger(data[pos:])
  1223 + pos += n
  1224 +
  1225 + switch {
  1226 + case isNull:
  1227 + dest[i] = nil
  1228 + continue
  1229 + case rows.columns[i].fieldType == fieldTypeTime:
  1230 + // database/sql does not support an equivalent to TIME, return a string
  1231 + var dstlen uint8
  1232 + switch decimals := rows.columns[i].decimals; decimals {
  1233 + case 0x00, 0x1f:
  1234 + dstlen = 8
  1235 + case 1, 2, 3, 4, 5, 6:
  1236 + dstlen = 8 + 1 + decimals
  1237 + default:
  1238 + return fmt.Errorf(
  1239 + "protocol error, illegal decimals value %d",
  1240 + rows.columns[i].decimals,
  1241 + )
  1242 + }
  1243 + dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true)
  1244 + case rows.mc.parseTime:
  1245 + dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc)
  1246 + default:
  1247 + var dstlen uint8
  1248 + if rows.columns[i].fieldType == fieldTypeDate {
  1249 + dstlen = 10
  1250 + } else {
  1251 + switch decimals := rows.columns[i].decimals; decimals {
  1252 + case 0x00, 0x1f:
  1253 + dstlen = 19
  1254 + case 1, 2, 3, 4, 5, 6:
  1255 + dstlen = 19 + 1 + decimals
  1256 + default:
  1257 + return fmt.Errorf(
  1258 + "protocol error, illegal decimals value %d",
  1259 + rows.columns[i].decimals,
  1260 + )
  1261 + }
  1262 + }
  1263 + dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, false)
  1264 + }
  1265 +
  1266 + if err == nil {
  1267 + pos += int(num)
  1268 + continue
  1269 + } else {
  1270 + return err
  1271 + }
  1272 +
  1273 + // Please report if this happens!
  1274 + default:
  1275 + return fmt.Errorf("unknown field type %d", rows.columns[i].fieldType)
  1276 + }
  1277 + }
  1278 +
  1279 + return nil
  1280 +}
... ...
src/github.com/go-sql-driver/mysql/result.go 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +type mysqlResult struct {
  12 + affectedRows int64
  13 + insertId int64
  14 +}
  15 +
  16 +func (res *mysqlResult) LastInsertId() (int64, error) {
  17 + return res.insertId, nil
  18 +}
  19 +
  20 +func (res *mysqlResult) RowsAffected() (int64, error) {
  21 + return res.affectedRows, nil
  22 +}
... ...
src/github.com/go-sql-driver/mysql/rows.go 0 → 100644
... ... @@ -0,0 +1,112 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +import (
  12 + "database/sql/driver"
  13 + "io"
  14 +)
  15 +
  16 +type mysqlField struct {
  17 + tableName string
  18 + name string
  19 + flags fieldFlag
  20 + fieldType byte
  21 + decimals byte
  22 +}
  23 +
  24 +type mysqlRows struct {
  25 + mc *mysqlConn
  26 + columns []mysqlField
  27 +}
  28 +
  29 +type binaryRows struct {
  30 + mysqlRows
  31 +}
  32 +
  33 +type textRows struct {
  34 + mysqlRows
  35 +}
  36 +
  37 +type emptyRows struct{}
  38 +
  39 +func (rows *mysqlRows) Columns() []string {
  40 + columns := make([]string, len(rows.columns))
  41 + if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias {
  42 + for i := range columns {
  43 + if tableName := rows.columns[i].tableName; len(tableName) > 0 {
  44 + columns[i] = tableName + "." + rows.columns[i].name
  45 + } else {
  46 + columns[i] = rows.columns[i].name
  47 + }
  48 + }
  49 + } else {
  50 + for i := range columns {
  51 + columns[i] = rows.columns[i].name
  52 + }
  53 + }
  54 + return columns
  55 +}
  56 +
  57 +func (rows *mysqlRows) Close() error {
  58 + mc := rows.mc
  59 + if mc == nil {
  60 + return nil
  61 + }
  62 + if mc.netConn == nil {
  63 + return ErrInvalidConn
  64 + }
  65 +
  66 + // Remove unread packets from stream
  67 + err := mc.readUntilEOF()
  68 + if err == nil {
  69 + if err = mc.discardResults(); err != nil {
  70 + return err
  71 + }
  72 + }
  73 +
  74 + rows.mc = nil
  75 + return err
  76 +}
  77 +
  78 +func (rows *binaryRows) Next(dest []driver.Value) error {
  79 + if mc := rows.mc; mc != nil {
  80 + if mc.netConn == nil {
  81 + return ErrInvalidConn
  82 + }
  83 +
  84 + // Fetch next row from stream
  85 + return rows.readRow(dest)
  86 + }
  87 + return io.EOF
  88 +}
  89 +
  90 +func (rows *textRows) Next(dest []driver.Value) error {
  91 + if mc := rows.mc; mc != nil {
  92 + if mc.netConn == nil {
  93 + return ErrInvalidConn
  94 + }
  95 +
  96 + // Fetch next row from stream
  97 + return rows.readRow(dest)
  98 + }
  99 + return io.EOF
  100 +}
  101 +
  102 +func (rows emptyRows) Columns() []string {
  103 + return nil
  104 +}
  105 +
  106 +func (rows emptyRows) Close() error {
  107 + return nil
  108 +}
  109 +
  110 +func (rows emptyRows) Next(dest []driver.Value) error {
  111 + return io.EOF
  112 +}
... ...
src/github.com/go-sql-driver/mysql/statement.go 0 → 100644
... ... @@ -0,0 +1,153 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +import (
  12 + "database/sql/driver"
  13 + "fmt"
  14 + "reflect"
  15 + "strconv"
  16 +)
  17 +
  18 +type mysqlStmt struct {
  19 + mc *mysqlConn
  20 + id uint32
  21 + paramCount int
  22 + columns []mysqlField // cached from the first query
  23 +}
  24 +
  25 +func (stmt *mysqlStmt) Close() error {
  26 + if stmt.mc == nil || stmt.mc.netConn == nil {
  27 + // driver.Stmt.Close can be called more than once, thus this function
  28 + // has to be idempotent.
  29 + // See also Issue #450 and golang/go#16019.
  30 + //errLog.Print(ErrInvalidConn)
  31 + return driver.ErrBadConn
  32 + }
  33 +
  34 + err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id)
  35 + stmt.mc = nil
  36 + return err
  37 +}
  38 +
  39 +func (stmt *mysqlStmt) NumInput() int {
  40 + return stmt.paramCount
  41 +}
  42 +
  43 +func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
  44 + return converter{}
  45 +}
  46 +
  47 +func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
  48 + if stmt.mc.netConn == nil {
  49 + errLog.Print(ErrInvalidConn)
  50 + return nil, driver.ErrBadConn
  51 + }
  52 + // Send command
  53 + err := stmt.writeExecutePacket(args)
  54 + if err != nil {
  55 + return nil, err
  56 + }
  57 +
  58 + mc := stmt.mc
  59 +
  60 + mc.affectedRows = 0
  61 + mc.insertId = 0
  62 +
  63 + // Read Result
  64 + resLen, err := mc.readResultSetHeaderPacket()
  65 + if err == nil {
  66 + if resLen > 0 {
  67 + // Columns
  68 + err = mc.readUntilEOF()
  69 + if err != nil {
  70 + return nil, err
  71 + }
  72 +
  73 + // Rows
  74 + err = mc.readUntilEOF()
  75 + }
  76 + if err == nil {
  77 + return &mysqlResult{
  78 + affectedRows: int64(mc.affectedRows),
  79 + insertId: int64(mc.insertId),
  80 + }, nil
  81 + }
  82 + }
  83 +
  84 + return nil, err
  85 +}
  86 +
  87 +func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
  88 + if stmt.mc.netConn == nil {
  89 + errLog.Print(ErrInvalidConn)
  90 + return nil, driver.ErrBadConn
  91 + }
  92 + // Send command
  93 + err := stmt.writeExecutePacket(args)
  94 + if err != nil {
  95 + return nil, err
  96 + }
  97 +
  98 + mc := stmt.mc
  99 +
  100 + // Read Result
  101 + resLen, err := mc.readResultSetHeaderPacket()
  102 + if err != nil {
  103 + return nil, err
  104 + }
  105 +
  106 + rows := new(binaryRows)
  107 +
  108 + if resLen > 0 {
  109 + rows.mc = mc
  110 + // Columns
  111 + // If not cached, read them and cache them
  112 + if stmt.columns == nil {
  113 + rows.columns, err = mc.readColumns(resLen)
  114 + stmt.columns = rows.columns
  115 + } else {
  116 + rows.columns = stmt.columns
  117 + err = mc.readUntilEOF()
  118 + }
  119 + }
  120 +
  121 + return rows, err
  122 +}
  123 +
  124 +type converter struct{}
  125 +
  126 +func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
  127 + if driver.IsValue(v) {
  128 + return v, nil
  129 + }
  130 +
  131 + rv := reflect.ValueOf(v)
  132 + switch rv.Kind() {
  133 + case reflect.Ptr:
  134 + // indirect pointers
  135 + if rv.IsNil() {
  136 + return nil, nil
  137 + }
  138 + return c.ConvertValue(rv.Elem().Interface())
  139 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  140 + return rv.Int(), nil
  141 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
  142 + return int64(rv.Uint()), nil
  143 + case reflect.Uint64:
  144 + u64 := rv.Uint()
  145 + if u64 >= 1<<63 {
  146 + return strconv.FormatUint(u64, 10), nil
  147 + }
  148 + return int64(u64), nil
  149 + case reflect.Float32, reflect.Float64:
  150 + return rv.Float(), nil
  151 + }
  152 + return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
  153 +}
... ...
src/github.com/go-sql-driver/mysql/transaction.go 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +type mysqlTx struct {
  12 + mc *mysqlConn
  13 +}
  14 +
  15 +func (tx *mysqlTx) Commit() (err error) {
  16 + if tx.mc == nil || tx.mc.netConn == nil {
  17 + return ErrInvalidConn
  18 + }
  19 + err = tx.mc.exec("COMMIT")
  20 + tx.mc = nil
  21 + return
  22 +}
  23 +
  24 +func (tx *mysqlTx) Rollback() (err error) {
  25 + if tx.mc == nil || tx.mc.netConn == nil {
  26 + return ErrInvalidConn
  27 + }
  28 + err = tx.mc.exec("ROLLBACK")
  29 + tx.mc = nil
  30 + return
  31 +}
... ...
src/github.com/go-sql-driver/mysql/utils.go 0 → 100644
... ... @@ -0,0 +1,740 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +import (
  12 + "crypto/sha1"
  13 + "crypto/tls"
  14 + "database/sql/driver"
  15 + "encoding/binary"
  16 + "fmt"
  17 + "io"
  18 + "strings"
  19 + "time"
  20 +)
  21 +
  22 +var (
  23 + tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
  24 +)
  25 +
  26 +// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
  27 +// Use the key as a value in the DSN where tls=value.
  28 +//
  29 +// rootCertPool := x509.NewCertPool()
  30 +// pem, err := ioutil.ReadFile("/path/ca-cert.pem")
  31 +// if err != nil {
  32 +// log.Fatal(err)
  33 +// }
  34 +// if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
  35 +// log.Fatal("Failed to append PEM.")
  36 +// }
  37 +// clientCert := make([]tls.Certificate, 0, 1)
  38 +// certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem")
  39 +// if err != nil {
  40 +// log.Fatal(err)
  41 +// }
  42 +// clientCert = append(clientCert, certs)
  43 +// mysql.RegisterTLSConfig("custom", &tls.Config{
  44 +// RootCAs: rootCertPool,
  45 +// Certificates: clientCert,
  46 +// })
  47 +// db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
  48 +//
  49 +func RegisterTLSConfig(key string, config *tls.Config) error {
  50 + if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" {
  51 + return fmt.Errorf("key '%s' is reserved", key)
  52 + }
  53 +
  54 + if tlsConfigRegister == nil {
  55 + tlsConfigRegister = make(map[string]*tls.Config)
  56 + }
  57 +
  58 + tlsConfigRegister[key] = config
  59 + return nil
  60 +}
  61 +
  62 +// DeregisterTLSConfig removes the tls.Config associated with key.
  63 +func DeregisterTLSConfig(key string) {
  64 + if tlsConfigRegister != nil {
  65 + delete(tlsConfigRegister, key)
  66 + }
  67 +}
  68 +
  69 +// Returns the bool value of the input.
  70 +// The 2nd return value indicates if the input was a valid bool value
  71 +func readBool(input string) (value bool, valid bool) {
  72 + switch input {
  73 + case "1", "true", "TRUE", "True":
  74 + return true, true
  75 + case "0", "false", "FALSE", "False":
  76 + return false, true
  77 + }
  78 +
  79 + // Not a valid bool value
  80 + return
  81 +}
  82 +
  83 +/******************************************************************************
  84 +* Authentication *
  85 +******************************************************************************/
  86 +
  87 +// Encrypt password using 4.1+ method
  88 +func scramblePassword(scramble, password []byte) []byte {
  89 + if len(password) == 0 {
  90 + return nil
  91 + }
  92 +
  93 + // stage1Hash = SHA1(password)
  94 + crypt := sha1.New()
  95 + crypt.Write(password)
  96 + stage1 := crypt.Sum(nil)
  97 +
  98 + // scrambleHash = SHA1(scramble + SHA1(stage1Hash))
  99 + // inner Hash
  100 + crypt.Reset()
  101 + crypt.Write(stage1)
  102 + hash := crypt.Sum(nil)
  103 +
  104 + // outer Hash
  105 + crypt.Reset()
  106 + crypt.Write(scramble)
  107 + crypt.Write(hash)
  108 + scramble = crypt.Sum(nil)
  109 +
  110 + // token = scrambleHash XOR stage1Hash
  111 + for i := range scramble {
  112 + scramble[i] ^= stage1[i]
  113 + }
  114 + return scramble
  115 +}
  116 +
  117 +// Encrypt password using pre 4.1 (old password) method
  118 +// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
  119 +type myRnd struct {
  120 + seed1, seed2 uint32
  121 +}
  122 +
  123 +const myRndMaxVal = 0x3FFFFFFF
  124 +
  125 +// Pseudo random number generator
  126 +func newMyRnd(seed1, seed2 uint32) *myRnd {
  127 + return &myRnd{
  128 + seed1: seed1 % myRndMaxVal,
  129 + seed2: seed2 % myRndMaxVal,
  130 + }
  131 +}
  132 +
  133 +// Tested to be equivalent to MariaDB's floating point variant
  134 +// http://play.golang.org/p/QHvhd4qved
  135 +// http://play.golang.org/p/RG0q4ElWDx
  136 +func (r *myRnd) NextByte() byte {
  137 + r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
  138 + r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
  139 +
  140 + return byte(uint64(r.seed1) * 31 / myRndMaxVal)
  141 +}
  142 +
  143 +// Generate binary hash from byte string using insecure pre 4.1 method
  144 +func pwHash(password []byte) (result [2]uint32) {
  145 + var add uint32 = 7
  146 + var tmp uint32
  147 +
  148 + result[0] = 1345345333
  149 + result[1] = 0x12345671
  150 +
  151 + for _, c := range password {
  152 + // skip spaces and tabs in password
  153 + if c == ' ' || c == '\t' {
  154 + continue
  155 + }
  156 +
  157 + tmp = uint32(c)
  158 + result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
  159 + result[1] += (result[1] << 8) ^ result[0]
  160 + add += tmp
  161 + }
  162 +
  163 + // Remove sign bit (1<<31)-1)
  164 + result[0] &= 0x7FFFFFFF
  165 + result[1] &= 0x7FFFFFFF
  166 +
  167 + return
  168 +}
  169 +
  170 +// Encrypt password using insecure pre 4.1 method
  171 +func scrambleOldPassword(scramble, password []byte) []byte {
  172 + if len(password) == 0 {
  173 + return nil
  174 + }
  175 +
  176 + scramble = scramble[:8]
  177 +
  178 + hashPw := pwHash(password)
  179 + hashSc := pwHash(scramble)
  180 +
  181 + r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
  182 +
  183 + var out [8]byte
  184 + for i := range out {
  185 + out[i] = r.NextByte() + 64
  186 + }
  187 +
  188 + mask := r.NextByte()
  189 + for i := range out {
  190 + out[i] ^= mask
  191 + }
  192 +
  193 + return out[:]
  194 +}
  195 +
  196 +/******************************************************************************
  197 +* Time related utils *
  198 +******************************************************************************/
  199 +
  200 +// NullTime represents a time.Time that may be NULL.
  201 +// NullTime implements the Scanner interface so
  202 +// it can be used as a scan destination:
  203 +//
  204 +// var nt NullTime
  205 +// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
  206 +// ...
  207 +// if nt.Valid {
  208 +// // use nt.Time
  209 +// } else {
  210 +// // NULL value
  211 +// }
  212 +//
  213 +// This NullTime implementation is not driver-specific
  214 +type NullTime struct {
  215 + Time time.Time
  216 + Valid bool // Valid is true if Time is not NULL
  217 +}
  218 +
  219 +// Scan implements the Scanner interface.
  220 +// The value type must be time.Time or string / []byte (formatted time-string),
  221 +// otherwise Scan fails.
  222 +func (nt *NullTime) Scan(value interface{}) (err error) {
  223 + if value == nil {
  224 + nt.Time, nt.Valid = time.Time{}, false
  225 + return
  226 + }
  227 +
  228 + switch v := value.(type) {
  229 + case time.Time:
  230 + nt.Time, nt.Valid = v, true
  231 + return
  232 + case []byte:
  233 + nt.Time, err = parseDateTime(string(v), time.UTC)
  234 + nt.Valid = (err == nil)
  235 + return
  236 + case string:
  237 + nt.Time, err = parseDateTime(v, time.UTC)
  238 + nt.Valid = (err == nil)
  239 + return
  240 + }
  241 +
  242 + nt.Valid = false
  243 + return fmt.Errorf("Can't convert %T to time.Time", value)
  244 +}
  245 +
  246 +// Value implements the driver Valuer interface.
  247 +func (nt NullTime) Value() (driver.Value, error) {
  248 + if !nt.Valid {
  249 + return nil, nil
  250 + }
  251 + return nt.Time, nil
  252 +}
  253 +
  254 +func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
  255 + base := "0000-00-00 00:00:00.0000000"
  256 + switch len(str) {
  257 + case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
  258 + if str == base[:len(str)] {
  259 + return
  260 + }
  261 + t, err = time.Parse(timeFormat[:len(str)], str)
  262 + default:
  263 + err = fmt.Errorf("invalid time string: %s", str)
  264 + return
  265 + }
  266 +
  267 + // Adjust location
  268 + if err == nil && loc != time.UTC {
  269 + y, mo, d := t.Date()
  270 + h, mi, s := t.Clock()
  271 + t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil
  272 + }
  273 +
  274 + return
  275 +}
  276 +
  277 +func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) {
  278 + switch num {
  279 + case 0:
  280 + return time.Time{}, nil
  281 + case 4:
  282 + return time.Date(
  283 + int(binary.LittleEndian.Uint16(data[:2])), // year
  284 + time.Month(data[2]), // month
  285 + int(data[3]), // day
  286 + 0, 0, 0, 0,
  287 + loc,
  288 + ), nil
  289 + case 7:
  290 + return time.Date(
  291 + int(binary.LittleEndian.Uint16(data[:2])), // year
  292 + time.Month(data[2]), // month
  293 + int(data[3]), // day
  294 + int(data[4]), // hour
  295 + int(data[5]), // minutes
  296 + int(data[6]), // seconds
  297 + 0,
  298 + loc,
  299 + ), nil
  300 + case 11:
  301 + return time.Date(
  302 + int(binary.LittleEndian.Uint16(data[:2])), // year
  303 + time.Month(data[2]), // month
  304 + int(data[3]), // day
  305 + int(data[4]), // hour
  306 + int(data[5]), // minutes
  307 + int(data[6]), // seconds
  308 + int(binary.LittleEndian.Uint32(data[7:11]))*1000, // nanoseconds
  309 + loc,
  310 + ), nil
  311 + }
  312 + return nil, fmt.Errorf("invalid DATETIME packet length %d", num)
  313 +}
  314 +
  315 +// zeroDateTime is used in formatBinaryDateTime to avoid an allocation
  316 +// if the DATE or DATETIME has the zero value.
  317 +// It must never be changed.
  318 +// The current behavior depends on database/sql copying the result.
  319 +var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
  320 +
  321 +const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
  322 +const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
  323 +
  324 +func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {
  325 + // length expects the deterministic length of the zero value,
  326 + // negative time and 100+ hours are automatically added if needed
  327 + if len(src) == 0 {
  328 + if justTime {
  329 + return zeroDateTime[11 : 11+length], nil
  330 + }
  331 + return zeroDateTime[:length], nil
  332 + }
  333 + var dst []byte // return value
  334 + var pt, p1, p2, p3 byte // current digit pair
  335 + var zOffs byte // offset of value in zeroDateTime
  336 + if justTime {
  337 + switch length {
  338 + case
  339 + 8, // time (can be up to 10 when negative and 100+ hours)
  340 + 10, 11, 12, 13, 14, 15: // time with fractional seconds
  341 + default:
  342 + return nil, fmt.Errorf("illegal TIME length %d", length)
  343 + }
  344 + switch len(src) {
  345 + case 8, 12:
  346 + default:
  347 + return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
  348 + }
  349 + // +2 to enable negative time and 100+ hours
  350 + dst = make([]byte, 0, length+2)
  351 + if src[0] == 1 {
  352 + dst = append(dst, '-')
  353 + }
  354 + if src[1] != 0 {
  355 + hour := uint16(src[1])*24 + uint16(src[5])
  356 + pt = byte(hour / 100)
  357 + p1 = byte(hour - 100*uint16(pt))
  358 + dst = append(dst, digits01[pt])
  359 + } else {
  360 + p1 = src[5]
  361 + }
  362 + zOffs = 11
  363 + src = src[6:]
  364 + } else {
  365 + switch length {
  366 + case 10, 19, 21, 22, 23, 24, 25, 26:
  367 + default:
  368 + t := "DATE"
  369 + if length > 10 {
  370 + t += "TIME"
  371 + }
  372 + return nil, fmt.Errorf("illegal %s length %d", t, length)
  373 + }
  374 + switch len(src) {
  375 + case 4, 7, 11:
  376 + default:
  377 + t := "DATE"
  378 + if length > 10 {
  379 + t += "TIME"
  380 + }
  381 + return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
  382 + }
  383 + dst = make([]byte, 0, length)
  384 + // start with the date
  385 + year := binary.LittleEndian.Uint16(src[:2])
  386 + pt = byte(year / 100)
  387 + p1 = byte(year - 100*uint16(pt))
  388 + p2, p3 = src[2], src[3]
  389 + dst = append(dst,
  390 + digits10[pt], digits01[pt],
  391 + digits10[p1], digits01[p1], '-',
  392 + digits10[p2], digits01[p2], '-',
  393 + digits10[p3], digits01[p3],
  394 + )
  395 + if length == 10 {
  396 + return dst, nil
  397 + }
  398 + if len(src) == 4 {
  399 + return append(dst, zeroDateTime[10:length]...), nil
  400 + }
  401 + dst = append(dst, ' ')
  402 + p1 = src[4] // hour
  403 + src = src[5:]
  404 + }
  405 + // p1 is 2-digit hour, src is after hour
  406 + p2, p3 = src[0], src[1]
  407 + dst = append(dst,
  408 + digits10[p1], digits01[p1], ':',
  409 + digits10[p2], digits01[p2], ':',
  410 + digits10[p3], digits01[p3],
  411 + )
  412 + if length <= byte(len(dst)) {
  413 + return dst, nil
  414 + }
  415 + src = src[2:]
  416 + if len(src) == 0 {
  417 + return append(dst, zeroDateTime[19:zOffs+length]...), nil
  418 + }
  419 + microsecs := binary.LittleEndian.Uint32(src[:4])
  420 + p1 = byte(microsecs / 10000)
  421 + microsecs -= 10000 * uint32(p1)
  422 + p2 = byte(microsecs / 100)
  423 + microsecs -= 100 * uint32(p2)
  424 + p3 = byte(microsecs)
  425 + switch decimals := zOffs + length - 20; decimals {
  426 + default:
  427 + return append(dst, '.',
  428 + digits10[p1], digits01[p1],
  429 + digits10[p2], digits01[p2],
  430 + digits10[p3], digits01[p3],
  431 + ), nil
  432 + case 1:
  433 + return append(dst, '.',
  434 + digits10[p1],
  435 + ), nil
  436 + case 2:
  437 + return append(dst, '.',
  438 + digits10[p1], digits01[p1],
  439 + ), nil
  440 + case 3:
  441 + return append(dst, '.',
  442 + digits10[p1], digits01[p1],
  443 + digits10[p2],
  444 + ), nil
  445 + case 4:
  446 + return append(dst, '.',
  447 + digits10[p1], digits01[p1],
  448 + digits10[p2], digits01[p2],
  449 + ), nil
  450 + case 5:
  451 + return append(dst, '.',
  452 + digits10[p1], digits01[p1],
  453 + digits10[p2], digits01[p2],
  454 + digits10[p3],
  455 + ), nil
  456 + }
  457 +}
  458 +
  459 +/******************************************************************************
  460 +* Convert from and to bytes *
  461 +******************************************************************************/
  462 +
  463 +func uint64ToBytes(n uint64) []byte {
  464 + return []byte{
  465 + byte(n),
  466 + byte(n >> 8),
  467 + byte(n >> 16),
  468 + byte(n >> 24),
  469 + byte(n >> 32),
  470 + byte(n >> 40),
  471 + byte(n >> 48),
  472 + byte(n >> 56),
  473 + }
  474 +}
  475 +
  476 +func uint64ToString(n uint64) []byte {
  477 + var a [20]byte
  478 + i := 20
  479 +
  480 + // U+0030 = 0
  481 + // ...
  482 + // U+0039 = 9
  483 +
  484 + var q uint64
  485 + for n >= 10 {
  486 + i--
  487 + q = n / 10
  488 + a[i] = uint8(n-q*10) + 0x30
  489 + n = q
  490 + }
  491 +
  492 + i--
  493 + a[i] = uint8(n) + 0x30
  494 +
  495 + return a[i:]
  496 +}
  497 +
  498 +// treats string value as unsigned integer representation
  499 +func stringToInt(b []byte) int {
  500 + val := 0
  501 + for i := range b {
  502 + val *= 10
  503 + val += int(b[i] - 0x30)
  504 + }
  505 + return val
  506 +}
  507 +
  508 +// returns the string read as a bytes slice, wheter the value is NULL,
  509 +// the number of bytes read and an error, in case the string is longer than
  510 +// the input slice
  511 +func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
  512 + // Get length
  513 + num, isNull, n := readLengthEncodedInteger(b)
  514 + if num < 1 {
  515 + return b[n:n], isNull, n, nil
  516 + }
  517 +
  518 + n += int(num)
  519 +
  520 + // Check data length
  521 + if len(b) >= n {
  522 + return b[n-int(num) : n], false, n, nil
  523 + }
  524 + return nil, false, n, io.EOF
  525 +}
  526 +
  527 +// returns the number of bytes skipped and an error, in case the string is
  528 +// longer than the input slice
  529 +func skipLengthEncodedString(b []byte) (int, error) {
  530 + // Get length
  531 + num, _, n := readLengthEncodedInteger(b)
  532 + if num < 1 {
  533 + return n, nil
  534 + }
  535 +
  536 + n += int(num)
  537 +
  538 + // Check data length
  539 + if len(b) >= n {
  540 + return n, nil
  541 + }
  542 + return n, io.EOF
  543 +}
  544 +
  545 +// returns the number read, whether the value is NULL and the number of bytes read
  546 +func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
  547 + // See issue #349
  548 + if len(b) == 0 {
  549 + return 0, true, 1
  550 + }
  551 + switch b[0] {
  552 +
  553 + // 251: NULL
  554 + case 0xfb:
  555 + return 0, true, 1
  556 +
  557 + // 252: value of following 2
  558 + case 0xfc:
  559 + return uint64(b[1]) | uint64(b[2])<<8, false, 3
  560 +
  561 + // 253: value of following 3
  562 + case 0xfd:
  563 + return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4
  564 +
  565 + // 254: value of following 8
  566 + case 0xfe:
  567 + return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 |
  568 + uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 |
  569 + uint64(b[7])<<48 | uint64(b[8])<<56,
  570 + false, 9
  571 + }
  572 +
  573 + // 0-250: value of first byte
  574 + return uint64(b[0]), false, 1
  575 +}
  576 +
  577 +// encodes a uint64 value and appends it to the given bytes slice
  578 +func appendLengthEncodedInteger(b []byte, n uint64) []byte {
  579 + switch {
  580 + case n <= 250:
  581 + return append(b, byte(n))
  582 +
  583 + case n <= 0xffff:
  584 + return append(b, 0xfc, byte(n), byte(n>>8))
  585 +
  586 + case n <= 0xffffff:
  587 + return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16))
  588 + }
  589 + return append(b, 0xfe, byte(n), byte(n>>8), byte(n>>16), byte(n>>24),
  590 + byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56))
  591 +}
  592 +
  593 +// reserveBuffer checks cap(buf) and expand buffer to len(buf) + appendSize.
  594 +// If cap(buf) is not enough, reallocate new buffer.
  595 +func reserveBuffer(buf []byte, appendSize int) []byte {
  596 + newSize := len(buf) + appendSize
  597 + if cap(buf) < newSize {
  598 + // Grow buffer exponentially
  599 + newBuf := make([]byte, len(buf)*2+appendSize)
  600 + copy(newBuf, buf)
  601 + buf = newBuf
  602 + }
  603 + return buf[:newSize]
  604 +}
  605 +
  606 +// escapeBytesBackslash escapes []byte with backslashes (\)
  607 +// This escapes the contents of a string (provided as []byte) by adding backslashes before special
  608 +// characters, and turning others into specific escape sequences, such as
  609 +// turning newlines into \n and null bytes into \0.
  610 +// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L823-L932
  611 +func escapeBytesBackslash(buf, v []byte) []byte {
  612 + pos := len(buf)
  613 + buf = reserveBuffer(buf, len(v)*2)
  614 +
  615 + for _, c := range v {
  616 + switch c {
  617 + case '\x00':
  618 + buf[pos] = '\\'
  619 + buf[pos+1] = '0'
  620 + pos += 2
  621 + case '\n':
  622 + buf[pos] = '\\'
  623 + buf[pos+1] = 'n'
  624 + pos += 2
  625 + case '\r':
  626 + buf[pos] = '\\'
  627 + buf[pos+1] = 'r'
  628 + pos += 2
  629 + case '\x1a':
  630 + buf[pos] = '\\'
  631 + buf[pos+1] = 'Z'
  632 + pos += 2
  633 + case '\'':
  634 + buf[pos] = '\\'
  635 + buf[pos+1] = '\''
  636 + pos += 2
  637 + case '"':
  638 + buf[pos] = '\\'
  639 + buf[pos+1] = '"'
  640 + pos += 2
  641 + case '\\':
  642 + buf[pos] = '\\'
  643 + buf[pos+1] = '\\'
  644 + pos += 2
  645 + default:
  646 + buf[pos] = c
  647 + pos++
  648 + }
  649 + }
  650 +
  651 + return buf[:pos]
  652 +}
  653 +
  654 +// escapeStringBackslash is similar to escapeBytesBackslash but for string.
  655 +func escapeStringBackslash(buf []byte, v string) []byte {
  656 + pos := len(buf)
  657 + buf = reserveBuffer(buf, len(v)*2)
  658 +
  659 + for i := 0; i < len(v); i++ {
  660 + c := v[i]
  661 + switch c {
  662 + case '\x00':
  663 + buf[pos] = '\\'
  664 + buf[pos+1] = '0'
  665 + pos += 2
  666 + case '\n':
  667 + buf[pos] = '\\'
  668 + buf[pos+1] = 'n'
  669 + pos += 2
  670 + case '\r':
  671 + buf[pos] = '\\'
  672 + buf[pos+1] = 'r'
  673 + pos += 2
  674 + case '\x1a':
  675 + buf[pos] = '\\'
  676 + buf[pos+1] = 'Z'
  677 + pos += 2
  678 + case '\'':
  679 + buf[pos] = '\\'
  680 + buf[pos+1] = '\''
  681 + pos += 2
  682 + case '"':
  683 + buf[pos] = '\\'
  684 + buf[pos+1] = '"'
  685 + pos += 2
  686 + case '\\':
  687 + buf[pos] = '\\'
  688 + buf[pos+1] = '\\'
  689 + pos += 2
  690 + default:
  691 + buf[pos] = c
  692 + pos++
  693 + }
  694 + }
  695 +
  696 + return buf[:pos]
  697 +}
  698 +
  699 +// escapeBytesQuotes escapes apostrophes in []byte by doubling them up.
  700 +// This escapes the contents of a string by doubling up any apostrophes that
  701 +// it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
  702 +// effect on the server.
  703 +// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L963-L1038
  704 +func escapeBytesQuotes(buf, v []byte) []byte {
  705 + pos := len(buf)
  706 + buf = reserveBuffer(buf, len(v)*2)
  707 +
  708 + for _, c := range v {
  709 + if c == '\'' {
  710 + buf[pos] = '\''
  711 + buf[pos+1] = '\''
  712 + pos += 2
  713 + } else {
  714 + buf[pos] = c
  715 + pos++
  716 + }
  717 + }
  718 +
  719 + return buf[:pos]
  720 +}
  721 +
  722 +// escapeStringQuotes is similar to escapeBytesQuotes but for string.
  723 +func escapeStringQuotes(buf []byte, v string) []byte {
  724 + pos := len(buf)
  725 + buf = reserveBuffer(buf, len(v)*2)
  726 +
  727 + for i := 0; i < len(v); i++ {
  728 + c := v[i]
  729 + if c == '\'' {
  730 + buf[pos] = '\''
  731 + buf[pos+1] = '\''
  732 + pos += 2
  733 + } else {
  734 + buf[pos] = c
  735 + pos++
  736 + }
  737 + }
  738 +
  739 + return buf[:pos]
  740 +}
... ...
src/github.com/go-sql-driver/mysql/utils_test.go 0 → 100644
... ... @@ -0,0 +1,197 @@
  1 +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2 +//
  3 +// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
  4 +//
  5 +// This Source Code Form is subject to the terms of the Mozilla Public
  6 +// License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7 +// You can obtain one at http://mozilla.org/MPL/2.0/.
  8 +
  9 +package mysql
  10 +
  11 +import (
  12 + "bytes"
  13 + "encoding/binary"
  14 + "fmt"
  15 + "testing"
  16 + "time"
  17 +)
  18 +
  19 +func TestScanNullTime(t *testing.T) {
  20 + var scanTests = []struct {
  21 + in interface{}
  22 + error bool
  23 + valid bool
  24 + time time.Time
  25 + }{
  26 + {tDate, false, true, tDate},
  27 + {sDate, false, true, tDate},
  28 + {[]byte(sDate), false, true, tDate},
  29 + {tDateTime, false, true, tDateTime},
  30 + {sDateTime, false, true, tDateTime},
  31 + {[]byte(sDateTime), false, true, tDateTime},
  32 + {tDate0, false, true, tDate0},
  33 + {sDate0, false, true, tDate0},
  34 + {[]byte(sDate0), false, true, tDate0},
  35 + {sDateTime0, false, true, tDate0},
  36 + {[]byte(sDateTime0), false, true, tDate0},
  37 + {"", true, false, tDate0},
  38 + {"1234", true, false, tDate0},
  39 + {0, true, false, tDate0},
  40 + }
  41 +
  42 + var nt = NullTime{}
  43 + var err error
  44 +
  45 + for _, tst := range scanTests {
  46 + err = nt.Scan(tst.in)
  47 + if (err != nil) != tst.error {
  48 + t.Errorf("%v: expected error status %t, got %t", tst.in, tst.error, (err != nil))
  49 + }
  50 + if nt.Valid != tst.valid {
  51 + t.Errorf("%v: expected valid status %t, got %t", tst.in, tst.valid, nt.Valid)
  52 + }
  53 + if nt.Time != tst.time {
  54 + t.Errorf("%v: expected time %v, got %v", tst.in, tst.time, nt.Time)
  55 + }
  56 + }
  57 +}
  58 +
  59 +func TestLengthEncodedInteger(t *testing.T) {
  60 + var integerTests = []struct {
  61 + num uint64
  62 + encoded []byte
  63 + }{
  64 + {0x0000000000000000, []byte{0x00}},
  65 + {0x0000000000000012, []byte{0x12}},
  66 + {0x00000000000000fa, []byte{0xfa}},
  67 + {0x0000000000000100, []byte{0xfc, 0x00, 0x01}},
  68 + {0x0000000000001234, []byte{0xfc, 0x34, 0x12}},
  69 + {0x000000000000ffff, []byte{0xfc, 0xff, 0xff}},
  70 + {0x0000000000010000, []byte{0xfd, 0x00, 0x00, 0x01}},
  71 + {0x0000000000123456, []byte{0xfd, 0x56, 0x34, 0x12}},
  72 + {0x0000000000ffffff, []byte{0xfd, 0xff, 0xff, 0xff}},
  73 + {0x0000000001000000, []byte{0xfe, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}},
  74 + {0x123456789abcdef0, []byte{0xfe, 0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12}},
  75 + {0xffffffffffffffff, []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
  76 + }
  77 +
  78 + for _, tst := range integerTests {
  79 + num, isNull, numLen := readLengthEncodedInteger(tst.encoded)
  80 + if isNull {
  81 + t.Errorf("%x: expected %d, got NULL", tst.encoded, tst.num)
  82 + }
  83 + if num != tst.num {
  84 + t.Errorf("%x: expected %d, got %d", tst.encoded, tst.num, num)
  85 + }
  86 + if numLen != len(tst.encoded) {
  87 + t.Errorf("%x: expected size %d, got %d", tst.encoded, len(tst.encoded), numLen)
  88 + }
  89 + encoded := appendLengthEncodedInteger(nil, num)
  90 + if !bytes.Equal(encoded, tst.encoded) {
  91 + t.Errorf("%v: expected %x, got %x", num, tst.encoded, encoded)
  92 + }
  93 + }
  94 +}
  95 +
  96 +func TestOldPass(t *testing.T) {
  97 + scramble := []byte{9, 8, 7, 6, 5, 4, 3, 2}
  98 + vectors := []struct {
  99 + pass string
  100 + out string
  101 + }{
  102 + {" pass", "47575c5a435b4251"},
  103 + {"pass ", "47575c5a435b4251"},
  104 + {"123\t456", "575c47505b5b5559"},
  105 + {"C0mpl!ca ted#PASS123", "5d5d554849584a45"},
  106 + }
  107 + for _, tuple := range vectors {
  108 + ours := scrambleOldPassword(scramble, []byte(tuple.pass))
  109 + if tuple.out != fmt.Sprintf("%x", ours) {
  110 + t.Errorf("Failed old password %q", tuple.pass)
  111 + }
  112 + }
  113 +}
  114 +
  115 +func TestFormatBinaryDateTime(t *testing.T) {
  116 + rawDate := [11]byte{}
  117 + binary.LittleEndian.PutUint16(rawDate[:2], 1978) // years
  118 + rawDate[2] = 12 // months
  119 + rawDate[3] = 30 // days
  120 + rawDate[4] = 15 // hours
  121 + rawDate[5] = 46 // minutes
  122 + rawDate[6] = 23 // seconds
  123 + binary.LittleEndian.PutUint32(rawDate[7:], 987654) // microseconds
  124 + expect := func(expected string, inlen, outlen uint8) {
  125 + actual, _ := formatBinaryDateTime(rawDate[:inlen], outlen, false)
  126 + bytes, ok := actual.([]byte)
  127 + if !ok {
  128 + t.Errorf("formatBinaryDateTime must return []byte, was %T", actual)
  129 + }
  130 + if string(bytes) != expected {
  131 + t.Errorf(
  132 + "expected %q, got %q for length in %d, out %d",
  133 + bytes, actual, inlen, outlen,
  134 + )
  135 + }
  136 + }
  137 + expect("0000-00-00", 0, 10)
  138 + expect("0000-00-00 00:00:00", 0, 19)
  139 + expect("1978-12-30", 4, 10)
  140 + expect("1978-12-30 15:46:23", 7, 19)
  141 + expect("1978-12-30 15:46:23.987654", 11, 26)
  142 +}
  143 +
  144 +func TestEscapeBackslash(t *testing.T) {
  145 + expect := func(expected, value string) {
  146 + actual := string(escapeBytesBackslash([]byte{}, []byte(value)))
  147 + if actual != expected {
  148 + t.Errorf(
  149 + "expected %s, got %s",
  150 + expected, actual,
  151 + )
  152 + }
  153 +
  154 + actual = string(escapeStringBackslash([]byte{}, value))
  155 + if actual != expected {
  156 + t.Errorf(
  157 + "expected %s, got %s",
  158 + expected, actual,
  159 + )
  160 + }
  161 + }
  162 +
  163 + expect("foo\\0bar", "foo\x00bar")
  164 + expect("foo\\nbar", "foo\nbar")
  165 + expect("foo\\rbar", "foo\rbar")
  166 + expect("foo\\Zbar", "foo\x1abar")
  167 + expect("foo\\\"bar", "foo\"bar")
  168 + expect("foo\\\\bar", "foo\\bar")
  169 + expect("foo\\'bar", "foo'bar")
  170 +}
  171 +
  172 +func TestEscapeQuotes(t *testing.T) {
  173 + expect := func(expected, value string) {
  174 + actual := string(escapeBytesQuotes([]byte{}, []byte(value)))
  175 + if actual != expected {
  176 + t.Errorf(
  177 + "expected %s, got %s",
  178 + expected, actual,
  179 + )
  180 + }
  181 +
  182 + actual = string(escapeStringQuotes([]byte{}, value))
  183 + if actual != expected {
  184 + t.Errorf(
  185 + "expected %s, got %s",
  186 + expected, actual,
  187 + )
  188 + }
  189 + }
  190 +
  191 + expect("foo\x00bar", "foo\x00bar") // not affected
  192 + expect("foo\nbar", "foo\nbar") // not affected
  193 + expect("foo\rbar", "foo\rbar") // not affected
  194 + expect("foo\x1abar", "foo\x1abar") // not affected
  195 + expect("foo''bar", "foo'bar") // affected
  196 + expect("foo\"bar", "foo\"bar") // not affected
  197 +}
... ...
src/mysql/dbmysql.go
... ... @@ -5,6 +5,7 @@ import (
5 5 "common/logger"
6 6 "database/sql"
7 7 "strconv"
  8 + _ "github.com/go-sql-driver/mysql"
8 9 )
9 10  
10 11 var (
... ...