提交 78477138 编写于 作者: martianzhang's avatar martianzhang

update go-sql-driver vendor

上级 3faa8cd2
...@@ -28,9 +28,9 @@ import ( ...@@ -28,9 +28,9 @@ import (
"github.com/XiaoMi/soar/database" "github.com/XiaoMi/soar/database"
"github.com/XiaoMi/soar/env" "github.com/XiaoMi/soar/env"
"github.com/go-sql-driver/mysql"
"github.com/kr/pretty" "github.com/kr/pretty"
"github.com/percona/go-mysql/query" "github.com/percona/go-mysql/query"
"github.com/ziutek/mymysql/mysql"
"vitess.io/vitess/go/vt/sqlparser" "vitess.io/vitess/go/vt/sqlparser"
) )
...@@ -244,7 +244,7 @@ func main() { ...@@ -244,7 +244,7 @@ func main() {
} }
} else { } else {
// 根据错误号输出建议 // 根据错误号输出建议
switch vEnv.Error.(*mysql.Error).Code { switch vEnv.Error.(*mysql.MySQLError).Number {
case 1061: case 1061:
idxSuggest["IDX.001"] = advisor.Rule{ idxSuggest["IDX.001"] = advisor.Rule{
Item: "IDX.001", Item: "IDX.001",
......
...@@ -22,8 +22,8 @@ import ( ...@@ -22,8 +22,8 @@ import (
"github.com/XiaoMi/soar/common" "github.com/XiaoMi/soar/common"
"github.com/XiaoMi/soar/database" "github.com/XiaoMi/soar/database"
"github.com/go-sql-driver/mysql"
"github.com/kr/pretty" "github.com/kr/pretty"
"github.com/ziutek/mymysql/mysql"
) )
var connTest *database.Connector var connTest *database.Connector
...@@ -102,8 +102,8 @@ func TestNewVirtualEnv(t *testing.T) { ...@@ -102,8 +102,8 @@ func TestNewVirtualEnv(t *testing.T) {
// unexpected EOF // unexpected EOF
// 测试环境无法访问,或者被Disable的时候会进入这个分支 // 测试环境无法访问,或者被Disable的时候会进入这个分支
pretty.Println(sql, err) pretty.Println(sql, err)
case *mysql.Error: case *mysql.MySQLError:
if err.Code != 1061 { if err.Number != 1061 {
t.Error(err) t.Error(err)
} }
default: default:
......
# This is the official list of Go-MySQL-Driver authors for copyright purposes.
# If you are submitting a patch, please add your name or the name of the
# organization which holds the copyright to this list in alphabetical order.
# Names should be added to this file as
# Name <email address>
# The email address is not required for organizations.
# Please keep the list sorted.
# Individual Persons
Aaron Hopkins <go-sql-driver at die.net>
Achille Roussel <achille.roussel at gmail.com>
Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
Andrew Reid <andrew.reid at tixtrack.com>
Arne Hormann <arnehormann at gmail.com>
Asta Xie <xiemengjun at gmail.com>
Bulat Gaifullin <gaifullinbf at gmail.com>
Carlos Nieto <jose.carlos at menteslibres.net>
Chris Moos <chris at tech9computers.com>
Craig Wilson <craiggwilson at gmail.com>
Daniel Montoya <dsmontoyam at gmail.com>
Daniel Nichter <nil at codenode.com>
Daniël van Eeden <git at myname.nl>
Dave Protasowski <dprotaso at gmail.com>
DisposaBoy <disposaboy at dby.me>
Egor Smolyakov <egorsmkv at gmail.com>
Evan Shaw <evan at vendhq.com>
Frederick Mayle <frederickmayle at gmail.com>
Gustavo Kristic <gkristic at gmail.com>
Hajime Nakagami <nakagami at gmail.com>
Hanno Braun <mail at hannobraun.com>
Henri Yandell <flamefew at gmail.com>
Hirotaka Yamamoto <ymmt2005 at gmail.com>
ICHINOSE Shogo <shogo82148 at gmail.com>
Ilia Cimpoes <ichimpoesh at gmail.com>
INADA Naoki <songofacandy at gmail.com>
Jacek Szwec <szwec.jacek at gmail.com>
James Harr <james.harr at gmail.com>
Jeff Hodges <jeff at somethingsimilar.com>
Jeffrey Charles <jeffreycharles at gmail.com>
Jian Zhen <zhenjl at gmail.com>
Joshua Prunier <joshua.prunier at gmail.com>
Julien Lefevre <julien.lefevr at gmail.com>
Julien Schmidt <go-sql-driver at julienschmidt.com>
Justin Li <jli at j-li.net>
Justin Nuß <nuss.justin at gmail.com>
Kamil Dziedzic <kamil at klecza.pl>
Kevin Malachowski <kevin at chowski.com>
Kieron Woodhouse <kieron.woodhouse at infosum.com>
Lennart Rudolph <lrudolph at hmc.edu>
Leonardo YongUk Kim <dalinaum at gmail.com>
Linh Tran Tuan <linhduonggnu at gmail.com>
Lion Yang <lion at aosc.xyz>
Luca Looz <luca.looz92 at gmail.com>
Lucas Liu <extrafliu at gmail.com>
Luke Scott <luke at webconnex.com>
Maciej Zimnoch <maciej.zimnoch at codilime.com>
Michael Woolnough <michael.woolnough at gmail.com>
Nicola Peduzzi <thenikso at gmail.com>
Olivier Mengué <dolmen at cpan.org>
oscarzhao <oscarzhaosl at gmail.com>
Paul Bonser <misterpib at gmail.com>
Peter Schultz <peter.schultz at classmarkets.com>
Rebecca Chin <rchin at pivotal.io>
Reed Allman <rdallman10 at gmail.com>
Richard Wilkes <wilkes at me.com>
Robert Russell <robert at rrbrussell.com>
Runrioter Wung <runrioter at gmail.com>
Shuode Li <elemount at qq.com>
Soroush Pour <me at soroushjp.com>
Stan Putrya <root.vagner at gmail.com>
Stanley Gunawan <gunawan.stanley at gmail.com>
Steven Hartland <steven.hartland at multiplay.co.uk>
Thomas Wodarek <wodarekwebpage at gmail.com>
Tom Jenkinson <tom at tjenkinson.me>
Xiangyu Hu <xiangyu.hu at outlook.com>
Xiaobing Jiang <s7v7nislands at gmail.com>
Xiuming Chen <cc at cxm.cc>
Zhenye Xie <xiezhenye at gmail.com>
# Organizations
Barracuda Networks, Inc.
Counting Ltd.
Google Inc.
InfoSum Ltd.
Keybase Inc.
Percona LLC
Pivotal Inc.
Stripe Inc.
Multiplay Ltd.
## Version 1.4 (2018-06-03)
Changes:
- Documentation fixes (#530, #535, #567)
- Refactoring (#575, #579, #580, #581, #603, #615, #704)
- Cache column names (#444)
- Sort the DSN parameters in DSNs generated from a config (#637)
- Allow native password authentication by default (#644)
- Use the default port if it is missing in the DSN (#668)
- Removed the `strict` mode (#676)
- Do not query `max_allowed_packet` by default (#680)
- Dropped support Go 1.6 and lower (#696)
- Updated `ConvertValue()` to match the database/sql/driver implementation (#760)
- Document the usage of `0000-00-00T00:00:00` as the time.Time zero value (#783)
- Improved the compatibility of the authentication system (#807)
New Features:
- Multi-Results support (#537)
- `rejectReadOnly` DSN option (#604)
- `context.Context` support (#608, #612, #627, #761)
- Transaction isolation level support (#619, #744)
- Read-Only transactions support (#618, #634)
- `NewConfig` function which initializes a config with default values (#679)
- Implemented the `ColumnType` interfaces (#667, #724)
- Support for custom string types in `ConvertValue` (#623)
- Implemented `NamedValueChecker`, improving support for uint64 with high bit set (#690, #709, #710)
- `caching_sha2_password` authentication plugin support (#794, #800, #801, #802)
- Implemented `driver.SessionResetter` (#779)
- `sha256_password` authentication plugin support (#808)
Bugfixes:
- Use the DSN hostname as TLS default ServerName if `tls=true` (#564, #718)
- Fixed LOAD LOCAL DATA INFILE for empty files (#590)
- Removed columns definition cache since it sometimes cached invalid data (#592)
- Don't mutate registered TLS configs (#600)
- Make RegisterTLSConfig concurrency-safe (#613)
- Handle missing auth data in the handshake packet correctly (#646)
- Do not retry queries when data was written to avoid data corruption (#302, #736)
- Cache the connection pointer for error handling before invalidating it (#678)
- Fixed imports for appengine/cloudsql (#700)
- Fix sending STMT_LONG_DATA for 0 byte data (#734)
- Set correct capacity for []bytes read from length-encoded strings (#766)
- Make RegisterDial concurrency-safe (#773)
## Version 1.3 (2016-12-01)
Changes:
- Go 1.1 is no longer supported
- Use decimals fields in MySQL to format time types (#249)
- Buffer optimizations (#269)
- TLS ServerName defaults to the host (#283)
- Refactoring (#400, #410, #437)
- Adjusted documentation for second generation CloudSQL (#485)
- Documented DSN system var quoting rules (#502)
- Made statement.Close() calls idempotent to avoid errors in Go 1.6+ (#512)
New Features:
- Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249)
- Support for returning table alias on Columns() (#289, #359, #382)
- Placeholder interpolation, can be actived with the DSN parameter `interpolateParams=true` (#309, #318, #490)
- Support for uint64 parameters with high bit set (#332, #345)
- Cleartext authentication plugin support (#327)
- Exported ParseDSN function and the Config struct (#403, #419, #429)
- Read / Write timeouts (#401)
- Support for JSON field type (#414)
- Support for multi-statements and multi-results (#411, #431)
- DSN parameter to set the driver-side max_allowed_packet value manually (#489)
- Native password authentication plugin support (#494, #524)
Bugfixes:
- Fixed handling of queries without columns and rows (#255)
- Fixed a panic when SetKeepAlive() failed (#298)
- Handle ERR packets while reading rows (#321)
- Fixed reading NULL length-encoded integers in MySQL 5.6+ (#349)
- Fixed absolute paths support in LOAD LOCAL DATA INFILE (#356)
- Actually zero out bytes in handshake response (#378)
- Fixed race condition in registering LOAD DATA INFILE handler (#383)
- Fixed tests with MySQL 5.7.9+ (#380)
- QueryUnescape TLS config names (#397)
- Fixed "broken pipe" error by writing to closed socket (#390)
- Fixed LOAD LOCAL DATA INFILE buffering (#424)
- Fixed parsing of floats into float64 when placeholders are used (#434)
- Fixed DSN tests with Go 1.7+ (#459)
- Handle ERR packets while waiting for EOF (#473)
- Invalidate connection on error while discarding additional results (#513)
- Allow terminating packets of length 0 (#516)
## Version 1.2 (2014-06-03)
Changes:
- We switched back to a "rolling release". `go get` installs the current master branch again
- Version v1 of the driver will not be maintained anymore. Go 1.0 is no longer supported by this driver
- Exported errors to allow easy checking from application code
- Enabled TCP Keepalives on TCP connections
- Optimized INFILE handling (better buffer size calculation, lazy init, ...)
- The DSN parser also checks for a missing separating slash
- Faster binary date / datetime to string formatting
- Also exported the MySQLWarning type
- mysqlConn.Close returns the first error encountered instead of ignoring all errors
- writePacket() automatically writes the packet size to the header
- readPacket() uses an iterative approach instead of the recursive approach to merge splitted packets
New Features:
- `RegisterDial` allows the usage of a custom dial function to establish the network connection
- Setting the connection collation is possible with the `collation` DSN parameter. This parameter should be preferred over the `charset` parameter
- Logging of critical errors is configurable with `SetLogger`
- Google CloudSQL support
Bugfixes:
- Allow more than 32 parameters in prepared statements
- Various old_password fixes
- Fixed TestConcurrent test to pass Go's race detection
- Fixed appendLengthEncodedInteger for large numbers
- Renamed readLengthEnodedString to readLengthEncodedString and skipLengthEnodedString to skipLengthEncodedString (fixed typo)
## Version 1.1 (2013-11-02)
Changes:
- Go-MySQL-Driver now requires Go 1.1
- Connections now use the collation `utf8_general_ci` by default. Adding `&charset=UTF8` to the DSN should not be necessary anymore
- Made closing rows and connections error tolerant. This allows for example deferring rows.Close() without checking for errors
- `[]byte(nil)` is now treated as a NULL value. Before, it was treated like an empty string / `[]byte("")`
- DSN parameter values must now be url.QueryEscape'ed. This allows text values to contain special characters, such as '&'.
- Use the IO buffer also for writing. This results in zero allocations (by the driver) for most queries
- Optimized the buffer for reading
- stmt.Query now caches column metadata
- New Logo
- Changed the copyright header to include all contributors
- Improved the LOAD INFILE documentation
- The driver struct is now exported to make the driver directly accessible
- Refactored the driver tests
- Added more benchmarks and moved all to a separate file
- Other small refactoring
New Features:
- Added *old_passwords* support: Required in some cases, but must be enabled by adding `allowOldPasswords=true` to the DSN since it is insecure
- Added a `clientFoundRows` parameter: Return the number of matching rows instead of the number of rows changed on UPDATEs
- Added TLS/SSL support: Use a TLS/SSL encrypted connection to the server. Custom TLS configs can be registered and used
Bugfixes:
- Fixed MySQL 4.1 support: MySQL 4.1 sends packets with lengths which differ from the specification
- Convert to DB timezone when inserting `time.Time`
- Splitted packets (more than 16MB) are now merged correctly
- Fixed false positive `io.EOF` errors when the data was fully read
- Avoid panics on reuse of closed connections
- Fixed empty string producing false nil values
- Fixed sign byte for positive TIME fields
## Version 1.0 (2013-05-14)
Initial Release
# Contributing Guidelines
## Reporting Issues
Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed).
## Contributing Code
By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file.
Don't forget to add yourself to the AUTHORS file.
### Code Review
Everyone is invited to review and comment on pull requests.
If it looks fine to you, comment with "LGTM" (Looks good to me).
If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes.
Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM".
## Development Ideas
If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/go-sql-driver/mysql/wiki/Development-Ideas) Wiki page.
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
# Go-MySQL-Driver
A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) package
![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin")
---------------------------------------
* [Features](#features)
* [Requirements](#requirements)
* [Installation](#installation)
* [Usage](#usage)
* [DSN (Data Source Name)](#dsn-data-source-name)
* [Password](#password)
* [Protocol](#protocol)
* [Address](#address)
* [Parameters](#parameters)
* [Examples](#examples)
* [Connection pool and timeouts](#connection-pool-and-timeouts)
* [context.Context Support](#contextcontext-support)
* [ColumnType Support](#columntype-support)
* [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support)
* [time.Time support](#timetime-support)
* [Unicode support](#unicode-support)
* [Testing / Development](#testing--development)
* [License](#license)
---------------------------------------
## Features
* Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance")
* Native Go implementation. No C-bindings, just pure Go
* Connections over TCP/IPv4, TCP/IPv6, Unix domain sockets or [custom protocols](https://godoc.org/github.com/go-sql-driver/mysql#DialFunc)
* Automatic handling of broken connections
* Automatic Connection Pooling *(by database/sql package)*
* Supports queries larger than 16MB
* Full [`sql.RawBytes`](https://golang.org/pkg/database/sql/#RawBytes) support.
* Intelligent `LONG DATA` handling in prepared statements
* Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support
* Optional `time.Time` parsing
* Optional placeholder interpolation
## Requirements
* Go 1.8 or higher. We aim to support the 3 latest versions of Go.
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
---------------------------------------
## Installation
Simple install the package to your [$GOPATH](https://github.com/golang/go/wiki/GOPATH "GOPATH") with the [go tool](https://golang.org/cmd/go/ "go command") from shell:
```bash
$ go get -u github.com/go-sql-driver/mysql
```
Make sure [Git is installed](https://git-scm.com/downloads) on your machine and in your system's `PATH`.
## Usage
_Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](https://golang.org/pkg/database/sql/) API then.
Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name) as `dataSourceName`:
```go
import "database/sql"
import _ "github.com/go-sql-driver/mysql"
db, err := sql.Open("mysql", "user:password@/dbname")
```
[Examples are available in our Wiki](https://github.com/go-sql-driver/mysql/wiki/Examples "Go-MySQL-Driver Examples").
### DSN (Data Source Name)
The Data Source Name has a common format, like e.g. [PEAR DB](http://pear.php.net/manual/en/package.database.db.intro-dsn.php) uses it, but without type-prefix (optional parts marked by squared brackets):
```
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
```
A DSN in its fullest form:
```
username:password@protocol(address)/dbname?param=value
```
Except for the databasename, all values are optional. So the minimal DSN is:
```
/dbname
```
If you do not want to preselect a database, leave `dbname` empty:
```
/
```
This has the same effect as an empty DSN string:
```
```
Alternatively, [Config.FormatDSN](https://godoc.org/github.com/go-sql-driver/mysql#Config.FormatDSN) can be used to create a DSN string by filling a struct.
#### Password
Passwords can consist of any character. Escaping is **not** necessary.
#### Protocol
See [net.Dial](https://golang.org/pkg/net/#Dial) for more information which networks are available.
In general you should use an Unix domain socket if available and TCP otherwise for best performance.
#### Address
For TCP and UDP networks, addresses have the form `host[:port]`.
If `port` is omitted, the default port will be used.
If `host` is a literal IPv6 address, it must be enclosed in square brackets.
The functions [net.JoinHostPort](https://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](https://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`.
#### Parameters
*Parameters are case-sensitive!*
Notice that any of `true`, `TRUE`, `True` or `1` is accepted to stand for a true boolean value. Not surprisingly, false can be specified as any of: `false`, `FALSE`, `False` or `0`.
##### `allowAllFiles`
```
Type: bool
Valid Values: true, false
Default: false
```
`allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files.
[*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
##### `allowCleartextPasswords`
```
Type: bool
Valid Values: true, false
Default: false
```
`allowCleartextPasswords=true` allows using the [cleartext client side plugin](http://dev.mysql.com/doc/en/cleartext-authentication-plugin.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network.
##### `allowNativePasswords`
```
Type: bool
Valid Values: true, false
Default: true
```
`allowNativePasswords=false` disallows the usage of MySQL native password method.
##### `allowOldPasswords`
```
Type: bool
Valid Values: true, false
Default: false
```
`allowOldPasswords=true` allows the usage of the insecure old password method. This should be avoided, but is necessary in some cases. See also [the old_passwords wiki page](https://github.com/go-sql-driver/mysql/wiki/old_passwords).
##### `charset`
```
Type: string
Valid Values: <name>
Default: none
```
Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
Usage of the `charset` parameter is discouraged because it issues additional queries to the server.
Unless you need the fallback behavior, please use `collation` instead.
##### `collation`
```
Type: string
Valid Values: <name>
Default: utf8_general_ci
```
Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail.
A list of valid charsets for a server is retrievable with `SHOW COLLATION`.
##### `clientFoundRows`
```
Type: bool
Valid Values: true, false
Default: false
```
`clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed.
##### `columnsWithAlias`
```
Type: bool
Valid Values: true, false
Default: false
```
When `columnsWithAlias` is true, calls to `sql.Rows.Columns()` will return the table alias and the column name separated by a dot. For example:
```
SELECT u.id FROM users as u
```
will return `u.id` instead of just `id` if `columnsWithAlias=true`.
##### `interpolateParams`
```
Type: bool
Valid Values: true, false
Default: false
```
If `interpolateParams` is true, placeholders (`?`) in calls to `db.Query()` and `db.Exec()` are interpolated into a single query string with given parameters. This reduces the number of roundtrips, since the driver has to prepare a statement, execute it with given parameters and close the statement again with `interpolateParams=false`.
*This can not be used together with the multibyte encodings BIG5, CP932, GB2312, GBK or SJIS. These are blacklisted as they may [introduce a SQL injection vulnerability](http://stackoverflow.com/a/12118602/3430118)!*
##### `loc`
```
Type: string
Valid Values: <escaped name>
Default: UTC
```
Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](https://golang.org/pkg/time/#LoadLocation) for details.
Note that this sets the location for time.Time values but does not change MySQL's [time_zone setting](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html). For that see the [time_zone system variable](#system-variables), which can also be set as a DSN parameter.
Please keep in mind, that param values must be [url.QueryEscape](https://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`.
##### `maxAllowedPacket`
```
Type: decimal number
Default: 4194304
```
Max packet size allowed in bytes. The default value is 4 MiB and should be adjusted to match the server settings. `maxAllowedPacket=0` can be used to automatically fetch the `max_allowed_packet` variable from server *on every connection*.
##### `multiStatements`
```
Type: bool
Valid Values: true, false
Default: false
```
Allow multiple statements in one query. While this allows batch queries, it also greatly increases the risk of SQL injections. Only the result of the first query is returned, all other results are silently discarded.
When `multiStatements` is used, `?` parameters must only be used in the first statement.
##### `parseTime`
```
Type: bool
Valid Values: true, false
Default: false
```
`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
The date or datetime like `0000-00-00 00:00:00` is converted into zero value of `time.Time`.
##### `readTimeout`
```
Type: duration
Default: 0
```
I/O read timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
##### `rejectReadOnly`
```
Type: bool
Valid Values: true, false
Default: false
```
`rejectReadOnly=true` causes the driver to reject read-only connections. This
is for a possible race condition during an automatic failover, where the mysql
client gets connected to a read-only replica after the failover.
Note that this should be a fairly rare case, as an automatic failover normally
happens when the primary is down, and the race condition shouldn't happen
unless it comes back up online as soon as the failover is kicked off. On the
other hand, when this happens, a MySQL application can get stuck on a
read-only connection until restarted. It is however fairly easy to reproduce,
for example, using a manual failover on AWS Aurora's MySQL-compatible cluster.
If you are not relying on read-only transactions to reject writes that aren't
supposed to happen, setting this on some MySQL providers (such as AWS Aurora)
is safer for failovers.
Note that ERROR 1290 can be returned for a `read-only` server and this option will
cause a retry for that error. However the same error number is used for some
other cases. You should ensure your application will never cause an ERROR 1290
except for `read-only` mode when enabling this option.
##### `serverPubKey`
```
Type: string
Valid Values: <name>
Default: none
```
Server public keys can be registered with [`mysql.RegisterServerPubKey`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterServerPubKey), which can then be used by the assigned name in the DSN.
Public keys are used to transmit encrypted data, e.g. for authentication.
If the server's public key is known, it should be set manually to avoid expensive and potentially insecure transmissions of the public key from the server to the client each time it is required.
##### `timeout`
```
Type: duration
Default: OS default
```
Timeout for establishing connections, aka dial timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
##### `tls`
```
Type: bool / string
Valid Values: true, false, skip-verify, preferred, <name>
Default: false
```
`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side) or use `preferred` to use TLS only when advertised by the server. This is similar to `skip-verify`, but additionally allows a fallback to a connection which is not encrypted. Neither `skip-verify` nor `preferred` add any reliable security. You can use a custom TLS config after registering it with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
##### `writeTimeout`
```
Type: duration
Default: 0
```
I/O write timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
##### System Variables
Any other parameters are interpreted as system variables:
* `<boolean_var>=<value>`: `SET <boolean_var>=<value>`
* `<enum_var>=<value>`: `SET <enum_var>=<value>`
* `<string_var>=%27<value>%27`: `SET <string_var>='<value>'`
Rules:
* The values for string variables must be quoted with `'`.
* The values must also be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!
(which implies values of string variables must be wrapped with `%27`).
Examples:
* `autocommit=1`: `SET autocommit=1`
* [`time_zone=%27Europe%2FParis%27`](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html): `SET time_zone='Europe/Paris'`
* [`tx_isolation=%27REPEATABLE-READ%27`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `SET tx_isolation='REPEATABLE-READ'`
#### Examples
```
user@unix(/path/to/socket)/dbname
```
```
root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local
```
```
user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true
```
Treat warnings as errors by setting the system variable [`sql_mode`](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html):
```
user:password@/dbname?sql_mode=TRADITIONAL
```
TCP via IPv6:
```
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s&collation=utf8mb4_unicode_ci
```
TCP on a remote host, e.g. Amazon RDS:
```
id:password@tcp(your-amazonaws-uri.com:3306)/dbname
```
Google Cloud SQL on App Engine (First Generation MySQL Server):
```
user@cloudsql(project-id:instance-name)/dbname
```
Google Cloud SQL on App Engine (Second Generation MySQL Server):
```
user@cloudsql(project-id:regionname:instance-name)/dbname
```
TCP using default port (3306) on localhost:
```
user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped
```
Use the default protocol (tcp) and host (localhost:3306):
```
user:password@/dbname
```
No Database preselected:
```
user:password@/
```
### Connection pool and timeouts
The connection pool is managed by Go's database/sql package. For details on how to configure the size of the pool and how long connections stay in the pool see `*DB.SetMaxOpenConns`, `*DB.SetMaxIdleConns`, and `*DB.SetConnMaxLifetime` in the [database/sql documentation](https://golang.org/pkg/database/sql/). The read, write, and dial timeouts for each individual connection are configured with the DSN parameters [`readTimeout`](#readtimeout), [`writeTimeout`](#writetimeout), and [`timeout`](#timeout), respectively.
## `ColumnType` Support
This driver supports the [`ColumnType` interface](https://golang.org/pkg/database/sql/#ColumnType) introduced in Go 1.8, with the exception of [`ColumnType.Length()`](https://golang.org/pkg/database/sql/#ColumnType.Length), which is currently not supported.
## `context.Context` Support
Go 1.8 added `database/sql` support for `context.Context`. This driver supports query timeouts and cancellation via contexts.
See [context support in the database/sql package](https://golang.org/doc/go1.8#database_sql) for more details.
### `LOAD DATA LOCAL INFILE` support
For this feature you need direct access to the package. Therefore you must change the import path (no `_`):
```go
import "github.com/go-sql-driver/mysql"
```
Files must be whitelisted by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the Whitelist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)).
To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore.
See the [godoc of Go-MySQL-Driver](https://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
### `time.Time` support
The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your program.
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](https://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
**Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
Alternatively you can use the [`NullTime`](https://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`.
### Unicode support
Since version 1.1 Go-MySQL-Driver automatically uses the collation `utf8_general_ci` by default.
Other collations / charsets can be set using the [`collation`](#collation) DSN parameter.
Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAMES utf8`) to the DSN to enable proper UTF-8 support. This is not necessary anymore. The [`collation`](#collation) parameter should be preferred to set another collation / charset than the default.
See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
## Testing / Development
To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
Go-MySQL-Driver is not feature-complete yet. Your help is very appreciated.
If you want to contribute, you can work on an [open issue](https://github.com/go-sql-driver/mysql/issues?state=open) or review a [pull request](https://github.com/go-sql-driver/mysql/pulls).
See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/CONTRIBUTING.md) for details.
---------------------------------------
## License
Go-MySQL-Driver is licensed under the [Mozilla Public License Version 2.0](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
Mozilla summarizes the license scope as follows:
> MPL: The copyleft applies to any files containing MPLed code.
That means:
* You can **use** the **unchanged** source code both in private and commercially.
* When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0).
* You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**.
Please read the [MPL 2.0 FAQ](https://www.mozilla.org/en-US/MPL/2.0/FAQ/) if you have further questions regarding the license.
You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE).
![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow")
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build appengine
package mysql
import (
"google.golang.org/appengine/cloudsql"
)
func init() {
RegisterDial("cloudsql", cloudsql.Dial)
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2018 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"sync"
)
// server pub keys registry
var (
serverPubKeyLock sync.RWMutex
serverPubKeyRegistry map[string]*rsa.PublicKey
)
// RegisterServerPubKey registers a server RSA public key which can be used to
// send data in a secure manner to the server without receiving the public key
// in a potentially insecure way from the server first.
// Registered keys can afterwards be used adding serverPubKey=<name> to the DSN.
//
// Note: The provided rsa.PublicKey instance is exclusively owned by the driver
// after registering it and may not be modified.
//
// data, err := ioutil.ReadFile("mykey.pem")
// if err != nil {
// log.Fatal(err)
// }
//
// block, _ := pem.Decode(data)
// if block == nil || block.Type != "PUBLIC KEY" {
// log.Fatal("failed to decode PEM block containing public key")
// }
//
// pub, err := x509.ParsePKIXPublicKey(block.Bytes)
// if err != nil {
// log.Fatal(err)
// }
//
// if rsaPubKey, ok := pub.(*rsa.PublicKey); ok {
// mysql.RegisterServerPubKey("mykey", rsaPubKey)
// } else {
// log.Fatal("not a RSA public key")
// }
//
func RegisterServerPubKey(name string, pubKey *rsa.PublicKey) {
serverPubKeyLock.Lock()
if serverPubKeyRegistry == nil {
serverPubKeyRegistry = make(map[string]*rsa.PublicKey)
}
serverPubKeyRegistry[name] = pubKey
serverPubKeyLock.Unlock()
}
// DeregisterServerPubKey removes the public key registered with the given name.
func DeregisterServerPubKey(name string) {
serverPubKeyLock.Lock()
if serverPubKeyRegistry != nil {
delete(serverPubKeyRegistry, name)
}
serverPubKeyLock.Unlock()
}
func getServerPubKey(name string) (pubKey *rsa.PublicKey) {
serverPubKeyLock.RLock()
if v, ok := serverPubKeyRegistry[name]; ok {
pubKey = v
}
serverPubKeyLock.RUnlock()
return
}
// Hash password using pre 4.1 (old password) method
// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
type myRnd struct {
seed1, seed2 uint32
}
const myRndMaxVal = 0x3FFFFFFF
// Pseudo random number generator
func newMyRnd(seed1, seed2 uint32) *myRnd {
return &myRnd{
seed1: seed1 % myRndMaxVal,
seed2: seed2 % myRndMaxVal,
}
}
// Tested to be equivalent to MariaDB's floating point variant
// http://play.golang.org/p/QHvhd4qved
// http://play.golang.org/p/RG0q4ElWDx
func (r *myRnd) NextByte() byte {
r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
return byte(uint64(r.seed1) * 31 / myRndMaxVal)
}
// Generate binary hash from byte string using insecure pre 4.1 method
func pwHash(password []byte) (result [2]uint32) {
var add uint32 = 7
var tmp uint32
result[0] = 1345345333
result[1] = 0x12345671
for _, c := range password {
// skip spaces and tabs in password
if c == ' ' || c == '\t' {
continue
}
tmp = uint32(c)
result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
result[1] += (result[1] << 8) ^ result[0]
add += tmp
}
// Remove sign bit (1<<31)-1)
result[0] &= 0x7FFFFFFF
result[1] &= 0x7FFFFFFF
return
}
// Hash password using insecure pre 4.1 method
func scrambleOldPassword(scramble []byte, password string) []byte {
if len(password) == 0 {
return nil
}
scramble = scramble[:8]
hashPw := pwHash([]byte(password))
hashSc := pwHash(scramble)
r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
var out [8]byte
for i := range out {
out[i] = r.NextByte() + 64
}
mask := r.NextByte()
for i := range out {
out[i] ^= mask
}
return out[:]
}
// Hash password using 4.1+ method (SHA1)
func scramblePassword(scramble []byte, password string) []byte {
if len(password) == 0 {
return nil
}
// stage1Hash = SHA1(password)
crypt := sha1.New()
crypt.Write([]byte(password))
stage1 := crypt.Sum(nil)
// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
// inner Hash
crypt.Reset()
crypt.Write(stage1)
hash := crypt.Sum(nil)
// outer Hash
crypt.Reset()
crypt.Write(scramble)
crypt.Write(hash)
scramble = crypt.Sum(nil)
// token = scrambleHash XOR stage1Hash
for i := range scramble {
scramble[i] ^= stage1[i]
}
return scramble
}
// Hash password using MySQL 8+ method (SHA256)
func scrambleSHA256Password(scramble []byte, password string) []byte {
if len(password) == 0 {
return nil
}
// XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble))
crypt := sha256.New()
crypt.Write([]byte(password))
message1 := crypt.Sum(nil)
crypt.Reset()
crypt.Write(message1)
message1Hash := crypt.Sum(nil)
crypt.Reset()
crypt.Write(message1Hash)
crypt.Write(scramble)
message2 := crypt.Sum(nil)
for i := range message1 {
message1[i] ^= message2[i]
}
return message1
}
func encryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte, error) {
plain := make([]byte, len(password)+1)
copy(plain, password)
for i := range plain {
j := i % len(seed)
plain[i] ^= seed[j]
}
sha1 := sha1.New()
return rsa.EncryptOAEP(sha1, rand.Reader, pub, plain, nil)
}
func (mc *mysqlConn) sendEncryptedPassword(seed []byte, pub *rsa.PublicKey) error {
enc, err := encryptPassword(mc.cfg.Passwd, seed, pub)
if err != nil {
return err
}
return mc.writeAuthSwitchPacket(enc)
}
func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
switch plugin {
case "caching_sha2_password":
authResp := scrambleSHA256Password(authData, mc.cfg.Passwd)
return authResp, nil
case "mysql_old_password":
if !mc.cfg.AllowOldPasswords {
return nil, ErrOldPassword
}
// Note: there are edge cases where this should work but doesn't;
// this is currently "wontfix":
// https://github.com/go-sql-driver/mysql/issues/184
authResp := append(scrambleOldPassword(authData[:8], mc.cfg.Passwd), 0)
return authResp, nil
case "mysql_clear_password":
if !mc.cfg.AllowCleartextPasswords {
return nil, ErrCleartextPassword
}
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
return append([]byte(mc.cfg.Passwd), 0), nil
case "mysql_native_password":
if !mc.cfg.AllowNativePasswords {
return nil, ErrNativePassword
}
// https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
// Native password authentication only need and will need 20-byte challenge.
authResp := scramblePassword(authData[:20], mc.cfg.Passwd)
return authResp, nil
case "sha256_password":
if len(mc.cfg.Passwd) == 0 {
return []byte{0}, nil
}
if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
// write cleartext auth packet
return append([]byte(mc.cfg.Passwd), 0), nil
}
pubKey := mc.cfg.pubKey
if pubKey == nil {
// request public key from server
return []byte{1}, nil
}
// encrypted password
enc, err := encryptPassword(mc.cfg.Passwd, authData, pubKey)
return enc, err
default:
errLog.Print("unknown auth plugin:", plugin)
return nil, ErrUnknownPlugin
}
}
func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
// Read Result Packet
authData, newPlugin, err := mc.readAuthResult()
if err != nil {
return err
}
// handle auth plugin switch, if requested
if newPlugin != "" {
// If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
// sent and we have to keep using the cipher sent in the init packet.
if authData == nil {
authData = oldAuthData
} else {
// copy data from read buffer to owned slice
copy(oldAuthData, authData)
}
plugin = newPlugin
authResp, err := mc.auth(authData, plugin)
if err != nil {
return err
}
if err = mc.writeAuthSwitchPacket(authResp); err != nil {
return err
}
// Read Result Packet
authData, newPlugin, err = mc.readAuthResult()
if err != nil {
return err
}
// Do not allow to change the auth plugin more than once
if newPlugin != "" {
return ErrMalformPkt
}
}
switch plugin {
// https://insidemysql.com/preparing-your-community-connector-for-mysql-8-part-2-sha256/
case "caching_sha2_password":
switch len(authData) {
case 0:
return nil // auth successful
case 1:
switch authData[0] {
case cachingSha2PasswordFastAuthSuccess:
if err = mc.readResultOK(); err == nil {
return nil // auth successful
}
case cachingSha2PasswordPerformFullAuthentication:
if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
// write cleartext auth packet
err = mc.writeAuthSwitchPacket(append([]byte(mc.cfg.Passwd), 0))
if err != nil {
return err
}
} else {
pubKey := mc.cfg.pubKey
if pubKey == nil {
// request public key from server
data, err := mc.buf.takeSmallBuffer(4 + 1)
if err != nil {
return err
}
data[4] = cachingSha2PasswordRequestPublicKey
mc.writePacket(data)
// parse public key
if data, err = mc.readPacket(); err != nil {
return err
}
block, _ := pem.Decode(data[1:])
pkix, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return err
}
pubKey = pkix.(*rsa.PublicKey)
}
// send encrypted password
err = mc.sendEncryptedPassword(oldAuthData, pubKey)
if err != nil {
return err
}
}
return mc.readResultOK()
default:
return ErrMalformPkt
}
default:
return ErrMalformPkt
}
case "sha256_password":
switch len(authData) {
case 0:
return nil // auth successful
default:
block, _ := pem.Decode(authData)
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return err
}
// send encrypted password
err = mc.sendEncryptedPassword(oldAuthData, pub.(*rsa.PublicKey))
if err != nil {
return err
}
return mc.readResultOK()
}
default:
return nil // auth successful
}
return err
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"io"
"net"
"time"
)
const defaultBufSize = 4096
// A buffer which is used for both reading and writing.
// This is possible since communication on each connection is synchronous.
// In other words, we can't write and read simultaneously on the same connection.
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
// Also highly optimized for this particular use case.
type buffer struct {
buf []byte // buf is a byte buffer who's length and capacity are equal.
nc net.Conn
idx int
length int
timeout time.Duration
}
// newBuffer allocates and returns a new buffer.
func newBuffer(nc net.Conn) buffer {
return buffer{
buf: make([]byte, defaultBufSize),
nc: nc,
}
}
// fill reads into the buffer until at least _need_ bytes are in it
func (b *buffer) fill(need int) error {
n := b.length
// move existing data to the beginning
if n > 0 && b.idx > 0 {
copy(b.buf[0:n], b.buf[b.idx:])
}
// grow buffer if necessary
// TODO: let the buffer shrink again at some point
// Maybe keep the org buf slice and swap back?
if need > len(b.buf) {
// Round up to the next multiple of the default size
newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
copy(newBuf, b.buf)
b.buf = newBuf
}
b.idx = 0
for {
if b.timeout > 0 {
if err := b.nc.SetReadDeadline(time.Now().Add(b.timeout)); err != nil {
return err
}
}
nn, err := b.nc.Read(b.buf[n:])
n += nn
switch err {
case nil:
if n < need {
continue
}
b.length = n
return nil
case io.EOF:
if n >= need {
b.length = n
return nil
}
return io.ErrUnexpectedEOF
default:
return err
}
}
}
// returns next N bytes from buffer.
// The returned slice is only guaranteed to be valid until the next read
func (b *buffer) readNext(need int) ([]byte, error) {
if b.length < need {
// refill
if err := b.fill(need); err != nil {
return nil, err
}
}
offset := b.idx
b.idx += need
b.length -= need
return b.buf[offset:b.idx], nil
}
// takeBuffer returns a buffer with the requested size.
// If possible, a slice from the existing buffer is returned.
// Otherwise a bigger buffer is made.
// Only one buffer (total) can be used at a time.
func (b *buffer) takeBuffer(length int) ([]byte, error) {
if b.length > 0 {
return nil, ErrBusyBuffer
}
// test (cheap) general case first
if length <= cap(b.buf) {
return b.buf[:length], nil
}
if length < maxPacketSize {
b.buf = make([]byte, length)
return b.buf, nil
}
// buffer is larger than we want to store.
return make([]byte, length), nil
}
// takeSmallBuffer is shortcut which can be used if length is
// known to be smaller than defaultBufSize.
// Only one buffer (total) can be used at a time.
func (b *buffer) takeSmallBuffer(length int) ([]byte, error) {
if b.length > 0 {
return nil, ErrBusyBuffer
}
return b.buf[:length], nil
}
// takeCompleteBuffer returns the complete existing buffer.
// This can be used if the necessary buffer size is unknown.
// cap and len of the returned buffer will be equal.
// Only one buffer (total) can be used at a time.
func (b *buffer) takeCompleteBuffer() ([]byte, error) {
if b.length > 0 {
return nil, ErrBusyBuffer
}
return b.buf, nil
}
// store stores buf, an updated buffer, if its suitable to do so.
func (b *buffer) store(buf []byte) error {
if b.length > 0 {
return ErrBusyBuffer
} else if cap(buf) <= maxPacketSize && cap(buf) > cap(b.buf) {
b.buf = buf[:cap(buf)]
}
return nil
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
const defaultCollation = "utf8_general_ci"
const binaryCollation = "binary"
// A list of available collations mapped to the internal ID.
// To update this map use the following MySQL query:
// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS
var collations = map[string]byte{
"big5_chinese_ci": 1,
"latin2_czech_cs": 2,
"dec8_swedish_ci": 3,
"cp850_general_ci": 4,
"latin1_german1_ci": 5,
"hp8_english_ci": 6,
"koi8r_general_ci": 7,
"latin1_swedish_ci": 8,
"latin2_general_ci": 9,
"swe7_swedish_ci": 10,
"ascii_general_ci": 11,
"ujis_japanese_ci": 12,
"sjis_japanese_ci": 13,
"cp1251_bulgarian_ci": 14,
"latin1_danish_ci": 15,
"hebrew_general_ci": 16,
"tis620_thai_ci": 18,
"euckr_korean_ci": 19,
"latin7_estonian_cs": 20,
"latin2_hungarian_ci": 21,
"koi8u_general_ci": 22,
"cp1251_ukrainian_ci": 23,
"gb2312_chinese_ci": 24,
"greek_general_ci": 25,
"cp1250_general_ci": 26,
"latin2_croatian_ci": 27,
"gbk_chinese_ci": 28,
"cp1257_lithuanian_ci": 29,
"latin5_turkish_ci": 30,
"latin1_german2_ci": 31,
"armscii8_general_ci": 32,
"utf8_general_ci": 33,
"cp1250_czech_cs": 34,
"ucs2_general_ci": 35,
"cp866_general_ci": 36,
"keybcs2_general_ci": 37,
"macce_general_ci": 38,
"macroman_general_ci": 39,
"cp852_general_ci": 40,
"latin7_general_ci": 41,
"latin7_general_cs": 42,
"macce_bin": 43,
"cp1250_croatian_ci": 44,
"utf8mb4_general_ci": 45,
"utf8mb4_bin": 46,
"latin1_bin": 47,
"latin1_general_ci": 48,
"latin1_general_cs": 49,
"cp1251_bin": 50,
"cp1251_general_ci": 51,
"cp1251_general_cs": 52,
"macroman_bin": 53,
"utf16_general_ci": 54,
"utf16_bin": 55,
"utf16le_general_ci": 56,
"cp1256_general_ci": 57,
"cp1257_bin": 58,
"cp1257_general_ci": 59,
"utf32_general_ci": 60,
"utf32_bin": 61,
"utf16le_bin": 62,
"binary": 63,
"armscii8_bin": 64,
"ascii_bin": 65,
"cp1250_bin": 66,
"cp1256_bin": 67,
"cp866_bin": 68,
"dec8_bin": 69,
"greek_bin": 70,
"hebrew_bin": 71,
"hp8_bin": 72,
"keybcs2_bin": 73,
"koi8r_bin": 74,
"koi8u_bin": 75,
"latin2_bin": 77,
"latin5_bin": 78,
"latin7_bin": 79,
"cp850_bin": 80,
"cp852_bin": 81,
"swe7_bin": 82,
"utf8_bin": 83,
"big5_bin": 84,
"euckr_bin": 85,
"gb2312_bin": 86,
"gbk_bin": 87,
"sjis_bin": 88,
"tis620_bin": 89,
"ucs2_bin": 90,
"ujis_bin": 91,
"geostd8_general_ci": 92,
"geostd8_bin": 93,
"latin1_spanish_ci": 94,
"cp932_japanese_ci": 95,
"cp932_bin": 96,
"eucjpms_japanese_ci": 97,
"eucjpms_bin": 98,
"cp1250_polish_ci": 99,
"utf16_unicode_ci": 101,
"utf16_icelandic_ci": 102,
"utf16_latvian_ci": 103,
"utf16_romanian_ci": 104,
"utf16_slovenian_ci": 105,
"utf16_polish_ci": 106,
"utf16_estonian_ci": 107,
"utf16_spanish_ci": 108,
"utf16_swedish_ci": 109,
"utf16_turkish_ci": 110,
"utf16_czech_ci": 111,
"utf16_danish_ci": 112,
"utf16_lithuanian_ci": 113,
"utf16_slovak_ci": 114,
"utf16_spanish2_ci": 115,
"utf16_roman_ci": 116,
"utf16_persian_ci": 117,
"utf16_esperanto_ci": 118,
"utf16_hungarian_ci": 119,
"utf16_sinhala_ci": 120,
"utf16_german2_ci": 121,
"utf16_croatian_ci": 122,
"utf16_unicode_520_ci": 123,
"utf16_vietnamese_ci": 124,
"ucs2_unicode_ci": 128,
"ucs2_icelandic_ci": 129,
"ucs2_latvian_ci": 130,
"ucs2_romanian_ci": 131,
"ucs2_slovenian_ci": 132,
"ucs2_polish_ci": 133,
"ucs2_estonian_ci": 134,
"ucs2_spanish_ci": 135,
"ucs2_swedish_ci": 136,
"ucs2_turkish_ci": 137,
"ucs2_czech_ci": 138,
"ucs2_danish_ci": 139,
"ucs2_lithuanian_ci": 140,
"ucs2_slovak_ci": 141,
"ucs2_spanish2_ci": 142,
"ucs2_roman_ci": 143,
"ucs2_persian_ci": 144,
"ucs2_esperanto_ci": 145,
"ucs2_hungarian_ci": 146,
"ucs2_sinhala_ci": 147,
"ucs2_german2_ci": 148,
"ucs2_croatian_ci": 149,
"ucs2_unicode_520_ci": 150,
"ucs2_vietnamese_ci": 151,
"ucs2_general_mysql500_ci": 159,
"utf32_unicode_ci": 160,
"utf32_icelandic_ci": 161,
"utf32_latvian_ci": 162,
"utf32_romanian_ci": 163,
"utf32_slovenian_ci": 164,
"utf32_polish_ci": 165,
"utf32_estonian_ci": 166,
"utf32_spanish_ci": 167,
"utf32_swedish_ci": 168,
"utf32_turkish_ci": 169,
"utf32_czech_ci": 170,
"utf32_danish_ci": 171,
"utf32_lithuanian_ci": 172,
"utf32_slovak_ci": 173,
"utf32_spanish2_ci": 174,
"utf32_roman_ci": 175,
"utf32_persian_ci": 176,
"utf32_esperanto_ci": 177,
"utf32_hungarian_ci": 178,
"utf32_sinhala_ci": 179,
"utf32_german2_ci": 180,
"utf32_croatian_ci": 181,
"utf32_unicode_520_ci": 182,
"utf32_vietnamese_ci": 183,
"utf8_unicode_ci": 192,
"utf8_icelandic_ci": 193,
"utf8_latvian_ci": 194,
"utf8_romanian_ci": 195,
"utf8_slovenian_ci": 196,
"utf8_polish_ci": 197,
"utf8_estonian_ci": 198,
"utf8_spanish_ci": 199,
"utf8_swedish_ci": 200,
"utf8_turkish_ci": 201,
"utf8_czech_ci": 202,
"utf8_danish_ci": 203,
"utf8_lithuanian_ci": 204,
"utf8_slovak_ci": 205,
"utf8_spanish2_ci": 206,
"utf8_roman_ci": 207,
"utf8_persian_ci": 208,
"utf8_esperanto_ci": 209,
"utf8_hungarian_ci": 210,
"utf8_sinhala_ci": 211,
"utf8_german2_ci": 212,
"utf8_croatian_ci": 213,
"utf8_unicode_520_ci": 214,
"utf8_vietnamese_ci": 215,
"utf8_general_mysql500_ci": 223,
"utf8mb4_unicode_ci": 224,
"utf8mb4_icelandic_ci": 225,
"utf8mb4_latvian_ci": 226,
"utf8mb4_romanian_ci": 227,
"utf8mb4_slovenian_ci": 228,
"utf8mb4_polish_ci": 229,
"utf8mb4_estonian_ci": 230,
"utf8mb4_spanish_ci": 231,
"utf8mb4_swedish_ci": 232,
"utf8mb4_turkish_ci": 233,
"utf8mb4_czech_ci": 234,
"utf8mb4_danish_ci": 235,
"utf8mb4_lithuanian_ci": 236,
"utf8mb4_slovak_ci": 237,
"utf8mb4_spanish2_ci": 238,
"utf8mb4_roman_ci": 239,
"utf8mb4_persian_ci": 240,
"utf8mb4_esperanto_ci": 241,
"utf8mb4_hungarian_ci": 242,
"utf8mb4_sinhala_ci": 243,
"utf8mb4_german2_ci": 244,
"utf8mb4_croatian_ci": 245,
"utf8mb4_unicode_520_ci": 246,
"utf8mb4_vietnamese_ci": 247,
}
// A blacklist of collations which is unsafe to interpolate parameters.
// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
var unsafeCollations = map[string]bool{
"big5_chinese_ci": true,
"sjis_japanese_ci": true,
"gbk_chinese_ci": true,
"big5_bin": true,
"gb2312_bin": true,
"gbk_bin": true,
"sjis_bin": true,
"cp932_japanese_ci": true,
"cp932_bin": true,
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"context"
"database/sql"
"database/sql/driver"
"io"
"net"
"strconv"
"strings"
"time"
)
type mysqlConn struct {
buf buffer
netConn net.Conn
affectedRows uint64
insertId uint64
cfg *Config
maxAllowedPacket int
maxWriteSize int
writeTimeout time.Duration
flags clientFlag
status statusFlag
sequence uint8
parseTime bool
// for context support (Go 1.8+)
watching bool
watcher chan<- context.Context
closech chan struct{}
finished chan<- struct{}
canceled atomicError // set non-nil if conn is canceled
closed atomicBool // set when conn is closed, before closech is closed
}
// Handles parameters set in DSN after the connection is established
func (mc *mysqlConn) handleParams() (err error) {
for param, val := range mc.cfg.Params {
switch param {
// Charset
case "charset":
charsets := strings.Split(val, ",")
for i := range charsets {
// ignore errors here - a charset may not exist
err = mc.exec("SET NAMES " + charsets[i])
if err == nil {
break
}
}
if err != nil {
return
}
// System Vars
default:
err = mc.exec("SET " + param + "=" + val + "")
if err != nil {
return
}
}
}
return
}
func (mc *mysqlConn) markBadConn(err error) error {
if mc == nil {
return err
}
if err != errBadConnNoWrite {
return err
}
return driver.ErrBadConn
}
func (mc *mysqlConn) Begin() (driver.Tx, error) {
return mc.begin(false)
}
func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
var q string
if readOnly {
q = "START TRANSACTION READ ONLY"
} else {
q = "START TRANSACTION"
}
err := mc.exec(q)
if err == nil {
return &mysqlTx{mc}, err
}
return nil, mc.markBadConn(err)
}
func (mc *mysqlConn) Close() (err error) {
// Makes Close idempotent
if !mc.closed.IsSet() {
err = mc.writeCommandPacket(comQuit)
}
mc.cleanup()
return
}
// Closes the network connection and unsets internal variables. Do not call this
// function after successfully authentication, call Close instead. This function
// is called before auth or on auth failure because MySQL will have already
// closed the network connection.
func (mc *mysqlConn) cleanup() {
if !mc.closed.TrySet(true) {
return
}
// Makes cleanup idempotent
close(mc.closech)
if mc.netConn == nil {
return
}
if err := mc.netConn.Close(); err != nil {
errLog.Print(err)
}
}
func (mc *mysqlConn) error() error {
if mc.closed.IsSet() {
if err := mc.canceled.Value(); err != nil {
return err
}
return ErrInvalidConn
}
return nil
}
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
// Send command
err := mc.writeCommandPacketStr(comStmtPrepare, query)
if err != nil {
return nil, mc.markBadConn(err)
}
stmt := &mysqlStmt{
mc: mc,
}
// Read Result
columnCount, err := stmt.readPrepareResultPacket()
if err == nil {
if stmt.paramCount > 0 {
if err = mc.readUntilEOF(); err != nil {
return nil, err
}
}
if columnCount > 0 {
err = mc.readUntilEOF()
}
}
return stmt, err
}
func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) {
// Number of ? should be same to len(args)
if strings.Count(query, "?") != len(args) {
return "", driver.ErrSkip
}
buf, err := mc.buf.takeCompleteBuffer()
if err != nil {
// can not take the buffer. Something must be wrong with the connection
errLog.Print(err)
return "", ErrInvalidConn
}
buf = buf[:0]
argPos := 0
for i := 0; i < len(query); i++ {
q := strings.IndexByte(query[i:], '?')
if q == -1 {
buf = append(buf, query[i:]...)
break
}
buf = append(buf, query[i:i+q]...)
i += q
arg := args[argPos]
argPos++
if arg == nil {
buf = append(buf, "NULL"...)
continue
}
switch v := arg.(type) {
case int64:
buf = strconv.AppendInt(buf, v, 10)
case float64:
buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
case bool:
if v {
buf = append(buf, '1')
} else {
buf = append(buf, '0')
}
case time.Time:
if v.IsZero() {
buf = append(buf, "'0000-00-00'"...)
} else {
v := v.In(mc.cfg.Loc)
v = v.Add(time.Nanosecond * 500) // To round under microsecond
year := v.Year()
year100 := year / 100
year1 := year % 100
month := v.Month()
day := v.Day()
hour := v.Hour()
minute := v.Minute()
second := v.Second()
micro := v.Nanosecond() / 1000
buf = append(buf, []byte{
'\'',
digits10[year100], digits01[year100],
digits10[year1], digits01[year1],
'-',
digits10[month], digits01[month],
'-',
digits10[day], digits01[day],
' ',
digits10[hour], digits01[hour],
':',
digits10[minute], digits01[minute],
':',
digits10[second], digits01[second],
}...)
if micro != 0 {
micro10000 := micro / 10000
micro100 := micro / 100 % 100
micro1 := micro % 100
buf = append(buf, []byte{
'.',
digits10[micro10000], digits01[micro10000],
digits10[micro100], digits01[micro100],
digits10[micro1], digits01[micro1],
}...)
}
buf = append(buf, '\'')
}
case []byte:
if v == nil {
buf = append(buf, "NULL"...)
} else {
buf = append(buf, "_binary'"...)
if mc.status&statusNoBackslashEscapes == 0 {
buf = escapeBytesBackslash(buf, v)
} else {
buf = escapeBytesQuotes(buf, v)
}
buf = append(buf, '\'')
}
case string:
buf = append(buf, '\'')
if mc.status&statusNoBackslashEscapes == 0 {
buf = escapeStringBackslash(buf, v)
} else {
buf = escapeStringQuotes(buf, v)
}
buf = append(buf, '\'')
default:
return "", driver.ErrSkip
}
if len(buf)+4 > mc.maxAllowedPacket {
return "", driver.ErrSkip
}
}
if argPos != len(args) {
return "", driver.ErrSkip
}
return string(buf), nil
}
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
if len(args) != 0 {
if !mc.cfg.InterpolateParams {
return nil, driver.ErrSkip
}
// try to interpolate the parameters to save extra roundtrips for preparing and closing a statement
prepared, err := mc.interpolateParams(query, args)
if err != nil {
return nil, err
}
query = prepared
}
mc.affectedRows = 0
mc.insertId = 0
err := mc.exec(query)
if err == nil {
return &mysqlResult{
affectedRows: int64(mc.affectedRows),
insertId: int64(mc.insertId),
}, err
}
return nil, mc.markBadConn(err)
}
// Internal function to execute commands
func (mc *mysqlConn) exec(query string) error {
// Send command
if err := mc.writeCommandPacketStr(comQuery, query); err != nil {
return mc.markBadConn(err)
}
// Read Result
resLen, err := mc.readResultSetHeaderPacket()
if err != nil {
return err
}
if resLen > 0 {
// columns
if err := mc.readUntilEOF(); err != nil {
return err
}
// rows
if err := mc.readUntilEOF(); err != nil {
return err
}
}
return mc.discardResults()
}
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
return mc.query(query, args)
}
func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
if len(args) != 0 {
if !mc.cfg.InterpolateParams {
return nil, driver.ErrSkip
}
// try client-side prepare to reduce roundtrip
prepared, err := mc.interpolateParams(query, args)
if err != nil {
return nil, err
}
query = prepared
}
// Send command
err := mc.writeCommandPacketStr(comQuery, query)
if err == nil {
// Read Result
var resLen int
resLen, err = mc.readResultSetHeaderPacket()
if err == nil {
rows := new(textRows)
rows.mc = mc
if resLen == 0 {
rows.rs.done = true
switch err := rows.NextResultSet(); err {
case nil, io.EOF:
return rows, nil
default:
return nil, err
}
}
// Columns
rows.rs.columns, err = mc.readColumns(resLen)
return rows, err
}
}
return nil, mc.markBadConn(err)
}
// Gets the value of the given MySQL System Variable
// The returned byte slice is only valid until the next read
func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
// Send command
if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil {
return nil, err
}
// Read Result
resLen, err := mc.readResultSetHeaderPacket()
if err == nil {
rows := new(textRows)
rows.mc = mc
rows.rs.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
if resLen > 0 {
// Columns
if err := mc.readUntilEOF(); err != nil {
return nil, err
}
}
dest := make([]driver.Value, resLen)
if err = rows.readRow(dest); err == nil {
return dest[0].([]byte), mc.readUntilEOF()
}
}
return nil, err
}
// finish is called when the query has canceled.
func (mc *mysqlConn) cancel(err error) {
mc.canceled.Set(err)
mc.cleanup()
}
// finish is called when the query has succeeded.
func (mc *mysqlConn) finish() {
if !mc.watching || mc.finished == nil {
return
}
select {
case mc.finished <- struct{}{}:
mc.watching = false
case <-mc.closech:
}
}
// Ping implements driver.Pinger interface
func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return driver.ErrBadConn
}
if err = mc.watchCancel(ctx); err != nil {
return
}
defer mc.finish()
if err = mc.writeCommandPacket(comPing); err != nil {
return mc.markBadConn(err)
}
return mc.readResultOK()
}
// BeginTx implements driver.ConnBeginTx interface
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
defer mc.finish()
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
level, err := mapIsolationLevel(opts.Isolation)
if err != nil {
return nil, err
}
err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
if err != nil {
return nil, err
}
}
return mc.begin(opts.ReadOnly)
}
func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
rows, err := mc.query(query, dargs)
if err != nil {
mc.finish()
return nil, err
}
rows.finish = mc.finish
return rows, err
}
func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
defer mc.finish()
return mc.Exec(query, dargs)
}
func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
stmt, err := mc.Prepare(query)
mc.finish()
if err != nil {
return nil, err
}
select {
default:
case <-ctx.Done():
stmt.Close()
return nil, ctx.Err()
}
return stmt, nil
}
func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := stmt.mc.watchCancel(ctx); err != nil {
return nil, err
}
rows, err := stmt.query(dargs)
if err != nil {
stmt.mc.finish()
return nil, err
}
rows.finish = stmt.mc.finish
return rows, err
}
func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := stmt.mc.watchCancel(ctx); err != nil {
return nil, err
}
defer stmt.mc.finish()
return stmt.Exec(dargs)
}
func (mc *mysqlConn) watchCancel(ctx context.Context) error {
if mc.watching {
// Reach here if canceled,
// so the connection is already invalid
mc.cleanup()
return nil
}
// When ctx is already cancelled, don't watch it.
if err := ctx.Err(); err != nil {
return err
}
// When ctx is not cancellable, don't watch it.
if ctx.Done() == nil {
return nil
}
// When watcher is not alive, can't watch it.
if mc.watcher == nil {
return nil
}
mc.watching = true
mc.watcher <- ctx
return nil
}
func (mc *mysqlConn) startWatcher() {
watcher := make(chan context.Context, 1)
mc.watcher = watcher
finished := make(chan struct{})
mc.finished = finished
go func() {
for {
var ctx context.Context
select {
case ctx = <-watcher:
case <-mc.closech:
return
}
select {
case <-ctx.Done():
mc.cancel(ctx.Err())
case <-finished:
case <-mc.closech:
return
}
}
}()
}
func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
nv.Value, err = converter{}.ConvertValue(nv.Value)
return
}
// ResetSession implements driver.SessionResetter.
// (From Go 1.10)
func (mc *mysqlConn) ResetSession(ctx context.Context) error {
if mc.closed.IsSet() {
return driver.ErrBadConn
}
return nil
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
const (
defaultAuthPlugin = "mysql_native_password"
defaultMaxAllowedPacket = 4 << 20 // 4 MiB
minProtocolVersion = 10
maxPacketSize = 1<<24 - 1
timeFormat = "2006-01-02 15:04:05.999999"
)
// MySQL constants documentation:
// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
const (
iOK byte = 0x00
iAuthMoreData byte = 0x01
iLocalInFile byte = 0xfb
iEOF byte = 0xfe
iERR byte = 0xff
)
// https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
type clientFlag uint32
const (
clientLongPassword clientFlag = 1 << iota
clientFoundRows
clientLongFlag
clientConnectWithDB
clientNoSchema
clientCompress
clientODBC
clientLocalFiles
clientIgnoreSpace
clientProtocol41
clientInteractive
clientSSL
clientIgnoreSIGPIPE
clientTransactions
clientReserved
clientSecureConn
clientMultiStatements
clientMultiResults
clientPSMultiResults
clientPluginAuth
clientConnectAttrs
clientPluginAuthLenEncClientData
clientCanHandleExpiredPasswords
clientSessionTrack
clientDeprecateEOF
)
const (
comQuit byte = iota + 1
comInitDB
comQuery
comFieldList
comCreateDB
comDropDB
comRefresh
comShutdown
comStatistics
comProcessInfo
comConnect
comProcessKill
comDebug
comPing
comTime
comDelayedInsert
comChangeUser
comBinlogDump
comTableDump
comConnectOut
comRegisterSlave
comStmtPrepare
comStmtExecute
comStmtSendLongData
comStmtClose
comStmtReset
comSetOption
comStmtFetch
)
// https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType
type fieldType byte
const (
fieldTypeDecimal fieldType = iota
fieldTypeTiny
fieldTypeShort
fieldTypeLong
fieldTypeFloat
fieldTypeDouble
fieldTypeNULL
fieldTypeTimestamp
fieldTypeLongLong
fieldTypeInt24
fieldTypeDate
fieldTypeTime
fieldTypeDateTime
fieldTypeYear
fieldTypeNewDate
fieldTypeVarChar
fieldTypeBit
)
const (
fieldTypeJSON fieldType = iota + 0xf5
fieldTypeNewDecimal
fieldTypeEnum
fieldTypeSet
fieldTypeTinyBLOB
fieldTypeMediumBLOB
fieldTypeLongBLOB
fieldTypeBLOB
fieldTypeVarString
fieldTypeString
fieldTypeGeometry
)
type fieldFlag uint16
const (
flagNotNULL fieldFlag = 1 << iota
flagPriKey
flagUniqueKey
flagMultipleKey
flagBLOB
flagUnsigned
flagZeroFill
flagBinary
flagEnum
flagAutoIncrement
flagTimestamp
flagSet
flagUnknown1
flagUnknown2
flagUnknown3
flagUnknown4
)
// http://dev.mysql.com/doc/internals/en/status-flags.html
type statusFlag uint16
const (
statusInTrans statusFlag = 1 << iota
statusInAutocommit
statusReserved // Not in documentation
statusMoreResultsExists
statusNoGoodIndexUsed
statusNoIndexUsed
statusCursorExists
statusLastRowSent
statusDbDropped
statusNoBackslashEscapes
statusMetadataChanged
statusQueryWasSlow
statusPsOutParams
statusInTransReadonly
statusSessionStateChanged
)
const (
cachingSha2PasswordRequestPublicKey = 2
cachingSha2PasswordFastAuthSuccess = 3
cachingSha2PasswordPerformFullAuthentication = 4
)
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// Package mysql provides a MySQL driver for Go's database/sql package.
//
// The driver should be used via the database/sql package:
//
// import "database/sql"
// import _ "github.com/go-sql-driver/mysql"
//
// db, err := sql.Open("mysql", "user:password@/dbname")
//
// See https://github.com/go-sql-driver/mysql#usage for details
package mysql
import (
"database/sql"
"database/sql/driver"
"net"
"sync"
)
// MySQLDriver is exported to make the driver directly accessible.
// In general the driver is used via the database/sql package.
type MySQLDriver struct{}
// DialFunc is a function which can be used to establish the network connection.
// Custom dial functions must be registered with RegisterDial
type DialFunc func(addr string) (net.Conn, error)
var (
dialsLock sync.RWMutex
dials map[string]DialFunc
)
// RegisterDial registers a custom dial function. It can then be used by the
// network address mynet(addr), where mynet is the registered new network.
// addr is passed as a parameter to the dial function.
func RegisterDial(net string, dial DialFunc) {
dialsLock.Lock()
defer dialsLock.Unlock()
if dials == nil {
dials = make(map[string]DialFunc)
}
dials[net] = dial
}
// Open new Connection.
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
// the DSN string is formatted
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
var err error
// New mysqlConn
mc := &mysqlConn{
maxAllowedPacket: maxPacketSize,
maxWriteSize: maxPacketSize - 1,
closech: make(chan struct{}),
}
mc.cfg, err = ParseDSN(dsn)
if err != nil {
return nil, err
}
mc.parseTime = mc.cfg.ParseTime
// Connect to Server
dialsLock.RLock()
dial, ok := dials[mc.cfg.Net]
dialsLock.RUnlock()
if ok {
mc.netConn, err = dial(mc.cfg.Addr)
} else {
nd := net.Dialer{Timeout: mc.cfg.Timeout}
mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr)
}
if err != nil {
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
errLog.Print("net.Error from Dial()': ", nerr.Error())
return nil, driver.ErrBadConn
}
return nil, err
}
// Enable TCP Keepalives on TCP connections
if tc, ok := mc.netConn.(*net.TCPConn); ok {
if err := tc.SetKeepAlive(true); err != nil {
// Don't send COM_QUIT before handshake.
mc.netConn.Close()
mc.netConn = nil
return nil, err
}
}
// Call startWatcher for context support (From Go 1.8)
mc.startWatcher()
mc.buf = newBuffer(mc.netConn)
// Set I/O timeouts
mc.buf.timeout = mc.cfg.ReadTimeout
mc.writeTimeout = mc.cfg.WriteTimeout
// Reading Handshake Initialization Packet
authData, plugin, err := mc.readHandshakePacket()
if err != nil {
mc.cleanup()
return nil, err
}
if plugin == "" {
plugin = defaultAuthPlugin
}
// Send Client Authentication Packet
authResp, err := mc.auth(authData, plugin)
if err != nil {
// try the default auth plugin, if using the requested plugin failed
errLog.Print("could not use requested auth plugin '"+plugin+"': ", err.Error())
plugin = defaultAuthPlugin
authResp, err = mc.auth(authData, plugin)
if err != nil {
mc.cleanup()
return nil, err
}
}
if err = mc.writeHandshakeResponsePacket(authResp, plugin); err != nil {
mc.cleanup()
return nil, err
}
// Handle response to auth packet, switch methods if possible
if err = mc.handleAuthResult(authData, plugin); err != nil {
// Authentication failed and MySQL has already closed the connection
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
// Do not send COM_QUIT, just cleanup and return the error.
mc.cleanup()
return nil, err
}
if mc.cfg.MaxAllowedPacket > 0 {
mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket
} else {
// Get max allowed packet size
maxap, err := mc.getSystemVar("max_allowed_packet")
if err != nil {
mc.Close()
return nil, err
}
mc.maxAllowedPacket = stringToInt(maxap) - 1
}
if mc.maxAllowedPacket < maxPacketSize {
mc.maxWriteSize = mc.maxAllowedPacket
}
// Handle DSN Params
err = mc.handleParams()
if err != nil {
mc.Close()
return nil, err
}
return mc, nil
}
func init() {
sql.Register("mysql", &MySQLDriver{})
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2016 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"bytes"
"crypto/rsa"
"crypto/tls"
"errors"
"fmt"
"net"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
var (
errInvalidDSNUnescaped = errors.New("invalid DSN: did you forget to escape a param value?")
errInvalidDSNAddr = errors.New("invalid DSN: network address not terminated (missing closing brace)")
errInvalidDSNNoSlash = errors.New("invalid DSN: missing the slash separating the database name")
errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations")
)
// Config is a configuration parsed from a DSN string.
// If a new Config is created instead of being parsed from a DSN string,
// the NewConfig function should be used, which sets default values.
type Config struct {
User string // Username
Passwd string // Password (requires User)
Net string // Network type
Addr string // Network address (requires Net)
DBName string // Database name
Params map[string]string // Connection parameters
Collation string // Connection collation
Loc *time.Location // Location for time.Time values
MaxAllowedPacket int // Max packet size allowed
ServerPubKey string // Server public key name
pubKey *rsa.PublicKey // Server public key
TLSConfig string // TLS configuration name
tls *tls.Config // TLS configuration
Timeout time.Duration // Dial timeout
ReadTimeout time.Duration // I/O read timeout
WriteTimeout time.Duration // I/O write timeout
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
AllowCleartextPasswords bool // Allows the cleartext client side plugin
AllowNativePasswords bool // Allows the native password authentication method
AllowOldPasswords bool // Allows the old insecure password method
ClientFoundRows bool // Return number of matching rows instead of rows changed
ColumnsWithAlias bool // Prepend table alias to column names
InterpolateParams bool // Interpolate placeholders into query string
MultiStatements bool // Allow multiple statements in one query
ParseTime bool // Parse time values to time.Time
RejectReadOnly bool // Reject read-only connections
}
// NewConfig creates a new Config and sets default values.
func NewConfig() *Config {
return &Config{
Collation: defaultCollation,
Loc: time.UTC,
MaxAllowedPacket: defaultMaxAllowedPacket,
AllowNativePasswords: true,
}
}
func (cfg *Config) normalize() error {
if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
return errInvalidDSNUnsafeCollation
}
// Set default network if empty
if cfg.Net == "" {
cfg.Net = "tcp"
}
// Set default address if empty
if cfg.Addr == "" {
switch cfg.Net {
case "tcp":
cfg.Addr = "127.0.0.1:3306"
case "unix":
cfg.Addr = "/tmp/mysql.sock"
default:
return errors.New("default addr for network '" + cfg.Net + "' unknown")
}
} else if cfg.Net == "tcp" {
cfg.Addr = ensureHavePort(cfg.Addr)
}
if cfg.tls != nil {
if cfg.tls.ServerName == "" && !cfg.tls.InsecureSkipVerify {
host, _, err := net.SplitHostPort(cfg.Addr)
if err == nil {
cfg.tls.ServerName = host
}
}
}
return nil
}
// FormatDSN formats the given Config into a DSN string which can be passed to
// the driver.
func (cfg *Config) FormatDSN() string {
var buf bytes.Buffer
// [username[:password]@]
if len(cfg.User) > 0 {
buf.WriteString(cfg.User)
if len(cfg.Passwd) > 0 {
buf.WriteByte(':')
buf.WriteString(cfg.Passwd)
}
buf.WriteByte('@')
}
// [protocol[(address)]]
if len(cfg.Net) > 0 {
buf.WriteString(cfg.Net)
if len(cfg.Addr) > 0 {
buf.WriteByte('(')
buf.WriteString(cfg.Addr)
buf.WriteByte(')')
}
}
// /dbname
buf.WriteByte('/')
buf.WriteString(cfg.DBName)
// [?param1=value1&...&paramN=valueN]
hasParam := false
if cfg.AllowAllFiles {
hasParam = true
buf.WriteString("?allowAllFiles=true")
}
if cfg.AllowCleartextPasswords {
if hasParam {
buf.WriteString("&allowCleartextPasswords=true")
} else {
hasParam = true
buf.WriteString("?allowCleartextPasswords=true")
}
}
if !cfg.AllowNativePasswords {
if hasParam {
buf.WriteString("&allowNativePasswords=false")
} else {
hasParam = true
buf.WriteString("?allowNativePasswords=false")
}
}
if cfg.AllowOldPasswords {
if hasParam {
buf.WriteString("&allowOldPasswords=true")
} else {
hasParam = true
buf.WriteString("?allowOldPasswords=true")
}
}
if cfg.ClientFoundRows {
if hasParam {
buf.WriteString("&clientFoundRows=true")
} else {
hasParam = true
buf.WriteString("?clientFoundRows=true")
}
}
if col := cfg.Collation; col != defaultCollation && len(col) > 0 {
if hasParam {
buf.WriteString("&collation=")
} else {
hasParam = true
buf.WriteString("?collation=")
}
buf.WriteString(col)
}
if cfg.ColumnsWithAlias {
if hasParam {
buf.WriteString("&columnsWithAlias=true")
} else {
hasParam = true
buf.WriteString("?columnsWithAlias=true")
}
}
if cfg.InterpolateParams {
if hasParam {
buf.WriteString("&interpolateParams=true")
} else {
hasParam = true
buf.WriteString("?interpolateParams=true")
}
}
if cfg.Loc != time.UTC && cfg.Loc != nil {
if hasParam {
buf.WriteString("&loc=")
} else {
hasParam = true
buf.WriteString("?loc=")
}
buf.WriteString(url.QueryEscape(cfg.Loc.String()))
}
if cfg.MultiStatements {
if hasParam {
buf.WriteString("&multiStatements=true")
} else {
hasParam = true
buf.WriteString("?multiStatements=true")
}
}
if cfg.ParseTime {
if hasParam {
buf.WriteString("&parseTime=true")
} else {
hasParam = true
buf.WriteString("?parseTime=true")
}
}
if cfg.ReadTimeout > 0 {
if hasParam {
buf.WriteString("&readTimeout=")
} else {
hasParam = true
buf.WriteString("?readTimeout=")
}
buf.WriteString(cfg.ReadTimeout.String())
}
if cfg.RejectReadOnly {
if hasParam {
buf.WriteString("&rejectReadOnly=true")
} else {
hasParam = true
buf.WriteString("?rejectReadOnly=true")
}
}
if len(cfg.ServerPubKey) > 0 {
if hasParam {
buf.WriteString("&serverPubKey=")
} else {
hasParam = true
buf.WriteString("?serverPubKey=")
}
buf.WriteString(url.QueryEscape(cfg.ServerPubKey))
}
if cfg.Timeout > 0 {
if hasParam {
buf.WriteString("&timeout=")
} else {
hasParam = true
buf.WriteString("?timeout=")
}
buf.WriteString(cfg.Timeout.String())
}
if len(cfg.TLSConfig) > 0 {
if hasParam {
buf.WriteString("&tls=")
} else {
hasParam = true
buf.WriteString("?tls=")
}
buf.WriteString(url.QueryEscape(cfg.TLSConfig))
}
if cfg.WriteTimeout > 0 {
if hasParam {
buf.WriteString("&writeTimeout=")
} else {
hasParam = true
buf.WriteString("?writeTimeout=")
}
buf.WriteString(cfg.WriteTimeout.String())
}
if cfg.MaxAllowedPacket != defaultMaxAllowedPacket {
if hasParam {
buf.WriteString("&maxAllowedPacket=")
} else {
hasParam = true
buf.WriteString("?maxAllowedPacket=")
}
buf.WriteString(strconv.Itoa(cfg.MaxAllowedPacket))
}
// other params
if cfg.Params != nil {
var params []string
for param := range cfg.Params {
params = append(params, param)
}
sort.Strings(params)
for _, param := range params {
if hasParam {
buf.WriteByte('&')
} else {
hasParam = true
buf.WriteByte('?')
}
buf.WriteString(param)
buf.WriteByte('=')
buf.WriteString(url.QueryEscape(cfg.Params[param]))
}
}
return buf.String()
}
// ParseDSN parses the DSN string to a Config
func ParseDSN(dsn string) (cfg *Config, err error) {
// New config with some default values
cfg = NewConfig()
// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
// Find the last '/' (since the password or the net addr might contain a '/')
foundSlash := false
for i := len(dsn) - 1; i >= 0; i-- {
if dsn[i] == '/' {
foundSlash = true
var j, k int
// left part is empty if i <= 0
if i > 0 {
// [username[:password]@][protocol[(address)]]
// Find the last '@' in dsn[:i]
for j = i; j >= 0; j-- {
if dsn[j] == '@' {
// username[:password]
// Find the first ':' in dsn[:j]
for k = 0; k < j; k++ {
if dsn[k] == ':' {
cfg.Passwd = dsn[k+1 : j]
break
}
}
cfg.User = dsn[:k]
break
}
}
// [protocol[(address)]]
// Find the first '(' in dsn[j+1:i]
for k = j + 1; k < i; k++ {
if dsn[k] == '(' {
// dsn[i-1] must be == ')' if an address is specified
if dsn[i-1] != ')' {
if strings.ContainsRune(dsn[k+1:i], ')') {
return nil, errInvalidDSNUnescaped
}
return nil, errInvalidDSNAddr
}
cfg.Addr = dsn[k+1 : i-1]
break
}
}
cfg.Net = dsn[j+1 : k]
}
// dbname[?param1=value1&...&paramN=valueN]
// Find the first '?' in dsn[i+1:]
for j = i + 1; j < len(dsn); j++ {
if dsn[j] == '?' {
if err = parseDSNParams(cfg, dsn[j+1:]); err != nil {
return
}
break
}
}
cfg.DBName = dsn[i+1 : j]
break
}
}
if !foundSlash && len(dsn) > 0 {
return nil, errInvalidDSNNoSlash
}
if err = cfg.normalize(); err != nil {
return nil, err
}
return
}
// parseDSNParams parses the DSN "query string"
// Values must be url.QueryEscape'ed
func parseDSNParams(cfg *Config, params string) (err error) {
for _, v := range strings.Split(params, "&") {
param := strings.SplitN(v, "=", 2)
if len(param) != 2 {
continue
}
// cfg params
switch value := param[1]; param[0] {
// Disable INFILE whitelist / enable all files
case "allowAllFiles":
var isBool bool
cfg.AllowAllFiles, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Use cleartext authentication mode (MySQL 5.5.10+)
case "allowCleartextPasswords":
var isBool bool
cfg.AllowCleartextPasswords, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Use native password authentication
case "allowNativePasswords":
var isBool bool
cfg.AllowNativePasswords, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Use old authentication mode (pre MySQL 4.1)
case "allowOldPasswords":
var isBool bool
cfg.AllowOldPasswords, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Switch "rowsAffected" mode
case "clientFoundRows":
var isBool bool
cfg.ClientFoundRows, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Collation
case "collation":
cfg.Collation = value
break
case "columnsWithAlias":
var isBool bool
cfg.ColumnsWithAlias, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Compression
case "compress":
return errors.New("compression not implemented yet")
// Enable client side placeholder substitution
case "interpolateParams":
var isBool bool
cfg.InterpolateParams, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Time Location
case "loc":
if value, err = url.QueryUnescape(value); err != nil {
return
}
cfg.Loc, err = time.LoadLocation(value)
if err != nil {
return
}
// multiple statements in one query
case "multiStatements":
var isBool bool
cfg.MultiStatements, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// time.Time parsing
case "parseTime":
var isBool bool
cfg.ParseTime, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// I/O read Timeout
case "readTimeout":
cfg.ReadTimeout, err = time.ParseDuration(value)
if err != nil {
return
}
// Reject read-only connections
case "rejectReadOnly":
var isBool bool
cfg.RejectReadOnly, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Server public key
case "serverPubKey":
name, err := url.QueryUnescape(value)
if err != nil {
return fmt.Errorf("invalid value for server pub key name: %v", err)
}
if pubKey := getServerPubKey(name); pubKey != nil {
cfg.ServerPubKey = name
cfg.pubKey = pubKey
} else {
return errors.New("invalid value / unknown server pub key name: " + name)
}
// Strict mode
case "strict":
panic("strict mode has been removed. See https://github.com/go-sql-driver/mysql/wiki/strict-mode")
// Dial Timeout
case "timeout":
cfg.Timeout, err = time.ParseDuration(value)
if err != nil {
return
}
// TLS-Encryption
case "tls":
boolValue, isBool := readBool(value)
if isBool {
if boolValue {
cfg.TLSConfig = "true"
cfg.tls = &tls.Config{}
} else {
cfg.TLSConfig = "false"
}
} else if vl := strings.ToLower(value); vl == "skip-verify" || vl == "preferred" {
cfg.TLSConfig = vl
cfg.tls = &tls.Config{InsecureSkipVerify: true}
} else {
name, err := url.QueryUnescape(value)
if err != nil {
return fmt.Errorf("invalid value for TLS config name: %v", err)
}
if tlsConfig := getTLSConfigClone(name); tlsConfig != nil {
cfg.TLSConfig = name
cfg.tls = tlsConfig
} else {
return errors.New("invalid value / unknown config name: " + name)
}
}
// I/O write Timeout
case "writeTimeout":
cfg.WriteTimeout, err = time.ParseDuration(value)
if err != nil {
return
}
case "maxAllowedPacket":
cfg.MaxAllowedPacket, err = strconv.Atoi(value)
if err != nil {
return
}
default:
// lazy init
if cfg.Params == nil {
cfg.Params = make(map[string]string)
}
if cfg.Params[param[0]], err = url.QueryUnescape(value); err != nil {
return
}
}
}
return
}
func ensureHavePort(addr string) string {
if _, _, err := net.SplitHostPort(addr); err != nil {
return net.JoinHostPort(addr, "3306")
}
return addr
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"errors"
"fmt"
"log"
"os"
)
// Various errors the driver might return. Can change between driver versions.
var (
ErrInvalidConn = errors.New("invalid connection")
ErrMalformPkt = errors.New("malformed packet")
ErrNoTLS = errors.New("TLS requested but server does not support TLS")
ErrCleartextPassword = errors.New("this user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN")
ErrNativePassword = errors.New("this user requires mysql native password authentication.")
ErrOldPassword = errors.New("this user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
ErrUnknownPlugin = errors.New("this authentication plugin is not supported")
ErrOldProtocol = errors.New("MySQL server does not support required protocol 41+")
ErrPktSync = errors.New("commands out of sync. You can't run this command now")
ErrPktSyncMul = errors.New("commands out of sync. Did you run multiple statements at once?")
ErrPktTooLarge = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server")
ErrBusyBuffer = errors.New("busy buffer")
// errBadConnNoWrite is used for connection errors where nothing was sent to the database yet.
// If this happens first in a function starting a database interaction, it should be replaced by driver.ErrBadConn
// to trigger a resend.
// See https://github.com/go-sql-driver/mysql/pull/302
errBadConnNoWrite = errors.New("bad connection")
)
var errLog = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))
// Logger is used to log critical error messages.
type Logger interface {
Print(v ...interface{})
}
// SetLogger is used to set the logger for critical errors.
// The initial logger is os.Stderr.
func SetLogger(logger Logger) error {
if logger == nil {
return errors.New("logger is nil")
}
errLog = logger
return nil
}
// MySQLError is an error type which represents a single MySQL error
type MySQLError struct {
Number uint16
Message string
}
func (me *MySQLError) Error() string {
return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"database/sql"
"reflect"
)
func (mf *mysqlField) typeDatabaseName() string {
switch mf.fieldType {
case fieldTypeBit:
return "BIT"
case fieldTypeBLOB:
if mf.charSet != collations[binaryCollation] {
return "TEXT"
}
return "BLOB"
case fieldTypeDate:
return "DATE"
case fieldTypeDateTime:
return "DATETIME"
case fieldTypeDecimal:
return "DECIMAL"
case fieldTypeDouble:
return "DOUBLE"
case fieldTypeEnum:
return "ENUM"
case fieldTypeFloat:
return "FLOAT"
case fieldTypeGeometry:
return "GEOMETRY"
case fieldTypeInt24:
return "MEDIUMINT"
case fieldTypeJSON:
return "JSON"
case fieldTypeLong:
return "INT"
case fieldTypeLongBLOB:
if mf.charSet != collations[binaryCollation] {
return "LONGTEXT"
}
return "LONGBLOB"
case fieldTypeLongLong:
return "BIGINT"
case fieldTypeMediumBLOB:
if mf.charSet != collations[binaryCollation] {
return "MEDIUMTEXT"
}
return "MEDIUMBLOB"
case fieldTypeNewDate:
return "DATE"
case fieldTypeNewDecimal:
return "DECIMAL"
case fieldTypeNULL:
return "NULL"
case fieldTypeSet:
return "SET"
case fieldTypeShort:
return "SMALLINT"
case fieldTypeString:
if mf.charSet == collations[binaryCollation] {
return "BINARY"
}
return "CHAR"
case fieldTypeTime:
return "TIME"
case fieldTypeTimestamp:
return "TIMESTAMP"
case fieldTypeTiny:
return "TINYINT"
case fieldTypeTinyBLOB:
if mf.charSet != collations[binaryCollation] {
return "TINYTEXT"
}
return "TINYBLOB"
case fieldTypeVarChar:
if mf.charSet == collations[binaryCollation] {
return "VARBINARY"
}
return "VARCHAR"
case fieldTypeVarString:
if mf.charSet == collations[binaryCollation] {
return "VARBINARY"
}
return "VARCHAR"
case fieldTypeYear:
return "YEAR"
default:
return ""
}
}
var (
scanTypeFloat32 = reflect.TypeOf(float32(0))
scanTypeFloat64 = reflect.TypeOf(float64(0))
scanTypeInt8 = reflect.TypeOf(int8(0))
scanTypeInt16 = reflect.TypeOf(int16(0))
scanTypeInt32 = reflect.TypeOf(int32(0))
scanTypeInt64 = reflect.TypeOf(int64(0))
scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
scanTypeNullInt = reflect.TypeOf(sql.NullInt64{})
scanTypeNullTime = reflect.TypeOf(NullTime{})
scanTypeUint8 = reflect.TypeOf(uint8(0))
scanTypeUint16 = reflect.TypeOf(uint16(0))
scanTypeUint32 = reflect.TypeOf(uint32(0))
scanTypeUint64 = reflect.TypeOf(uint64(0))
scanTypeRawBytes = reflect.TypeOf(sql.RawBytes{})
scanTypeUnknown = reflect.TypeOf(new(interface{}))
)
type mysqlField struct {
tableName string
name string
length uint32
flags fieldFlag
fieldType fieldType
decimals byte
charSet uint8
}
func (mf *mysqlField) scanType() reflect.Type {
switch mf.fieldType {
case fieldTypeTiny:
if mf.flags&flagNotNULL != 0 {
if mf.flags&flagUnsigned != 0 {
return scanTypeUint8
}
return scanTypeInt8
}
return scanTypeNullInt
case fieldTypeShort, fieldTypeYear:
if mf.flags&flagNotNULL != 0 {
if mf.flags&flagUnsigned != 0 {
return scanTypeUint16
}
return scanTypeInt16
}
return scanTypeNullInt
case fieldTypeInt24, fieldTypeLong:
if mf.flags&flagNotNULL != 0 {
if mf.flags&flagUnsigned != 0 {
return scanTypeUint32
}
return scanTypeInt32
}
return scanTypeNullInt
case fieldTypeLongLong:
if mf.flags&flagNotNULL != 0 {
if mf.flags&flagUnsigned != 0 {
return scanTypeUint64
}
return scanTypeInt64
}
return scanTypeNullInt
case fieldTypeFloat:
if mf.flags&flagNotNULL != 0 {
return scanTypeFloat32
}
return scanTypeNullFloat
case fieldTypeDouble:
if mf.flags&flagNotNULL != 0 {
return scanTypeFloat64
}
return scanTypeNullFloat
case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar,
fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB,
fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB,
fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON,
fieldTypeTime:
return scanTypeRawBytes
case fieldTypeDate, fieldTypeNewDate,
fieldTypeTimestamp, fieldTypeDateTime:
// NullTime is always returned for more consistent behavior as it can
// handle both cases of parseTime regardless if the field is nullable.
return scanTypeNullTime
default:
return scanTypeUnknown
}
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"fmt"
"io"
"os"
"strings"
"sync"
)
var (
fileRegister map[string]bool
fileRegisterLock sync.RWMutex
readerRegister map[string]func() io.Reader
readerRegisterLock sync.RWMutex
)
// RegisterLocalFile adds the given file to the file whitelist,
// so that it can be used by "LOAD DATA LOCAL INFILE <filepath>".
// Alternatively you can allow the use of all local files with
// the DSN parameter 'allowAllFiles=true'
//
// filePath := "/home/gopher/data.csv"
// mysql.RegisterLocalFile(filePath)
// err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo")
// if err != nil {
// ...
//
func RegisterLocalFile(filePath string) {
fileRegisterLock.Lock()
// lazy map init
if fileRegister == nil {
fileRegister = make(map[string]bool)
}
fileRegister[strings.Trim(filePath, `"`)] = true
fileRegisterLock.Unlock()
}
// DeregisterLocalFile removes the given filepath from the whitelist.
func DeregisterLocalFile(filePath string) {
fileRegisterLock.Lock()
delete(fileRegister, strings.Trim(filePath, `"`))
fileRegisterLock.Unlock()
}
// RegisterReaderHandler registers a handler function which is used
// to receive a io.Reader.
// The Reader can be used by "LOAD DATA LOCAL INFILE Reader::<name>".
// If the handler returns a io.ReadCloser Close() is called when the
// request is finished.
//
// mysql.RegisterReaderHandler("data", func() io.Reader {
// var csvReader io.Reader // Some Reader that returns CSV data
// ... // Open Reader here
// return csvReader
// })
// err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo")
// if err != nil {
// ...
//
func RegisterReaderHandler(name string, handler func() io.Reader) {
readerRegisterLock.Lock()
// lazy map init
if readerRegister == nil {
readerRegister = make(map[string]func() io.Reader)
}
readerRegister[name] = handler
readerRegisterLock.Unlock()
}
// DeregisterReaderHandler removes the ReaderHandler function with
// the given name from the registry.
func DeregisterReaderHandler(name string) {
readerRegisterLock.Lock()
delete(readerRegister, name)
readerRegisterLock.Unlock()
}
func deferredClose(err *error, closer io.Closer) {
closeErr := closer.Close()
if *err == nil {
*err = closeErr
}
}
func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
var rdr io.Reader
var data []byte
packetSize := 16 * 1024 // 16KB is small enough for disk readahead and large enough for TCP
if mc.maxWriteSize < packetSize {
packetSize = mc.maxWriteSize
}
if idx := strings.Index(name, "Reader::"); idx == 0 || (idx > 0 && name[idx-1] == '/') { // io.Reader
// The server might return an an absolute path. See issue #355.
name = name[idx+8:]
readerRegisterLock.RLock()
handler, inMap := readerRegister[name]
readerRegisterLock.RUnlock()
if inMap {
rdr = handler()
if rdr != nil {
if cl, ok := rdr.(io.Closer); ok {
defer deferredClose(&err, cl)
}
} else {
err = fmt.Errorf("Reader '%s' is <nil>", name)
}
} else {
err = fmt.Errorf("Reader '%s' is not registered", name)
}
} else { // File
name = strings.Trim(name, `"`)
fileRegisterLock.RLock()
fr := fileRegister[name]
fileRegisterLock.RUnlock()
if mc.cfg.AllowAllFiles || fr {
var file *os.File
var fi os.FileInfo
if file, err = os.Open(name); err == nil {
defer deferredClose(&err, file)
// get file size
if fi, err = file.Stat(); err == nil {
rdr = file
if fileSize := int(fi.Size()); fileSize < packetSize {
packetSize = fileSize
}
}
}
} else {
err = fmt.Errorf("local file '%s' is not registered", name)
}
}
// send content packets
// if packetSize == 0, the Reader contains no data
if err == nil && packetSize > 0 {
data := make([]byte, 4+packetSize)
var n int
for err == nil {
n, err = rdr.Read(data[4:])
if n > 0 {
if ioErr := mc.writePacket(data[:4+n]); ioErr != nil {
return ioErr
}
}
}
if err == io.EOF {
err = nil
}
}
// send empty packet (termination)
if data == nil {
data = make([]byte, 4)
}
if ioErr := mc.writePacket(data[:4]); ioErr != nil {
return ioErr
}
// read OK packet
if err == nil {
return mc.readResultOK()
}
mc.readPacket()
return err
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"bytes"
"crypto/tls"
"database/sql/driver"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"time"
)
// Packets documentation:
// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
// Read packet to buffer 'data'
func (mc *mysqlConn) readPacket() ([]byte, error) {
var prevData []byte
for {
// read packet header
data, err := mc.buf.readNext(4)
if err != nil {
if cerr := mc.canceled.Value(); cerr != nil {
return nil, cerr
}
errLog.Print(err)
mc.Close()
return nil, ErrInvalidConn
}
// packet length [24 bit]
pktLen := int(uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16)
// check packet sync [8 bit]
if data[3] != mc.sequence {
if data[3] > mc.sequence {
return nil, ErrPktSyncMul
}
return nil, ErrPktSync
}
mc.sequence++
// packets with length 0 terminate a previous packet which is a
// multiple of (2^24)-1 bytes long
if pktLen == 0 {
// there was no previous packet
if prevData == nil {
errLog.Print(ErrMalformPkt)
mc.Close()
return nil, ErrInvalidConn
}
return prevData, nil
}
// read packet body [pktLen bytes]
data, err = mc.buf.readNext(pktLen)
if err != nil {
if cerr := mc.canceled.Value(); cerr != nil {
return nil, cerr
}
errLog.Print(err)
mc.Close()
return nil, ErrInvalidConn
}
// return data if this was the last packet
if pktLen < maxPacketSize {
// zero allocations for non-split packets
if prevData == nil {
return data, nil
}
return append(prevData, data...), nil
}
prevData = append(prevData, data...)
}
}
// Write packet buffer 'data'
func (mc *mysqlConn) writePacket(data []byte) error {
pktLen := len(data) - 4
if pktLen > mc.maxAllowedPacket {
return ErrPktTooLarge
}
for {
var size int
if pktLen >= maxPacketSize {
data[0] = 0xff
data[1] = 0xff
data[2] = 0xff
size = maxPacketSize
} else {
data[0] = byte(pktLen)
data[1] = byte(pktLen >> 8)
data[2] = byte(pktLen >> 16)
size = pktLen
}
data[3] = mc.sequence
// Write packet
if mc.writeTimeout > 0 {
if err := mc.netConn.SetWriteDeadline(time.Now().Add(mc.writeTimeout)); err != nil {
return err
}
}
n, err := mc.netConn.Write(data[:4+size])
if err == nil && n == 4+size {
mc.sequence++
if size != maxPacketSize {
return nil
}
pktLen -= size
data = data[size:]
continue
}
// Handle error
if err == nil { // n != len(data)
mc.cleanup()
errLog.Print(ErrMalformPkt)
} else {
if cerr := mc.canceled.Value(); cerr != nil {
return cerr
}
if n == 0 && pktLen == len(data)-4 {
// only for the first loop iteration when nothing was written yet
return errBadConnNoWrite
}
mc.cleanup()
errLog.Print(err)
}
return ErrInvalidConn
}
}
/******************************************************************************
* Initialization Process *
******************************************************************************/
// Handshake Initialization Packet
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake
func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err error) {
data, err = mc.readPacket()
if err != nil {
// for init we can rewrite this to ErrBadConn for sql.Driver to retry, since
// in connection initialization we don't risk retrying non-idempotent actions.
if err == ErrInvalidConn {
return nil, "", driver.ErrBadConn
}
return
}
if data[0] == iERR {
return nil, "", mc.handleErrorPacket(data)
}
// protocol version [1 byte]
if data[0] < minProtocolVersion {
return nil, "", fmt.Errorf(
"unsupported protocol version %d. Version %d or higher is required",
data[0],
minProtocolVersion,
)
}
// server version [null terminated string]
// connection id [4 bytes]
pos := 1 + bytes.IndexByte(data[1:], 0x00) + 1 + 4
// first part of the password cipher [8 bytes]
authData := data[pos : pos+8]
// (filler) always 0x00 [1 byte]
pos += 8 + 1
// capability flags (lower 2 bytes) [2 bytes]
mc.flags = clientFlag(binary.LittleEndian.Uint16(data[pos : pos+2]))
if mc.flags&clientProtocol41 == 0 {
return nil, "", ErrOldProtocol
}
if mc.flags&clientSSL == 0 && mc.cfg.tls != nil {
if mc.cfg.TLSConfig == "preferred" {
mc.cfg.tls = nil
} else {
return nil, "", ErrNoTLS
}
}
pos += 2
if len(data) > pos {
// character set [1 byte]
// status flags [2 bytes]
// capability flags (upper 2 bytes) [2 bytes]
// length of auth-plugin-data [1 byte]
// reserved (all [00]) [10 bytes]
pos += 1 + 2 + 2 + 1 + 10
// second part of the password cipher [mininum 13 bytes],
// where len=MAX(13, length of auth-plugin-data - 8)
//
// The web documentation is ambiguous about the length. However,
// according to mysql-5.7/sql/auth/sql_authentication.cc line 538,
// the 13th byte is "\0 byte, terminating the second part of
// a scramble". So the second part of the password cipher is
// a NULL terminated string that's at least 13 bytes with the
// last byte being NULL.
//
// The official Python library uses the fixed length 12
// which seems to work but technically could have a hidden bug.
authData = append(authData, data[pos:pos+12]...)
pos += 13
// EOF if version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2)
// \NUL otherwise
if end := bytes.IndexByte(data[pos:], 0x00); end != -1 {
plugin = string(data[pos : pos+end])
} else {
plugin = string(data[pos:])
}
// make a memory safe copy of the cipher slice
var b [20]byte
copy(b[:], authData)
return b[:], plugin, nil
}
// make a memory safe copy of the cipher slice
var b [8]byte
copy(b[:], authData)
return b[:], plugin, nil
}
// Client Authentication Packet
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse
func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string) error {
// Adjust client flags based on server support
clientFlags := clientProtocol41 |
clientSecureConn |
clientLongPassword |
clientTransactions |
clientLocalFiles |
clientPluginAuth |
clientMultiResults |
mc.flags&clientLongFlag
if mc.cfg.ClientFoundRows {
clientFlags |= clientFoundRows
}
// To enable TLS / SSL
if mc.cfg.tls != nil {
clientFlags |= clientSSL
}
if mc.cfg.MultiStatements {
clientFlags |= clientMultiStatements
}
// encode length of the auth plugin data
var authRespLEIBuf [9]byte
authRespLen := len(authResp)
authRespLEI := appendLengthEncodedInteger(authRespLEIBuf[:0], uint64(authRespLen))
if len(authRespLEI) > 1 {
// if the length can not be written in 1 byte, it must be written as a
// length encoded integer
clientFlags |= clientPluginAuthLenEncClientData
}
pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + len(authRespLEI) + len(authResp) + 21 + 1
// To specify a db name
if n := len(mc.cfg.DBName); n > 0 {
clientFlags |= clientConnectWithDB
pktLen += n + 1
}
// Calculate packet length and get buffer with that size
data, err := mc.buf.takeSmallBuffer(pktLen + 4)
if err != nil {
// cannot take the buffer. Something must be wrong with the connection
errLog.Print(err)
return errBadConnNoWrite
}
// ClientFlags [32 bit]
data[4] = byte(clientFlags)
data[5] = byte(clientFlags >> 8)
data[6] = byte(clientFlags >> 16)
data[7] = byte(clientFlags >> 24)
// MaxPacketSize [32 bit] (none)
data[8] = 0x00
data[9] = 0x00
data[10] = 0x00
data[11] = 0x00
// Charset [1 byte]
var found bool
data[12], found = collations[mc.cfg.Collation]
if !found {
// Note possibility for false negatives:
// could be triggered although the collation is valid if the
// collations map does not contain entries the server supports.
return errors.New("unknown collation")
}
// SSL Connection Request Packet
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest
if mc.cfg.tls != nil {
// Send TLS / SSL request packet
if err := mc.writePacket(data[:(4+4+1+23)+4]); err != nil {
return err
}
// Switch to TLS
tlsConn := tls.Client(mc.netConn, mc.cfg.tls)
if err := tlsConn.Handshake(); err != nil {
return err
}
mc.netConn = tlsConn
mc.buf.nc = tlsConn
}
// Filler [23 bytes] (all 0x00)
pos := 13
for ; pos < 13+23; pos++ {
data[pos] = 0
}
// User [null terminated string]
if len(mc.cfg.User) > 0 {
pos += copy(data[pos:], mc.cfg.User)
}
data[pos] = 0x00
pos++
// Auth Data [length encoded integer]
pos += copy(data[pos:], authRespLEI)
pos += copy(data[pos:], authResp)
// Databasename [null terminated string]
if len(mc.cfg.DBName) > 0 {
pos += copy(data[pos:], mc.cfg.DBName)
data[pos] = 0x00
pos++
}
pos += copy(data[pos:], plugin)
data[pos] = 0x00
pos++
// Send Auth packet
return mc.writePacket(data[:pos])
}
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error {
pktLen := 4 + len(authData)
data, err := mc.buf.takeSmallBuffer(pktLen)
if err != nil {
// cannot take the buffer. Something must be wrong with the connection
errLog.Print(err)
return errBadConnNoWrite
}
// Add the auth data [EOF]
copy(data[4:], authData)
return mc.writePacket(data)
}
/******************************************************************************
* Command Packets *
******************************************************************************/
func (mc *mysqlConn) writeCommandPacket(command byte) error {
// Reset Packet Sequence
mc.sequence = 0
data, err := mc.buf.takeSmallBuffer(4 + 1)
if err != nil {
// cannot take the buffer. Something must be wrong with the connection
errLog.Print(err)
return errBadConnNoWrite
}
// Add command byte
data[4] = command
// Send CMD packet
return mc.writePacket(data)
}
func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
// Reset Packet Sequence
mc.sequence = 0
pktLen := 1 + len(arg)
data, err := mc.buf.takeBuffer(pktLen + 4)
if err != nil {
// cannot take the buffer. Something must be wrong with the connection
errLog.Print(err)
return errBadConnNoWrite
}
// Add command byte
data[4] = command
// Add arg
copy(data[5:], arg)
// Send CMD packet
return mc.writePacket(data)
}
func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
// Reset Packet Sequence
mc.sequence = 0
data, err := mc.buf.takeSmallBuffer(4 + 1 + 4)
if err != nil {
// cannot take the buffer. Something must be wrong with the connection
errLog.Print(err)
return errBadConnNoWrite
}
// Add command byte
data[4] = command
// Add arg [32 bit]
data[5] = byte(arg)
data[6] = byte(arg >> 8)
data[7] = byte(arg >> 16)
data[8] = byte(arg >> 24)
// Send CMD packet
return mc.writePacket(data)
}
/******************************************************************************
* Result Packets *
******************************************************************************/
func (mc *mysqlConn) readAuthResult() ([]byte, string, error) {
data, err := mc.readPacket()
if err != nil {
return nil, "", err
}
// packet indicator
switch data[0] {
case iOK:
return nil, "", mc.handleOkPacket(data)
case iAuthMoreData:
return data[1:], "", err
case iEOF:
if len(data) == 1 {
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
return nil, "mysql_old_password", nil
}
pluginEndIndex := bytes.IndexByte(data, 0x00)
if pluginEndIndex < 0 {
return nil, "", ErrMalformPkt
}
plugin := string(data[1:pluginEndIndex])
authData := data[pluginEndIndex+1:]
return authData, plugin, nil
default: // Error otherwise
return nil, "", mc.handleErrorPacket(data)
}
}
// Returns error if Packet is not an 'Result OK'-Packet
func (mc *mysqlConn) readResultOK() error {
data, err := mc.readPacket()
if err != nil {
return err
}
if data[0] == iOK {
return mc.handleOkPacket(data)
}
return mc.handleErrorPacket(data)
}
// Result Set Header Packet
// http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-ProtocolText::Resultset
func (mc *mysqlConn) readResultSetHeaderPacket() (int, error) {
data, err := mc.readPacket()
if err == nil {
switch data[0] {
case iOK:
return 0, mc.handleOkPacket(data)
case iERR:
return 0, mc.handleErrorPacket(data)
case iLocalInFile:
return 0, mc.handleInFileRequest(string(data[1:]))
}
// column count
num, _, n := readLengthEncodedInteger(data)
if n-len(data) == 0 {
return int(num), nil
}
return 0, ErrMalformPkt
}
return 0, err
}
// Error Packet
// http://dev.mysql.com/doc/internals/en/generic-response-packets.html#packet-ERR_Packet
func (mc *mysqlConn) handleErrorPacket(data []byte) error {
if data[0] != iERR {
return ErrMalformPkt
}
// 0xff [1 byte]
// Error Number [16 bit uint]
errno := binary.LittleEndian.Uint16(data[1:3])
// 1792: ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION
// 1290: ER_OPTION_PREVENTS_STATEMENT (returned by Aurora during failover)
if (errno == 1792 || errno == 1290) && mc.cfg.RejectReadOnly {
// Oops; we are connected to a read-only connection, and won't be able
// to issue any write statements. Since RejectReadOnly is configured,
// we throw away this connection hoping this one would have write
// permission. This is specifically for a possible race condition
// during failover (e.g. on AWS Aurora). See README.md for more.
//
// We explicitly close the connection before returning
// driver.ErrBadConn to ensure that `database/sql` purges this
// connection and initiates a new one for next statement next time.
mc.Close()
return driver.ErrBadConn
}
pos := 3
// SQL State [optional: # + 5bytes string]
if data[3] == 0x23 {
//sqlstate := string(data[4 : 4+5])
pos = 9
}
// Error Message [string]
return &MySQLError{
Number: errno,
Message: string(data[pos:]),
}
}
func readStatus(b []byte) statusFlag {
return statusFlag(b[0]) | statusFlag(b[1])<<8
}
// Ok Packet
// http://dev.mysql.com/doc/internals/en/generic-response-packets.html#packet-OK_Packet
func (mc *mysqlConn) handleOkPacket(data []byte) error {
var n, m int
// 0x00 [1 byte]
// Affected rows [Length Coded Binary]
mc.affectedRows, _, n = readLengthEncodedInteger(data[1:])
// Insert id [Length Coded Binary]
mc.insertId, _, m = readLengthEncodedInteger(data[1+n:])
// server_status [2 bytes]
mc.status = readStatus(data[1+n+m : 1+n+m+2])
if mc.status&statusMoreResultsExists != 0 {
return nil
}
// warning count [2 bytes]
return nil
}
// Read Packets as Field Packets until EOF-Packet or an Error appears
// http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnDefinition41
func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
columns := make([]mysqlField, count)
for i := 0; ; i++ {
data, err := mc.readPacket()
if err != nil {
return nil, err
}
// EOF Packet
if data[0] == iEOF && (len(data) == 5 || len(data) == 1) {
if i == count {
return columns, nil
}
return nil, fmt.Errorf("column count mismatch n:%d len:%d", count, len(columns))
}
// Catalog
pos, err := skipLengthEncodedString(data)
if err != nil {
return nil, err
}
// Database [len coded string]
n, err := skipLengthEncodedString(data[pos:])
if err != nil {
return nil, err
}
pos += n
// Table [len coded string]
if mc.cfg.ColumnsWithAlias {
tableName, _, n, err := readLengthEncodedString(data[pos:])
if err != nil {
return nil, err
}
pos += n
columns[i].tableName = string(tableName)
} else {
n, err = skipLengthEncodedString(data[pos:])
if err != nil {
return nil, err
}
pos += n
}
// Original table [len coded string]
n, err = skipLengthEncodedString(data[pos:])
if err != nil {
return nil, err
}
pos += n
// Name [len coded string]
name, _, n, err := readLengthEncodedString(data[pos:])
if err != nil {
return nil, err
}
columns[i].name = string(name)
pos += n
// Original name [len coded string]
n, err = skipLengthEncodedString(data[pos:])
if err != nil {
return nil, err
}
pos += n
// Filler [uint8]
pos++
// Charset [charset, collation uint8]
columns[i].charSet = data[pos]
pos += 2
// Length [uint32]
columns[i].length = binary.LittleEndian.Uint32(data[pos : pos+4])
pos += 4
// Field type [uint8]
columns[i].fieldType = fieldType(data[pos])
pos++
// Flags [uint16]
columns[i].flags = fieldFlag(binary.LittleEndian.Uint16(data[pos : pos+2]))
pos += 2
// Decimals [uint8]
columns[i].decimals = data[pos]
//pos++
// Default value [len coded binary]
//if pos < len(data) {
// defaultVal, _, err = bytesToLengthCodedBinary(data[pos:])
//}
}
}
// Read Packets as Field Packets until EOF-Packet or an Error appears
// http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-ProtocolText::ResultsetRow
func (rows *textRows) readRow(dest []driver.Value) error {
mc := rows.mc
if rows.rs.done {
return io.EOF
}
data, err := mc.readPacket()
if err != nil {
return err
}
// EOF Packet
if data[0] == iEOF && len(data) == 5 {
// server_status [2 bytes]
rows.mc.status = readStatus(data[3:])
rows.rs.done = true
if !rows.HasNextResultSet() {
rows.mc = nil
}
return io.EOF
}
if data[0] == iERR {
rows.mc = nil
return mc.handleErrorPacket(data)
}
// RowSet Packet
var n int
var isNull bool
pos := 0
for i := range dest {
// Read bytes and convert to string
dest[i], isNull, n, err = readLengthEncodedString(data[pos:])
pos += n
if err == nil {
if !isNull {
if !mc.parseTime {
continue
} else {
switch rows.rs.columns[i].fieldType {
case fieldTypeTimestamp, fieldTypeDateTime,
fieldTypeDate, fieldTypeNewDate:
dest[i], err = parseDateTime(
string(dest[i].([]byte)),
mc.cfg.Loc,
)
if err == nil {
continue
}
default:
continue
}
}
} else {
dest[i] = nil
continue
}
}
return err // err != nil
}
return nil
}
// Reads Packets until EOF-Packet or an Error appears. Returns count of Packets read
func (mc *mysqlConn) readUntilEOF() error {
for {
data, err := mc.readPacket()
if err != nil {
return err
}
switch data[0] {
case iERR:
return mc.handleErrorPacket(data)
case iEOF:
if len(data) == 5 {
mc.status = readStatus(data[3:])
}
return nil
}
}
}
/******************************************************************************
* Prepared Statements *
******************************************************************************/
// Prepare Result Packets
// http://dev.mysql.com/doc/internals/en/com-stmt-prepare-response.html
func (stmt *mysqlStmt) readPrepareResultPacket() (uint16, error) {
data, err := stmt.mc.readPacket()
if err == nil {
// packet indicator [1 byte]
if data[0] != iOK {
return 0, stmt.mc.handleErrorPacket(data)
}
// statement id [4 bytes]
stmt.id = binary.LittleEndian.Uint32(data[1:5])
// Column count [16 bit uint]
columnCount := binary.LittleEndian.Uint16(data[5:7])
// Param count [16 bit uint]
stmt.paramCount = int(binary.LittleEndian.Uint16(data[7:9]))
// Reserved [8 bit]
// Warning count [16 bit uint]
return columnCount, nil
}
return 0, err
}
// http://dev.mysql.com/doc/internals/en/com-stmt-send-long-data.html
func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error {
maxLen := stmt.mc.maxAllowedPacket - 1
pktLen := maxLen
// After the header (bytes 0-3) follows before the data:
// 1 byte command
// 4 bytes stmtID
// 2 bytes paramID
const dataOffset = 1 + 4 + 2
// Cannot use the write buffer since
// a) the buffer is too small
// b) it is in use
data := make([]byte, 4+1+4+2+len(arg))
copy(data[4+dataOffset:], arg)
for argLen := len(arg); argLen > 0; argLen -= pktLen - dataOffset {
if dataOffset+argLen < maxLen {
pktLen = dataOffset + argLen
}
stmt.mc.sequence = 0
// Add command byte [1 byte]
data[4] = comStmtSendLongData
// Add stmtID [32 bit]
data[5] = byte(stmt.id)
data[6] = byte(stmt.id >> 8)
data[7] = byte(stmt.id >> 16)
data[8] = byte(stmt.id >> 24)
// Add paramID [16 bit]
data[9] = byte(paramID)
data[10] = byte(paramID >> 8)
// Send CMD packet
err := stmt.mc.writePacket(data[:4+pktLen])
if err == nil {
data = data[pktLen-dataOffset:]
continue
}
return err
}
// Reset Packet Sequence
stmt.mc.sequence = 0
return nil
}
// Execute Prepared Statement
// http://dev.mysql.com/doc/internals/en/com-stmt-execute.html
func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
if len(args) != stmt.paramCount {
return fmt.Errorf(
"argument count mismatch (got: %d; has: %d)",
len(args),
stmt.paramCount,
)
}
const minPktLen = 4 + 1 + 4 + 1 + 4
mc := stmt.mc
// Determine threshold dynamically to avoid packet size shortage.
longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1)
if longDataSize < 64 {
longDataSize = 64
}
// Reset packet-sequence
mc.sequence = 0
var data []byte
var err error
if len(args) == 0 {
data, err = mc.buf.takeBuffer(minPktLen)
} else {
data, err = mc.buf.takeCompleteBuffer()
// In this case the len(data) == cap(data) which is used to optimise the flow below.
}
if err != nil {
// cannot take the buffer. Something must be wrong with the connection
errLog.Print(err)
return errBadConnNoWrite
}
// command [1 byte]
data[4] = comStmtExecute
// statement_id [4 bytes]
data[5] = byte(stmt.id)
data[6] = byte(stmt.id >> 8)
data[7] = byte(stmt.id >> 16)
data[8] = byte(stmt.id >> 24)
// flags (0: CURSOR_TYPE_NO_CURSOR) [1 byte]
data[9] = 0x00
// iteration_count (uint32(1)) [4 bytes]
data[10] = 0x01
data[11] = 0x00
data[12] = 0x00
data[13] = 0x00
if len(args) > 0 {
pos := minPktLen
var nullMask []byte
if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= cap(data) {
// buffer has to be extended but we don't know by how much so
// we depend on append after all data with known sizes fit.
// We stop at that because we deal with a lot of columns here
// which makes the required allocation size hard to guess.
tmp := make([]byte, pos+maskLen+typesLen)
copy(tmp[:pos], data[:pos])
data = tmp
nullMask = data[pos : pos+maskLen]
// No need to clean nullMask as make ensures that.
pos += maskLen
} else {
nullMask = data[pos : pos+maskLen]
for i := range nullMask {
nullMask[i] = 0
}
pos += maskLen
}
// newParameterBoundFlag 1 [1 byte]
data[pos] = 0x01
pos++
// type of each parameter [len(args)*2 bytes]
paramTypes := data[pos:]
pos += len(args) * 2
// value of each parameter [n bytes]
paramValues := data[pos:pos]
valuesCap := cap(paramValues)
for i, arg := range args {
// build NULL-bitmap
if arg == nil {
nullMask[i/8] |= 1 << (uint(i) & 7)
paramTypes[i+i] = byte(fieldTypeNULL)
paramTypes[i+i+1] = 0x00
continue
}
// cache types and values
switch v := arg.(type) {
case int64:
paramTypes[i+i] = byte(fieldTypeLongLong)
paramTypes[i+i+1] = 0x00
if cap(paramValues)-len(paramValues)-8 >= 0 {
paramValues = paramValues[:len(paramValues)+8]
binary.LittleEndian.PutUint64(
paramValues[len(paramValues)-8:],
uint64(v),
)
} else {
paramValues = append(paramValues,
uint64ToBytes(uint64(v))...,
)
}
case float64:
paramTypes[i+i] = byte(fieldTypeDouble)
paramTypes[i+i+1] = 0x00
if cap(paramValues)-len(paramValues)-8 >= 0 {
paramValues = paramValues[:len(paramValues)+8]
binary.LittleEndian.PutUint64(
paramValues[len(paramValues)-8:],
math.Float64bits(v),
)
} else {
paramValues = append(paramValues,
uint64ToBytes(math.Float64bits(v))...,
)
}
case bool:
paramTypes[i+i] = byte(fieldTypeTiny)
paramTypes[i+i+1] = 0x00
if v {
paramValues = append(paramValues, 0x01)
} else {
paramValues = append(paramValues, 0x00)
}
case []byte:
// Common case (non-nil value) first
if v != nil {
paramTypes[i+i] = byte(fieldTypeString)
paramTypes[i+i+1] = 0x00
if len(v) < longDataSize {
paramValues = appendLengthEncodedInteger(paramValues,
uint64(len(v)),
)
paramValues = append(paramValues, v...)
} else {
if err := stmt.writeCommandLongData(i, v); err != nil {
return err
}
}
continue
}
// Handle []byte(nil) as a NULL value
nullMask[i/8] |= 1 << (uint(i) & 7)
paramTypes[i+i] = byte(fieldTypeNULL)
paramTypes[i+i+1] = 0x00
case string:
paramTypes[i+i] = byte(fieldTypeString)
paramTypes[i+i+1] = 0x00
if len(v) < longDataSize {
paramValues = appendLengthEncodedInteger(paramValues,
uint64(len(v)),
)
paramValues = append(paramValues, v...)
} else {
if err := stmt.writeCommandLongData(i, []byte(v)); err != nil {
return err
}
}
case time.Time:
paramTypes[i+i] = byte(fieldTypeString)
paramTypes[i+i+1] = 0x00
var a [64]byte
var b = a[:0]
if v.IsZero() {
b = append(b, "0000-00-00"...)
} else {
b = v.In(mc.cfg.Loc).AppendFormat(b, timeFormat)
}
paramValues = appendLengthEncodedInteger(paramValues,
uint64(len(b)),
)
paramValues = append(paramValues, b...)
default:
return fmt.Errorf("cannot convert type: %T", arg)
}
}
// Check if param values exceeded the available buffer
// In that case we must build the data packet with the new values buffer
if valuesCap != cap(paramValues) {
data = append(data[:pos], paramValues...)
if err = mc.buf.store(data); err != nil {
errLog.Print(err)
return errBadConnNoWrite
}
}
pos += len(paramValues)
data = data[:pos]
}
return mc.writePacket(data)
}
func (mc *mysqlConn) discardResults() error {
for mc.status&statusMoreResultsExists != 0 {
resLen, err := mc.readResultSetHeaderPacket()
if err != nil {
return err
}
if resLen > 0 {
// columns
if err := mc.readUntilEOF(); err != nil {
return err
}
// rows
if err := mc.readUntilEOF(); err != nil {
return err
}
}
}
return nil
}
// http://dev.mysql.com/doc/internals/en/binary-protocol-resultset-row.html
func (rows *binaryRows) readRow(dest []driver.Value) error {
data, err := rows.mc.readPacket()
if err != nil {
return err
}
// packet indicator [1 byte]
if data[0] != iOK {
// EOF Packet
if data[0] == iEOF && len(data) == 5 {
rows.mc.status = readStatus(data[3:])
rows.rs.done = true
if !rows.HasNextResultSet() {
rows.mc = nil
}
return io.EOF
}
mc := rows.mc
rows.mc = nil
// Error otherwise
return mc.handleErrorPacket(data)
}
// NULL-bitmap, [(column-count + 7 + 2) / 8 bytes]
pos := 1 + (len(dest)+7+2)>>3
nullMask := data[1:pos]
for i := range dest {
// Field is NULL
// (byte >> bit-pos) % 2 == 1
if ((nullMask[(i+2)>>3] >> uint((i+2)&7)) & 1) == 1 {
dest[i] = nil
continue
}
// Convert to byte-coded string
switch rows.rs.columns[i].fieldType {
case fieldTypeNULL:
dest[i] = nil
continue
// Numeric Types
case fieldTypeTiny:
if rows.rs.columns[i].flags&flagUnsigned != 0 {
dest[i] = int64(data[pos])
} else {
dest[i] = int64(int8(data[pos]))
}
pos++
continue
case fieldTypeShort, fieldTypeYear:
if rows.rs.columns[i].flags&flagUnsigned != 0 {
dest[i] = int64(binary.LittleEndian.Uint16(data[pos : pos+2]))
} else {
dest[i] = int64(int16(binary.LittleEndian.Uint16(data[pos : pos+2])))
}
pos += 2
continue
case fieldTypeInt24, fieldTypeLong:
if rows.rs.columns[i].flags&flagUnsigned != 0 {
dest[i] = int64(binary.LittleEndian.Uint32(data[pos : pos+4]))
} else {
dest[i] = int64(int32(binary.LittleEndian.Uint32(data[pos : pos+4])))
}
pos += 4
continue
case fieldTypeLongLong:
if rows.rs.columns[i].flags&flagUnsigned != 0 {
val := binary.LittleEndian.Uint64(data[pos : pos+8])
if val > math.MaxInt64 {
dest[i] = uint64ToString(val)
} else {
dest[i] = int64(val)
}
} else {
dest[i] = int64(binary.LittleEndian.Uint64(data[pos : pos+8]))
}
pos += 8
continue
case fieldTypeFloat:
dest[i] = math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4]))
pos += 4
continue
case fieldTypeDouble:
dest[i] = math.Float64frombits(binary.LittleEndian.Uint64(data[pos : pos+8]))
pos += 8
continue
// Length coded Binary Strings
case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar,
fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB,
fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB,
fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON:
var isNull bool
var n int
dest[i], isNull, n, err = readLengthEncodedString(data[pos:])
pos += n
if err == nil {
if !isNull {
continue
} else {
dest[i] = nil
continue
}
}
return err
case
fieldTypeDate, fieldTypeNewDate, // Date YYYY-MM-DD
fieldTypeTime, // Time [-][H]HH:MM:SS[.fractal]
fieldTypeTimestamp, fieldTypeDateTime: // Timestamp YYYY-MM-DD HH:MM:SS[.fractal]
num, isNull, n := readLengthEncodedInteger(data[pos:])
pos += n
switch {
case isNull:
dest[i] = nil
continue
case rows.rs.columns[i].fieldType == fieldTypeTime:
// database/sql does not support an equivalent to TIME, return a string
var dstlen uint8
switch decimals := rows.rs.columns[i].decimals; decimals {
case 0x00, 0x1f:
dstlen = 8
case 1, 2, 3, 4, 5, 6:
dstlen = 8 + 1 + decimals
default:
return fmt.Errorf(
"protocol error, illegal decimals value %d",
rows.rs.columns[i].decimals,
)
}
dest[i], err = formatBinaryTime(data[pos:pos+int(num)], dstlen)
case rows.mc.parseTime:
dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc)
default:
var dstlen uint8
if rows.rs.columns[i].fieldType == fieldTypeDate {
dstlen = 10
} else {
switch decimals := rows.rs.columns[i].decimals; decimals {
case 0x00, 0x1f:
dstlen = 19
case 1, 2, 3, 4, 5, 6:
dstlen = 19 + 1 + decimals
default:
return fmt.Errorf(
"protocol error, illegal decimals value %d",
rows.rs.columns[i].decimals,
)
}
}
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen)
}
if err == nil {
pos += int(num)
continue
} else {
return err
}
// Please report if this happens!
default:
return fmt.Errorf("unknown field type %d", rows.rs.columns[i].fieldType)
}
}
return nil
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
type mysqlResult struct {
affectedRows int64
insertId int64
}
func (res *mysqlResult) LastInsertId() (int64, error) {
return res.insertId, nil
}
func (res *mysqlResult) RowsAffected() (int64, error) {
return res.affectedRows, nil
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"database/sql/driver"
"io"
"math"
"reflect"
)
type resultSet struct {
columns []mysqlField
columnNames []string
done bool
}
type mysqlRows struct {
mc *mysqlConn
rs resultSet
finish func()
}
type binaryRows struct {
mysqlRows
}
type textRows struct {
mysqlRows
}
func (rows *mysqlRows) Columns() []string {
if rows.rs.columnNames != nil {
return rows.rs.columnNames
}
columns := make([]string, len(rows.rs.columns))
if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias {
for i := range columns {
if tableName := rows.rs.columns[i].tableName; len(tableName) > 0 {
columns[i] = tableName + "." + rows.rs.columns[i].name
} else {
columns[i] = rows.rs.columns[i].name
}
}
} else {
for i := range columns {
columns[i] = rows.rs.columns[i].name
}
}
rows.rs.columnNames = columns
return columns
}
func (rows *mysqlRows) ColumnTypeDatabaseTypeName(i int) string {
return rows.rs.columns[i].typeDatabaseName()
}
// func (rows *mysqlRows) ColumnTypeLength(i int) (length int64, ok bool) {
// return int64(rows.rs.columns[i].length), true
// }
func (rows *mysqlRows) ColumnTypeNullable(i int) (nullable, ok bool) {
return rows.rs.columns[i].flags&flagNotNULL == 0, true
}
func (rows *mysqlRows) ColumnTypePrecisionScale(i int) (int64, int64, bool) {
column := rows.rs.columns[i]
decimals := int64(column.decimals)
switch column.fieldType {
case fieldTypeDecimal, fieldTypeNewDecimal:
if decimals > 0 {
return int64(column.length) - 2, decimals, true
}
return int64(column.length) - 1, decimals, true
case fieldTypeTimestamp, fieldTypeDateTime, fieldTypeTime:
return decimals, decimals, true
case fieldTypeFloat, fieldTypeDouble:
if decimals == 0x1f {
return math.MaxInt64, math.MaxInt64, true
}
return math.MaxInt64, decimals, true
}
return 0, 0, false
}
func (rows *mysqlRows) ColumnTypeScanType(i int) reflect.Type {
return rows.rs.columns[i].scanType()
}
func (rows *mysqlRows) Close() (err error) {
if f := rows.finish; f != nil {
f()
rows.finish = nil
}
mc := rows.mc
if mc == nil {
return nil
}
if err := mc.error(); err != nil {
return err
}
// Remove unread packets from stream
if !rows.rs.done {
err = mc.readUntilEOF()
}
if err == nil {
if err = mc.discardResults(); err != nil {
return err
}
}
rows.mc = nil
return err
}
func (rows *mysqlRows) HasNextResultSet() (b bool) {
if rows.mc == nil {
return false
}
return rows.mc.status&statusMoreResultsExists != 0
}
func (rows *mysqlRows) nextResultSet() (int, error) {
if rows.mc == nil {
return 0, io.EOF
}
if err := rows.mc.error(); err != nil {
return 0, err
}
// Remove unread packets from stream
if !rows.rs.done {
if err := rows.mc.readUntilEOF(); err != nil {
return 0, err
}
rows.rs.done = true
}
if !rows.HasNextResultSet() {
rows.mc = nil
return 0, io.EOF
}
rows.rs = resultSet{}
return rows.mc.readResultSetHeaderPacket()
}
func (rows *mysqlRows) nextNotEmptyResultSet() (int, error) {
for {
resLen, err := rows.nextResultSet()
if err != nil {
return 0, err
}
if resLen > 0 {
return resLen, nil
}
rows.rs.done = true
}
}
func (rows *binaryRows) NextResultSet() error {
resLen, err := rows.nextNotEmptyResultSet()
if err != nil {
return err
}
rows.rs.columns, err = rows.mc.readColumns(resLen)
return err
}
func (rows *binaryRows) Next(dest []driver.Value) error {
if mc := rows.mc; mc != nil {
if err := mc.error(); err != nil {
return err
}
// Fetch next row from stream
return rows.readRow(dest)
}
return io.EOF
}
func (rows *textRows) NextResultSet() (err error) {
resLen, err := rows.nextNotEmptyResultSet()
if err != nil {
return err
}
rows.rs.columns, err = rows.mc.readColumns(resLen)
return err
}
func (rows *textRows) Next(dest []driver.Value) error {
if mc := rows.mc; mc != nil {
if err := mc.error(); err != nil {
return err
}
// Fetch next row from stream
return rows.readRow(dest)
}
return io.EOF
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"database/sql/driver"
"fmt"
"io"
"reflect"
"strconv"
)
type mysqlStmt struct {
mc *mysqlConn
id uint32
paramCount int
}
func (stmt *mysqlStmt) Close() error {
if stmt.mc == nil || stmt.mc.closed.IsSet() {
// driver.Stmt.Close can be called more than once, thus this function
// has to be idempotent.
// See also Issue #450 and golang/go#16019.
//errLog.Print(ErrInvalidConn)
return driver.ErrBadConn
}
err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id)
stmt.mc = nil
return err
}
func (stmt *mysqlStmt) NumInput() int {
return stmt.paramCount
}
func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
return converter{}
}
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
if stmt.mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
// Send command
err := stmt.writeExecutePacket(args)
if err != nil {
return nil, stmt.mc.markBadConn(err)
}
mc := stmt.mc
mc.affectedRows = 0
mc.insertId = 0
// Read Result
resLen, err := mc.readResultSetHeaderPacket()
if err != nil {
return nil, err
}
if resLen > 0 {
// Columns
if err = mc.readUntilEOF(); err != nil {
return nil, err
}
// Rows
if err := mc.readUntilEOF(); err != nil {
return nil, err
}
}
if err := mc.discardResults(); err != nil {
return nil, err
}
return &mysqlResult{
affectedRows: int64(mc.affectedRows),
insertId: int64(mc.insertId),
}, nil
}
func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
return stmt.query(args)
}
func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
if stmt.mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
// Send command
err := stmt.writeExecutePacket(args)
if err != nil {
return nil, stmt.mc.markBadConn(err)
}
mc := stmt.mc
// Read Result
resLen, err := mc.readResultSetHeaderPacket()
if err != nil {
return nil, err
}
rows := new(binaryRows)
if resLen > 0 {
rows.mc = mc
rows.rs.columns, err = mc.readColumns(resLen)
} else {
rows.rs.done = true
switch err := rows.NextResultSet(); err {
case nil, io.EOF:
return rows, nil
default:
return nil, err
}
}
return rows, err
}
type converter struct{}
// ConvertValue mirrors the reference/default converter in database/sql/driver
// with _one_ exception. We support uint64 with their high bit and the default
// implementation does not. This function should be kept in sync with
// database/sql/driver defaultConverter.ConvertValue() except for that
// deliberate difference.
func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
if driver.IsValue(v) {
return v, nil
}
if vr, ok := v.(driver.Valuer); ok {
sv, err := callValuerValue(vr)
if err != nil {
return nil, err
}
if !driver.IsValue(sv) {
return nil, fmt.Errorf("non-Value type %T returned from Value", sv)
}
return sv, nil
}
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Ptr:
// indirect pointers
if rv.IsNil() {
return nil, nil
} else {
return c.ConvertValue(rv.Elem().Interface())
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return rv.Int(), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
return int64(rv.Uint()), nil
case reflect.Uint64:
u64 := rv.Uint()
if u64 >= 1<<63 {
return strconv.FormatUint(u64, 10), nil
}
return int64(u64), nil
case reflect.Float32, reflect.Float64:
return rv.Float(), nil
case reflect.Bool:
return rv.Bool(), nil
case reflect.Slice:
ek := rv.Type().Elem().Kind()
if ek == reflect.Uint8 {
return rv.Bytes(), nil
}
return nil, fmt.Errorf("unsupported type %T, a slice of %s", v, ek)
case reflect.String:
return rv.String(), nil
}
return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
}
var valuerReflectType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
// callValuerValue returns vr.Value(), with one exception:
// If vr.Value is an auto-generated method on a pointer type and the
// pointer is nil, it would panic at runtime in the panicwrap
// method. Treat it like nil instead.
//
// This is so people can implement driver.Value on value types and
// still use nil pointers to those types to mean nil/NULL, just like
// string/*string.
//
// This is an exact copy of the same-named unexported function from the
// database/sql package.
func callValuerValue(vr driver.Valuer) (v driver.Value, err error) {
if rv := reflect.ValueOf(vr); rv.Kind() == reflect.Ptr &&
rv.IsNil() &&
rv.Type().Elem().Implements(valuerReflectType) {
return nil, nil
}
return vr.Value()
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
type mysqlTx struct {
mc *mysqlConn
}
func (tx *mysqlTx) Commit() (err error) {
if tx.mc == nil || tx.mc.closed.IsSet() {
return ErrInvalidConn
}
err = tx.mc.exec("COMMIT")
tx.mc = nil
return
}
func (tx *mysqlTx) Rollback() (err error) {
if tx.mc == nil || tx.mc.closed.IsSet() {
return ErrInvalidConn
}
err = tx.mc.exec("ROLLBACK")
tx.mc = nil
return
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"crypto/tls"
"database/sql"
"database/sql/driver"
"encoding/binary"
"errors"
"fmt"
"io"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
)
// Registry for custom tls.Configs
var (
tlsConfigLock sync.RWMutex
tlsConfigRegistry map[string]*tls.Config
)
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
// Use the key as a value in the DSN where tls=value.
//
// Note: The provided tls.Config is exclusively owned by the driver after
// registering it.
//
// rootCertPool := x509.NewCertPool()
// pem, err := ioutil.ReadFile("/path/ca-cert.pem")
// if err != nil {
// log.Fatal(err)
// }
// if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
// log.Fatal("Failed to append PEM.")
// }
// clientCert := make([]tls.Certificate, 0, 1)
// certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem")
// if err != nil {
// log.Fatal(err)
// }
// clientCert = append(clientCert, certs)
// mysql.RegisterTLSConfig("custom", &tls.Config{
// RootCAs: rootCertPool,
// Certificates: clientCert,
// })
// db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
//
func RegisterTLSConfig(key string, config *tls.Config) error {
if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" {
return fmt.Errorf("key '%s' is reserved", key)
}
tlsConfigLock.Lock()
if tlsConfigRegistry == nil {
tlsConfigRegistry = make(map[string]*tls.Config)
}
tlsConfigRegistry[key] = config
tlsConfigLock.Unlock()
return nil
}
// DeregisterTLSConfig removes the tls.Config associated with key.
func DeregisterTLSConfig(key string) {
tlsConfigLock.Lock()
if tlsConfigRegistry != nil {
delete(tlsConfigRegistry, key)
}
tlsConfigLock.Unlock()
}
func getTLSConfigClone(key string) (config *tls.Config) {
tlsConfigLock.RLock()
if v, ok := tlsConfigRegistry[key]; ok {
config = v.Clone()
}
tlsConfigLock.RUnlock()
return
}
// Returns the bool value of the input.
// The 2nd return value indicates if the input was a valid bool value
func readBool(input string) (value bool, valid bool) {
switch input {
case "1", "true", "TRUE", "True":
return true, true
case "0", "false", "FALSE", "False":
return false, true
}
// Not a valid bool value
return
}
/******************************************************************************
* Time related utils *
******************************************************************************/
// NullTime represents a time.Time that may be NULL.
// NullTime implements the Scanner interface so
// it can be used as a scan destination:
//
// var nt NullTime
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
// ...
// if nt.Valid {
// // use nt.Time
// } else {
// // NULL value
// }
//
// This NullTime implementation is not driver-specific
type NullTime struct {
Time time.Time
Valid bool // Valid is true if Time is not NULL
}
// Scan implements the Scanner interface.
// The value type must be time.Time or string / []byte (formatted time-string),
// otherwise Scan fails.
func (nt *NullTime) Scan(value interface{}) (err error) {
if value == nil {
nt.Time, nt.Valid = time.Time{}, false
return
}
switch v := value.(type) {
case time.Time:
nt.Time, nt.Valid = v, true
return
case []byte:
nt.Time, err = parseDateTime(string(v), time.UTC)
nt.Valid = (err == nil)
return
case string:
nt.Time, err = parseDateTime(v, time.UTC)
nt.Valid = (err == nil)
return
}
nt.Valid = false
return fmt.Errorf("Can't convert %T to time.Time", value)
}
// Value implements the driver Valuer interface.
func (nt NullTime) Value() (driver.Value, error) {
if !nt.Valid {
return nil, nil
}
return nt.Time, nil
}
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
base := "0000-00-00 00:00:00.0000000"
switch len(str) {
case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
if str == base[:len(str)] {
return
}
t, err = time.Parse(timeFormat[:len(str)], str)
default:
err = fmt.Errorf("invalid time string: %s", str)
return
}
// Adjust location
if err == nil && loc != time.UTC {
y, mo, d := t.Date()
h, mi, s := t.Clock()
t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil
}
return
}
func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) {
switch num {
case 0:
return time.Time{}, nil
case 4:
return time.Date(
int(binary.LittleEndian.Uint16(data[:2])), // year
time.Month(data[2]), // month
int(data[3]), // day
0, 0, 0, 0,
loc,
), nil
case 7:
return time.Date(
int(binary.LittleEndian.Uint16(data[:2])), // year
time.Month(data[2]), // month
int(data[3]), // day
int(data[4]), // hour
int(data[5]), // minutes
int(data[6]), // seconds
0,
loc,
), nil
case 11:
return time.Date(
int(binary.LittleEndian.Uint16(data[:2])), // year
time.Month(data[2]), // month
int(data[3]), // day
int(data[4]), // hour
int(data[5]), // minutes
int(data[6]), // seconds
int(binary.LittleEndian.Uint32(data[7:11]))*1000, // nanoseconds
loc,
), nil
}
return nil, fmt.Errorf("invalid DATETIME packet length %d", num)
}
// zeroDateTime is used in formatBinaryDateTime to avoid an allocation
// if the DATE or DATETIME has the zero value.
// It must never be changed.
// The current behavior depends on database/sql copying the result.
var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
func appendMicrosecs(dst, src []byte, decimals int) []byte {
if decimals <= 0 {
return dst
}
if len(src) == 0 {
return append(dst, ".000000"[:decimals+1]...)
}
microsecs := binary.LittleEndian.Uint32(src[:4])
p1 := byte(microsecs / 10000)
microsecs -= 10000 * uint32(p1)
p2 := byte(microsecs / 100)
microsecs -= 100 * uint32(p2)
p3 := byte(microsecs)
switch decimals {
default:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2], digits01[p2],
digits10[p3], digits01[p3],
)
case 1:
return append(dst, '.',
digits10[p1],
)
case 2:
return append(dst, '.',
digits10[p1], digits01[p1],
)
case 3:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2],
)
case 4:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2], digits01[p2],
)
case 5:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2], digits01[p2],
digits10[p3],
)
}
}
func formatBinaryDateTime(src []byte, length uint8) (driver.Value, error) {
// length expects the deterministic length of the zero value,
// negative time and 100+ hours are automatically added if needed
if len(src) == 0 {
return zeroDateTime[:length], nil
}
var dst []byte // return value
var p1, p2, p3 byte // current digit pair
switch length {
case 10, 19, 21, 22, 23, 24, 25, 26:
default:
t := "DATE"
if length > 10 {
t += "TIME"
}
return nil, fmt.Errorf("illegal %s length %d", t, length)
}
switch len(src) {
case 4, 7, 11:
default:
t := "DATE"
if length > 10 {
t += "TIME"
}
return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
}
dst = make([]byte, 0, length)
// start with the date
year := binary.LittleEndian.Uint16(src[:2])
pt := year / 100
p1 = byte(year - 100*uint16(pt))
p2, p3 = src[2], src[3]
dst = append(dst,
digits10[pt], digits01[pt],
digits10[p1], digits01[p1], '-',
digits10[p2], digits01[p2], '-',
digits10[p3], digits01[p3],
)
if length == 10 {
return dst, nil
}
if len(src) == 4 {
return append(dst, zeroDateTime[10:length]...), nil
}
dst = append(dst, ' ')
p1 = src[4] // hour
src = src[5:]
// p1 is 2-digit hour, src is after hour
p2, p3 = src[0], src[1]
dst = append(dst,
digits10[p1], digits01[p1], ':',
digits10[p2], digits01[p2], ':',
digits10[p3], digits01[p3],
)
return appendMicrosecs(dst, src[2:], int(length)-20), nil
}
func formatBinaryTime(src []byte, length uint8) (driver.Value, error) {
// length expects the deterministic length of the zero value,
// negative time and 100+ hours are automatically added if needed
if len(src) == 0 {
return zeroDateTime[11 : 11+length], nil
}
var dst []byte // return value
switch length {
case
8, // time (can be up to 10 when negative and 100+ hours)
10, 11, 12, 13, 14, 15: // time with fractional seconds
default:
return nil, fmt.Errorf("illegal TIME length %d", length)
}
switch len(src) {
case 8, 12:
default:
return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
}
// +2 to enable negative time and 100+ hours
dst = make([]byte, 0, length+2)
if src[0] == 1 {
dst = append(dst, '-')
}
days := binary.LittleEndian.Uint32(src[1:5])
hours := int64(days)*24 + int64(src[5])
if hours >= 100 {
dst = strconv.AppendInt(dst, hours, 10)
} else {
dst = append(dst, digits10[hours], digits01[hours])
}
min, sec := src[6], src[7]
dst = append(dst, ':',
digits10[min], digits01[min], ':',
digits10[sec], digits01[sec],
)
return appendMicrosecs(dst, src[8:], int(length)-9), nil
}
/******************************************************************************
* Convert from and to bytes *
******************************************************************************/
func uint64ToBytes(n uint64) []byte {
return []byte{
byte(n),
byte(n >> 8),
byte(n >> 16),
byte(n >> 24),
byte(n >> 32),
byte(n >> 40),
byte(n >> 48),
byte(n >> 56),
}
}
func uint64ToString(n uint64) []byte {
var a [20]byte
i := 20
// U+0030 = 0
// ...
// U+0039 = 9
var q uint64
for n >= 10 {
i--
q = n / 10
a[i] = uint8(n-q*10) + 0x30
n = q
}
i--
a[i] = uint8(n) + 0x30
return a[i:]
}
// treats string value as unsigned integer representation
func stringToInt(b []byte) int {
val := 0
for i := range b {
val *= 10
val += int(b[i] - 0x30)
}
return val
}
// returns the string read as a bytes slice, wheter the value is NULL,
// the number of bytes read and an error, in case the string is longer than
// the input slice
func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
// Get length
num, isNull, n := readLengthEncodedInteger(b)
if num < 1 {
return b[n:n], isNull, n, nil
}
n += int(num)
// Check data length
if len(b) >= n {
return b[n-int(num) : n : n], false, n, nil
}
return nil, false, n, io.EOF
}
// returns the number of bytes skipped and an error, in case the string is
// longer than the input slice
func skipLengthEncodedString(b []byte) (int, error) {
// Get length
num, _, n := readLengthEncodedInteger(b)
if num < 1 {
return n, nil
}
n += int(num)
// Check data length
if len(b) >= n {
return n, nil
}
return n, io.EOF
}
// returns the number read, whether the value is NULL and the number of bytes read
func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
// See issue #349
if len(b) == 0 {
return 0, true, 1
}
switch b[0] {
// 251: NULL
case 0xfb:
return 0, true, 1
// 252: value of following 2
case 0xfc:
return uint64(b[1]) | uint64(b[2])<<8, false, 3
// 253: value of following 3
case 0xfd:
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4
// 254: value of following 8
case 0xfe:
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 |
uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 |
uint64(b[7])<<48 | uint64(b[8])<<56,
false, 9
}
// 0-250: value of first byte
return uint64(b[0]), false, 1
}
// encodes a uint64 value and appends it to the given bytes slice
func appendLengthEncodedInteger(b []byte, n uint64) []byte {
switch {
case n <= 250:
return append(b, byte(n))
case n <= 0xffff:
return append(b, 0xfc, byte(n), byte(n>>8))
case n <= 0xffffff:
return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16))
}
return append(b, 0xfe, byte(n), byte(n>>8), byte(n>>16), byte(n>>24),
byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56))
}
// reserveBuffer checks cap(buf) and expand buffer to len(buf) + appendSize.
// If cap(buf) is not enough, reallocate new buffer.
func reserveBuffer(buf []byte, appendSize int) []byte {
newSize := len(buf) + appendSize
if cap(buf) < newSize {
// Grow buffer exponentially
newBuf := make([]byte, len(buf)*2+appendSize)
copy(newBuf, buf)
buf = newBuf
}
return buf[:newSize]
}
// escapeBytesBackslash escapes []byte with backslashes (\)
// This escapes the contents of a string (provided as []byte) by adding backslashes before special
// characters, and turning others into specific escape sequences, such as
// turning newlines into \n and null bytes into \0.
// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L823-L932
func escapeBytesBackslash(buf, v []byte) []byte {
pos := len(buf)
buf = reserveBuffer(buf, len(v)*2)
for _, c := range v {
switch c {
case '\x00':
buf[pos] = '\\'
buf[pos+1] = '0'
pos += 2
case '\n':
buf[pos] = '\\'
buf[pos+1] = 'n'
pos += 2
case '\r':
buf[pos] = '\\'
buf[pos+1] = 'r'
pos += 2
case '\x1a':
buf[pos] = '\\'
buf[pos+1] = 'Z'
pos += 2
case '\'':
buf[pos] = '\\'
buf[pos+1] = '\''
pos += 2
case '"':
buf[pos] = '\\'
buf[pos+1] = '"'
pos += 2
case '\\':
buf[pos] = '\\'
buf[pos+1] = '\\'
pos += 2
default:
buf[pos] = c
pos++
}
}
return buf[:pos]
}
// escapeStringBackslash is similar to escapeBytesBackslash but for string.
func escapeStringBackslash(buf []byte, v string) []byte {
pos := len(buf)
buf = reserveBuffer(buf, len(v)*2)
for i := 0; i < len(v); i++ {
c := v[i]
switch c {
case '\x00':
buf[pos] = '\\'
buf[pos+1] = '0'
pos += 2
case '\n':
buf[pos] = '\\'
buf[pos+1] = 'n'
pos += 2
case '\r':
buf[pos] = '\\'
buf[pos+1] = 'r'
pos += 2
case '\x1a':
buf[pos] = '\\'
buf[pos+1] = 'Z'
pos += 2
case '\'':
buf[pos] = '\\'
buf[pos+1] = '\''
pos += 2
case '"':
buf[pos] = '\\'
buf[pos+1] = '"'
pos += 2
case '\\':
buf[pos] = '\\'
buf[pos+1] = '\\'
pos += 2
default:
buf[pos] = c
pos++
}
}
return buf[:pos]
}
// escapeBytesQuotes escapes apostrophes in []byte by doubling them up.
// This escapes the contents of a string by doubling up any apostrophes that
// it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
// effect on the server.
// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L963-L1038
func escapeBytesQuotes(buf, v []byte) []byte {
pos := len(buf)
buf = reserveBuffer(buf, len(v)*2)
for _, c := range v {
if c == '\'' {
buf[pos] = '\''
buf[pos+1] = '\''
pos += 2
} else {
buf[pos] = c
pos++
}
}
return buf[:pos]
}
// escapeStringQuotes is similar to escapeBytesQuotes but for string.
func escapeStringQuotes(buf []byte, v string) []byte {
pos := len(buf)
buf = reserveBuffer(buf, len(v)*2)
for i := 0; i < len(v); i++ {
c := v[i]
if c == '\'' {
buf[pos] = '\''
buf[pos+1] = '\''
pos += 2
} else {
buf[pos] = c
pos++
}
}
return buf[:pos]
}
/******************************************************************************
* Sync utils *
******************************************************************************/
// noCopy may be embedded into structs which must not be copied
// after the first use.
//
// See https://github.com/golang/go/issues/8005#issuecomment-190753527
// for details.
type noCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {}
// atomicBool is a wrapper around uint32 for usage as a boolean value with
// atomic access.
type atomicBool struct {
_noCopy noCopy
value uint32
}
// IsSet returns wether the current boolean value is true
func (ab *atomicBool) IsSet() bool {
return atomic.LoadUint32(&ab.value) > 0
}
// Set sets the value of the bool regardless of the previous value
func (ab *atomicBool) Set(value bool) {
if value {
atomic.StoreUint32(&ab.value, 1)
} else {
atomic.StoreUint32(&ab.value, 0)
}
}
// TrySet sets the value of the bool and returns wether the value changed
func (ab *atomicBool) TrySet(value bool) bool {
if value {
return atomic.SwapUint32(&ab.value, 1) == 0
}
return atomic.SwapUint32(&ab.value, 0) > 0
}
// atomicError is a wrapper for atomically accessed error values
type atomicError struct {
_noCopy noCopy
value atomic.Value
}
// Set sets the error value regardless of the previous value.
// The value must not be nil
func (ae *atomicError) Set(value error) {
ae.value.Store(value)
}
// Value returns the current error value
func (ae *atomicError) Value() error {
if v := ae.value.Load(); v != nil {
// this will panic if the value doesn't implement the error interface
return v.(error)
}
return nil
}
func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
dargs := make([]driver.Value, len(named))
for n, param := range named {
if len(param.Name) > 0 {
// TODO: support the use of Named Parameters #561
return nil, errors.New("mysql: driver does not support the use of Named Parameters")
}
dargs[n] = param.Value
}
return dargs, nil
}
func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
switch sql.IsolationLevel(level) {
case sql.LevelRepeatableRead:
return "REPEATABLE READ", nil
case sql.LevelReadCommitted:
return "READ COMMITTED", nil
case sql.LevelReadUncommitted:
return "READ UNCOMMITTED", nil
case sql.LevelSerializable:
return "SERIALIZABLE", nil
default:
return "", fmt.Errorf("mysql: unsupported isolation level: %v", level)
}
}
Copyright (c) 2010, Michal Derkacz
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package mysql
import (
"fmt"
)
// Error is a mymysql error.
// If a function/method returns error you may check the returned error type via
// a type assertion. If the type assertion succeeds you can retrieve the MySQL
// error code as below.
//
// Example:
// if val, ok := err.(*mysql.Error); ok {
// fmt.Println(val.Code)
// }
type Error struct {
Code uint16
Msg []byte
}
func (err Error) Error() string {
return fmt.Sprintf("Received #%d error from MySQL server: \"%s\"",
err.Code, err.Msg)
}
// MySQL error codes.
const (
ER_HASHCHK = 1000
ER_NISAMCHK = 1001
ER_NO = 1002
ER_YES = 1003
ER_CANT_CREATE_FILE = 1004
ER_CANT_CREATE_TABLE = 1005
ER_CANT_CREATE_DB = 1006
ER_DB_CREATE_EXISTS = 1007
ER_DB_DROP_EXISTS = 1008
ER_DB_DROP_DELETE = 1009
ER_DB_DROP_RMDIR = 1010
ER_CANT_DELETE_FILE = 1011
ER_CANT_FIND_SYSTEM_REC = 1012
ER_CANT_GET_STAT = 1013
ER_CANT_GET_WD = 1014
ER_CANT_LOCK = 1015
ER_CANT_OPEN_FILE = 1016
ER_FILE_NOT_FOUND = 1017
ER_CANT_READ_DIR = 1018
ER_CANT_SET_WD = 1019
ER_CHECKREAD = 1020
ER_DISK_FULL = 1021
ER_DUP_KEY = 1022
ER_ERROR_ON_CLOSE = 1023
ER_ERROR_ON_READ = 1024
ER_ERROR_ON_RENAME = 1025
ER_ERROR_ON_WRITE = 1026
ER_FILE_USED = 1027
ER_FILSORT_ABORT = 1028
ER_FORM_NOT_FOUND = 1029
ER_GET_ERRNO = 1030
ER_ILLEGAL_HA = 1031
ER_KEY_NOT_FOUND = 1032
ER_NOT_FORM_FILE = 1033
ER_NOT_KEYFILE = 1034
ER_OLD_KEYFILE = 1035
ER_OPEN_AS_READONLY = 1036
ER_OUTOFMEMORY = 1037
ER_OUT_OF_SORTMEMORY = 1038
ER_UNEXPECTED_EOF = 1039
ER_CON_COUNT_ERROR = 1040
ER_OUT_OF_RESOURCES = 1041
ER_BAD_HOST_ERROR = 1042
ER_HANDSHAKE_ERROR = 1043
ER_DBACCESS_DENIED_ERROR = 1044
ER_ACCESS_DENIED_ERROR = 1045
ER_NO_DB_ERROR = 1046
ER_UNKNOWN_COM_ERROR = 1047
ER_BAD_NULL_ERROR = 1048
ER_BAD_DB_ERROR = 1049
ER_TABLE_EXISTS_ERROR = 1050
ER_BAD_TABLE_ERROR = 1051
ER_NON_UNIQ_ERROR = 1052
ER_SERVER_SHUTDOWN = 1053
ER_BAD_FIELD_ERROR = 1054
ER_WRONG_FIELD_WITH_GROUP = 1055
ER_WRONG_GROUP_FIELD = 1056
ER_WRONG_SUM_SELECT = 1057
ER_WRONG_VALUE_COUNT = 1058
ER_TOO_LONG_IDENT = 1059
ER_DUP_FIELDNAME = 1060
ER_DUP_KEYNAME = 1061
ER_DUP_ENTRY = 1062
ER_WRONG_FIELD_SPEC = 1063
ER_PARSE_ERROR = 1064
ER_EMPTY_QUERY = 1065
ER_NONUNIQ_TABLE = 1066
ER_INVALID_DEFAULT = 1067
ER_MULTIPLE_PRI_KEY = 1068
ER_TOO_MANY_KEYS = 1069
ER_TOO_MANY_KEY_PARTS = 1070
ER_TOO_LONG_KEY = 1071
ER_KEY_COLUMN_DOES_NOT_EXITS = 1072
ER_BLOB_USED_AS_KEY = 1073
ER_TOO_BIG_FIELDLENGTH = 1074
ER_WRONG_AUTO_KEY = 1075
ER_READY = 1076
ER_NORMAL_SHUTDOWN = 1077
ER_GOT_SIGNAL = 1078
ER_SHUTDOWN_COMPLETE = 1079
ER_FORCING_CLOSE = 1080
ER_IPSOCK_ERROR = 1081
ER_NO_SUCH_INDEX = 1082
ER_WRONG_FIELD_TERMINATORS = 1083
ER_BLOBS_AND_NO_TERMINATED = 1084
ER_TEXTFILE_NOT_READABLE = 1085
ER_FILE_EXISTS_ERROR = 1086
ER_LOAD_INFO = 1087
ER_ALTER_INFO = 1088
ER_WRONG_SUB_KEY = 1089
ER_CANT_REMOVE_ALL_FIELDS = 1090
ER_CANT_DROP_FIELD_OR_KEY = 1091
ER_INSERT_INFO = 1092
ER_UPDATE_TABLE_USED = 1093
ER_NO_SUCH_THREAD = 1094
ER_KILL_DENIED_ERROR = 1095
ER_NO_TABLES_USED = 1096
ER_TOO_BIG_SET = 1097
ER_NO_UNIQUE_LOGFILE = 1098
ER_TABLE_NOT_LOCKED_FOR_WRITE = 1099
ER_TABLE_NOT_LOCKED = 1100
ER_BLOB_CANT_HAVE_DEFAULT = 1101
ER_WRONG_DB_NAME = 1102
ER_WRONG_TABLE_NAME = 1103
ER_TOO_BIG_SELECT = 1104
ER_UNKNOWN_ERROR = 1105
ER_UNKNOWN_PROCEDURE = 1106
ER_WRONG_PARAMCOUNT_TO_PROCEDURE = 1107
ER_WRONG_PARAMETERS_TO_PROCEDURE = 1108
ER_UNKNOWN_TABLE = 1109
ER_FIELD_SPECIFIED_TWICE = 1110
ER_INVALID_GROUP_FUNC_USE = 1111
ER_UNSUPPORTED_EXTENSION = 1112
ER_TABLE_MUST_HAVE_COLUMNS = 1113
ER_RECORD_FILE_FULL = 1114
ER_UNKNOWN_CHARACTER_SET = 1115
ER_TOO_MANY_TABLES = 1116
ER_TOO_MANY_FIELDS = 1117
ER_TOO_BIG_ROWSIZE = 1118
ER_STACK_OVERRUN = 1119
ER_WRONG_OUTER_JOIN = 1120
ER_NULL_COLUMN_IN_INDEX = 1121
ER_CANT_FIND_UDF = 1122
ER_CANT_INITIALIZE_UDF = 1123
ER_UDF_NO_PATHS = 1124
ER_UDF_EXISTS = 1125
ER_CANT_OPEN_LIBRARY = 1126
ER_CANT_FIND_DL_ENTRY = 1127
ER_FUNCTION_NOT_DEFINED = 1128
ER_HOST_IS_BLOCKED = 1129
ER_HOST_NOT_PRIVILEGED = 1130
ER_PASSWORD_ANONYMOUS_USER = 1131
ER_PASSWORD_NOT_ALLOWED = 1132
ER_PASSWORD_NO_MATCH = 1133
ER_UPDATE_INFO = 1134
ER_CANT_CREATE_THREAD = 1135
ER_WRONG_VALUE_COUNT_ON_ROW = 1136
ER_CANT_REOPEN_TABLE = 1137
ER_INVALID_USE_OF_NULL = 1138
ER_REGEXP_ERROR = 1139
ER_MIX_OF_GROUP_FUNC_AND_FIELDS = 1140
ER_NONEXISTING_GRANT = 1141
ER_TABLEACCESS_DENIED_ERROR = 1142
ER_COLUMNACCESS_DENIED_ERROR = 1143
ER_ILLEGAL_GRANT_FOR_TABLE = 1144
ER_GRANT_WRONG_HOST_OR_USER = 1145
ER_NO_SUCH_TABLE = 1146
ER_NONEXISTING_TABLE_GRANT = 1147
ER_NOT_ALLOWED_COMMAND = 1148
ER_SYNTAX_ERROR = 1149
ER_DELAYED_CANT_CHANGE_LOCK = 1150
ER_TOO_MANY_DELAYED_THREADS = 1151
ER_ABORTING_CONNECTION = 1152
ER_NET_PACKET_TOO_LARGE = 1153
ER_NET_READ_ERROR_FROM_PIPE = 1154
ER_NET_FCNTL_ERROR = 1155
ER_NET_PACKETS_OUT_OF_ORDER = 1156
ER_NET_UNCOMPRESS_ERROR = 1157
ER_NET_READ_ERROR = 1158
ER_NET_READ_INTERRUPTED = 1159
ER_NET_ERROR_ON_WRITE = 1160
ER_NET_WRITE_INTERRUPTED = 1161
ER_TOO_LONG_STRING = 1162
ER_TABLE_CANT_HANDLE_BLOB = 1163
ER_TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164
ER_DELAYED_INSERT_TABLE_LOCKED = 1165
ER_WRONG_COLUMN_NAME = 1166
ER_WRONG_KEY_COLUMN = 1167
ER_WRONG_MRG_TABLE = 1168
ER_DUP_UNIQUE = 1169
ER_BLOB_KEY_WITHOUT_LENGTH = 1170
ER_PRIMARY_CANT_HAVE_NULL = 1171
ER_TOO_MANY_ROWS = 1172
ER_REQUIRES_PRIMARY_KEY = 1173
ER_NO_RAID_COMPILED = 1174
ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175
ER_KEY_DOES_NOT_EXITS = 1176
ER_CHECK_NO_SUCH_TABLE = 1177
ER_CHECK_NOT_IMPLEMENTED = 1178
ER_CANT_DO_THIS_DURING_AN_TRANSACTION = 1179
ER_ERROR_DURING_COMMIT = 1180
ER_ERROR_DURING_ROLLBACK = 1181
ER_ERROR_DURING_FLUSH_LOGS = 1182
ER_ERROR_DURING_CHECKPOINT = 1183
ER_NEW_ABORTING_CONNECTION = 1184
ER_DUMP_NOT_IMPLEMENTED = 1185
ER_FLUSH_MASTER_BINLOG_CLOSED = 1186
ER_INDEX_REBUILD = 1187
ER_MASTER = 1188
ER_MASTER_NET_READ = 1189
ER_MASTER_NET_WRITE = 1190
ER_FT_MATCHING_KEY_NOT_FOUND = 1191
ER_LOCK_OR_ACTIVE_TRANSACTION = 1192
ER_UNKNOWN_SYSTEM_VARIABLE = 1193
ER_CRASHED_ON_USAGE = 1194
ER_CRASHED_ON_REPAIR = 1195
ER_WARNING_NOT_COMPLETE_ROLLBACK = 1196
ER_TRANS_CACHE_FULL = 1197
ER_SLAVE_MUST_STOP = 1198
ER_SLAVE_NOT_RUNNING = 1199
ER_BAD_SLAVE = 1200
ER_MASTER_INFO = 1201
ER_SLAVE_THREAD = 1202
ER_TOO_MANY_USER_CONNECTIONS = 1203
ER_SET_CONSTANTS_ONLY = 1204
ER_LOCK_WAIT_TIMEOUT = 1205
ER_LOCK_TABLE_FULL = 1206
ER_READ_ONLY_TRANSACTION = 1207
ER_DROP_DB_WITH_READ_LOCK = 1208
ER_CREATE_DB_WITH_READ_LOCK = 1209
ER_WRONG_ARGUMENTS = 1210
ER_NO_PERMISSION_TO_CREATE_USER = 1211
ER_UNION_TABLES_IN_DIFFERENT_DIR = 1212
ER_LOCK_DEADLOCK = 1213
ER_TABLE_CANT_HANDLE_FT = 1214
ER_CANNOT_ADD_FOREIGN = 1215
ER_NO_REFERENCED_ROW = 1216
ER_ROW_IS_REFERENCED = 1217
ER_CONNECT_TO_MASTER = 1218
ER_QUERY_ON_MASTER = 1219
ER_ERROR_WHEN_EXECUTING_COMMAND = 1220
ER_WRONG_USAGE = 1221
ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222
ER_CANT_UPDATE_WITH_READLOCK = 1223
ER_MIXING_NOT_ALLOWED = 1224
ER_DUP_ARGUMENT = 1225
ER_USER_LIMIT_REACHED = 1226
ER_SPECIFIC_ACCESS_DENIED_ERROR = 1227
ER_LOCAL_VARIABLE = 1228
ER_GLOBAL_VARIABLE = 1229
ER_NO_DEFAULT = 1230
ER_WRONG_VALUE_FOR_VAR = 1231
ER_WRONG_TYPE_FOR_VAR = 1232
ER_VAR_CANT_BE_READ = 1233
ER_CANT_USE_OPTION_HERE = 1234
ER_NOT_SUPPORTED_YET = 1235
ER_MASTER_FATAL_ERROR_READING_BINLOG = 1236
ER_SLAVE_IGNORED_TABLE = 1237
ER_INCORRECT_GLOBAL_LOCAL_VAR = 1238
ER_WRONG_FK_DEF = 1239
ER_KEY_REF_DO_NOT_MATCH_TABLE_REF = 1240
ER_OPERAND_COLUMNS = 1241
ER_SUBQUERY_NO_1_ROW = 1242
ER_UNKNOWN_STMT_HANDLER = 1243
ER_CORRUPT_HELP_DB = 1244
ER_CYCLIC_REFERENCE = 1245
ER_AUTO_CONVERT = 1246
ER_ILLEGAL_REFERENCE = 1247
ER_DERIVED_MUST_HAVE_ALIAS = 1248
ER_SELECT_REDUCED = 1249
ER_TABLENAME_NOT_ALLOWED_HERE = 1250
ER_NOT_SUPPORTED_AUTH_MODE = 1251
ER_SPATIAL_CANT_HAVE_NULL = 1252
ER_COLLATION_CHARSET_MISMATCH = 1253
ER_SLAVE_WAS_RUNNING = 1254
ER_SLAVE_WAS_NOT_RUNNING = 1255
ER_TOO_BIG_FOR_UNCOMPRESS = 1256
ER_ZLIB_Z_MEM_ERROR = 1257
ER_ZLIB_Z_BUF_ERROR = 1258
ER_ZLIB_Z_DATA_ERROR = 1259
ER_CUT_VALUE_GROUP_CONCAT = 1260
ER_WARN_TOO_FEW_RECORDS = 1261
ER_WARN_TOO_MANY_RECORDS = 1262
ER_WARN_NULL_TO_NOTNULL = 1263
ER_WARN_DATA_OUT_OF_RANGE = 1264
WARN_DATA_TRUNCATED = 1265
ER_WARN_USING_OTHER_HANDLER = 1266
ER_CANT_AGGREGATE_2COLLATIONS = 1267
ER_DROP_USER = 1268
ER_REVOKE_GRANTS = 1269
ER_CANT_AGGREGATE_3COLLATIONS = 1270
ER_CANT_AGGREGATE_NCOLLATIONS = 1271
ER_VARIABLE_IS_NOT_STRUCT = 1272
ER_UNKNOWN_COLLATION = 1273
ER_SLAVE_IGNORED_SSL_PARAMS = 1274
ER_SERVER_IS_IN_SECURE_AUTH_MODE = 1275
ER_WARN_FIELD_RESOLVED = 1276
ER_BAD_SLAVE_UNTIL_COND = 1277
ER_MISSING_SKIP_SLAVE = 1278
ER_UNTIL_COND_IGNORED = 1279
ER_WRONG_NAME_FOR_INDEX = 1280
ER_WRONG_NAME_FOR_CATALOG = 1281
ER_WARN_QC_RESIZE = 1282
ER_BAD_FT_COLUMN = 1283
ER_UNKNOWN_KEY_CACHE = 1284
ER_WARN_HOSTNAME_WONT_WORK = 1285
ER_UNKNOWN_STORAGE_ENGINE = 1286
ER_WARN_DEPRECATED_SYNTAX = 1287
ER_NON_UPDATABLE_TABLE = 1288
ER_FEATURE_DISABLED = 1289
ER_OPTION_PREVENTS_STATEMENT = 1290
ER_DUPLICATED_VALUE_IN_TYPE = 1291
ER_TRUNCATED_WRONG_VALUE = 1292
ER_TOO_MUCH_AUTO_TIMESTAMP_COLS = 1293
ER_INVALID_ON_UPDATE = 1294
ER_UNSUPPORTED_PS = 1295
ER_GET_ERRMSG = 1296
ER_GET_TEMPORARY_ERRMSG = 1297
ER_UNKNOWN_TIME_ZONE = 1298
ER_WARN_INVALID_TIMESTAMP = 1299
ER_INVALID_CHARACTER_STRING = 1300
ER_WARN_ALLOWED_PACKET_OVERFLOWED = 1301
ER_CONFLICTING_DECLARATIONS = 1302
ER_SP_NO_RECURSIVE_CREATE = 1303
ER_SP_ALREADY_EXISTS = 1304
ER_SP_DOES_NOT_EXIST = 1305
ER_SP_DROP_FAILED = 1306
ER_SP_STORE_FAILED = 1307
ER_SP_LILABEL_MISMATCH = 1308
ER_SP_LABEL_REDEFINE = 1309
ER_SP_LABEL_MISMATCH = 1310
ER_SP_UNINIT_VAR = 1311
ER_SP_BADSELECT = 1312
ER_SP_BADRETURN = 1313
ER_SP_BADSTATEMENT = 1314
ER_UPDATE_LOG_DEPRECATED_IGNORED = 1315
ER_UPDATE_LOG_DEPRECATED_TRANSLATED = 1316
ER_QUERY_INTERRUPTED = 1317
ER_SP_WRONG_NO_OF_ARGS = 1318
ER_SP_COND_MISMATCH = 1319
ER_SP_NORETURN = 1320
ER_SP_NORETURNEND = 1321
ER_SP_BAD_CURSOR_QUERY = 1322
ER_SP_BAD_CURSOR_SELECT = 1323
ER_SP_CURSOR_MISMATCH = 1324
ER_SP_CURSOR_ALREADY_OPEN = 1325
ER_SP_CURSOR_NOT_OPEN = 1326
ER_SP_UNDECLARED_VAR = 1327
ER_SP_WRONG_NO_OF_FETCH_ARGS = 1328
ER_SP_FETCH_NO_DATA = 1329
ER_SP_DUP_PARAM = 1330
ER_SP_DUP_VAR = 1331
ER_SP_DUP_COND = 1332
ER_SP_DUP_CURS = 1333
ER_SP_CANT_ALTER = 1334
ER_SP_SUBSELECT_NYI = 1335
ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG = 1336
ER_SP_VARCOND_AFTER_CURSHNDLR = 1337
ER_SP_CURSOR_AFTER_HANDLER = 1338
ER_SP_CASE_NOT_FOUND = 1339
ER_FPARSER_TOO_BIG_FILE = 1340
ER_FPARSER_BAD_HEADER = 1341
ER_FPARSER_EOF_IN_COMMENT = 1342
ER_FPARSER_ERROR_IN_PARAMETER = 1343
ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344
ER_VIEW_NO_EXPLAIN = 1345
ER_FRM_UNKNOWN_TYPE = 1346
ER_WRONG_OBJECT = 1347
ER_NONUPDATEABLE_COLUMN = 1348
ER_VIEW_SELECT_DERIVED = 1349
ER_VIEW_SELECT_CLAUSE = 1350
ER_VIEW_SELECT_VARIABLE = 1351
ER_VIEW_SELECT_TMPTABLE = 1352
ER_VIEW_WRONG_LIST = 1353
ER_WARN_VIEW_MERGE = 1354
ER_WARN_VIEW_WITHOUT_KEY = 1355
ER_VIEW_INVALID = 1356
ER_SP_NO_DROP_SP = 1357
ER_SP_GOTO_IN_HNDLR = 1358
ER_TRG_ALREADY_EXISTS = 1359
ER_TRG_DOES_NOT_EXIST = 1360
ER_TRG_ON_VIEW_OR_TEMP_TABLE = 1361
ER_TRG_CANT_CHANGE_ROW = 1362
ER_TRG_NO_SUCH_ROW_IN_TRG = 1363
ER_NO_DEFAULT_FOR_FIELD = 1364
ER_DIVISION_BY_ZERO = 1365
ER_TRUNCATED_WRONG_VALUE_FOR_FIELD = 1366
ER_ILLEGAL_VALUE_FOR_TYPE = 1367
ER_VIEW_NONUPD_CHECK = 1368
ER_VIEW_CHECK_FAILED = 1369
ER_PROCACCESS_DENIED_ERROR = 1370
ER_RELAY_LOG_FAIL = 1371
ER_PASSWD_LENGTH = 1372
ER_UNKNOWN_TARGET_BINLOG = 1373
ER_IO_ERR_LOG_INDEX_READ = 1374
ER_BINLOG_PURGE_PROHIBITED = 1375
ER_FSEEK_FAIL = 1376
ER_BINLOG_PURGE_FATAL_ERR = 1377
ER_LOG_IN_USE = 1378
ER_LOG_PURGE_UNKNOWN_ERR = 1379
ER_RELAY_LOG_INIT = 1380
ER_NO_BINARY_LOGGING = 1381
ER_RESERVED_SYNTAX = 1382
ER_WSAS_FAILED = 1383
ER_DIFF_GROUPS_PROC = 1384
ER_NO_GROUP_FOR_PROC = 1385
ER_ORDER_WITH_PROC = 1386
ER_LOGGING_PROHIBIT_CHANGING_OF = 1387
ER_NO_FILE_MAPPING = 1388
ER_WRONG_MAGIC = 1389
ER_PS_MANY_PARAM = 1390
ER_KEY_PART_0 = 1391
ER_VIEW_CHECKSUM = 1392
ER_VIEW_MULTIUPDATE = 1393
ER_VIEW_NO_INSERT_FIELD_LIST = 1394
ER_VIEW_DELETE_MERGE_VIEW = 1395
ER_CANNOT_USER = 1396
ER_XAER_NOTA = 1397
ER_XAER_INVAL = 1398
ER_XAER_RMFAIL = 1399
ER_XAER_OUTSIDE = 1400
ER_XAER_RMERR = 1401
ER_XA_RBROLLBACK = 1402
ER_NONEXISTING_PROC_GRANT = 1403
ER_PROC_AUTO_GRANT_FAIL = 1404
ER_PROC_AUTO_REVOKE_FAIL = 1405
ER_DATA_TOO_LONG = 1406
ER_SP_BAD_SQLSTATE = 1407
ER_STARTUP = 1408
ER_LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR = 1409
ER_CANT_CREATE_USER_WITH_GRANT = 1410
ER_WRONG_VALUE_FOR_TYPE = 1411
ER_TABLE_DEF_CHANGED = 1412
ER_SP_DUP_HANDLER = 1413
ER_SP_NOT_VAR_ARG = 1414
ER_SP_NO_RETSET = 1415
ER_CANT_CREATE_GEOMETRY_OBJECT = 1416
ER_FAILED_ROUTINE_BREAK_BINLOG = 1417
ER_BINLOG_UNSAFE_ROUTINE = 1418
ER_BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419
ER_EXEC_STMT_WITH_OPEN_CURSOR = 1420
ER_STMT_HAS_NO_OPEN_CURSOR = 1421
ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422
ER_NO_DEFAULT_FOR_VIEW_FIELD = 1423
ER_SP_NO_RECURSION = 1424
ER_TOO_BIG_SCALE = 1425
ER_TOO_BIG_PRECISION = 1426
ER_M_BIGGER_THAN_D = 1427
ER_WRONG_LOCK_OF_SYSTEM_TABLE = 1428
ER_CONNECT_TO_FOREIGN_DATA_SOURCE = 1429
ER_QUERY_ON_FOREIGN_DATA_SOURCE = 1430
ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431
ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432
ER_FOREIGN_DATA_STRING_INVALID = 1433
ER_CANT_CREATE_FEDERATED_TABLE = 1434
ER_TRG_IN_WRONG_SCHEMA = 1435
ER_STACK_OVERRUN_NEED_MORE = 1436
ER_TOO_LONG_BODY = 1437
ER_WARN_CANT_DROP_DEFAULT_KEYCACHE = 1438
ER_TOO_BIG_DISPLAYWIDTH = 1439
ER_XAER_DUPID = 1440
ER_DATETIME_FUNCTION_OVERFLOW = 1441
ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG = 1442
ER_VIEW_PREVENT_UPDATE = 1443
ER_PS_NO_RECURSION = 1444
ER_SP_CANT_SET_AUTOCOMMIT = 1445
ER_MALFORMED_DEFINER = 1446
ER_VIEW_FRM_NO_USER = 1447
ER_VIEW_OTHER_USER = 1448
ER_NO_SUCH_USER = 1449
ER_FORBID_SCHEMA_CHANGE = 1450
ER_ROW_IS_REFERENCED_2 = 1451
ER_NO_REFERENCED_ROW_2 = 1452
ER_SP_BAD_VAR_SHADOW = 1453
ER_TRG_NO_DEFINER = 1454
ER_OLD_FILE_FORMAT = 1455
ER_SP_RECURSION_LIMIT = 1456
ER_SP_PROC_TABLE_CORRUPT = 1457
ER_SP_WRONG_NAME = 1458
ER_TABLE_NEEDS_UPGRADE = 1459
ER_SP_NO_AGGREGATE = 1460
ER_MAX_PREPARED_STMT_COUNT_REACHED = 1461
ER_VIEW_RECURSIVE = 1462
ER_NON_GROUPING_FIELD_USED = 1463
ER_TABLE_CANT_HANDLE_SPKEYS = 1464
ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA = 1465
ER_REMOVED_SPACES = 1466
ER_AUTOINC_READ_FAILED = 1467
ER_USERNAME = 1468
ER_HOSTNAME = 1469
ER_WRONG_STRING_LENGTH = 1470
ER_NON_INSERTABLE_TABLE = 1471
)
// ClientError is a type for mymysql client errors.
type ClientError string
func (e ClientError) Error() string {
return string(e)
}
var (
ErrSeq = ClientError("packet sequence error")
ErrPkt = ClientError("malformed packet")
ErrPktLong = ClientError("packet too long")
ErrUnexpNullLCS = ClientError("unexpected NULL LCS")
ErrUnexpNullLCB = ClientError("unexpected NULL LCB")
ErrUnexpNullDate = ClientError("unexpected NULL DATETIME")
ErrUnexpNullTime = ClientError("unexpected NULL TIME")
ErrUnkResultPkt = ClientError("unexpected or unknown result packet")
ErrNotConn = ClientError("not connected")
ErrAlredyConn = ClientError("already connected")
ErrBadResult = ClientError("unexpected result")
ErrUnreadedReply = ClientError("reply is not completely read")
ErrBindCount = ClientError("wrong number of values for bind")
ErrBindUnkType = ClientError("unknown value type for bind")
ErrRowLength = ClientError("wrong length of row slice")
ErrBadCommand = ClientError("comand isn't text SQL nor *Stmt")
ErrWrongDateLen = ClientError("wrong datetime/timestamp length")
ErrWrongTimeLen = ClientError("wrong time length")
ErrUnkMySQLType = ClientError("unknown MySQL type")
ErrWrongParamNum = ClientError("wrong parameter number")
ErrUnkDataType = ClientError("unknown data source type")
ErrSmallPktSize = ClientError("specified packet size is to small")
ErrReadAfterEOR = ClientError("previous ScanRow call returned io.EOF")
ErrOldProtocol = ClientError("server does not support 4.1 protocol")
ErrAuthentication = ClientError("authentication error")
)
package mysql
type Field struct {
Catalog string
Db string
Table string
OrgTable string
Name string
OrgName string
DispLen uint32
// Charset uint16
Flags uint16
Type byte
Scale byte
}
// Package mysql is a MySQL Client API written entirely in Go without any external dependences.
package mysql
import (
"net"
"time"
)
// ConnCommon is a common interface for the connection.
// See mymysql/native for method documentation.
type ConnCommon interface {
Start(sql string, params ...interface{}) (Result, error)
Prepare(sql string) (Stmt, error)
Ping() error
ThreadId() uint32
Escape(txt string) string
Query(sql string, params ...interface{}) ([]Row, Result, error)
QueryFirst(sql string, params ...interface{}) (Row, Result, error)
QueryLast(sql string, params ...interface{}) (Row, Result, error)
}
// Dialer can be used to dial connections to MySQL. If Dialer returns (nil, nil)
// the hook is skipped and normal dialing proceeds.
type Dialer func(proto, laddr, raddr string, timeout time.Duration) (net.Conn, error)
// Conn represents connection to the MySQL server.
// See mymysql/native for method documentation.
type Conn interface {
ConnCommon
Clone() Conn
SetTimeout(time.Duration)
Connect() error
NetConn() net.Conn
SetDialer(Dialer)
Close() error
IsConnected() bool
Reconnect() error
Use(dbname string) error
Register(sql string)
SetMaxPktSize(new_size int) int
NarrowTypeSet(narrow bool)
FullFieldInfo(full bool)
Status() ConnStatus
Credentials() (user, passwd string)
Begin() (Transaction, error)
}
// Transaction represents MySQL transaction.
// See mymysql/native for method documentation.
type Transaction interface {
ConnCommon
Commit() error
Rollback() error
Do(st Stmt) Stmt
IsValid() bool
}
// Stmt represents MySQL prepared statement.
// See mymysql/native for method documentation.
type Stmt interface {
Bind(params ...interface{})
Run(params ...interface{}) (Result, error)
Delete() error
Reset() error
SendLongData(pnum int, data interface{}, pkt_size int) error
Fields() []*Field
NumParam() int
WarnCount() int
Exec(params ...interface{}) ([]Row, Result, error)
ExecFirst(params ...interface{}) (Row, Result, error)
ExecLast(params ...interface{}) (Row, Result, error)
}
// Result represents one MySQL result set.
// See mymysql/native for method documentation.
type Result interface {
StatusOnly() bool
ScanRow(Row) error
GetRow() (Row, error)
MoreResults() bool
NextResult() (Result, error)
Fields() []*Field
Map(string) int
Message() string
AffectedRows() uint64
InsertId() uint64
WarnCount() int
MakeRow() Row
GetRows() ([]Row, error)
End() error
GetFirstRow() (Row, error)
GetLastRow() (Row, error)
}
// New can be used to establish a connection. It is set by imported engine
// (see mymysql/native, mymysql/thrsafe).
var New func(proto, laddr, raddr, user, passwd string, db ...string) Conn
package mysql
import (
"bytes"
"fmt"
"math"
"os"
"reflect"
"strconv"
"time"
)
// Row is a type for result row. It contains values for any column of received row.
//
// If row is a result of ordinary text query, its element can be
// []byte slice, contained result text or nil if NULL is returned.
//
// If it is a result of prepared statement execution, its element field can be:
// intX, uintX, floatX, []byte, Date, Time, time.Time (in Local location) or nil.
type Row []interface{}
// Bin gets the nn-th value and returns it as []byte ([]byte{} if NULL).
func (tr Row) Bin(nn int) (bin []byte) {
switch data := tr[nn].(type) {
case nil:
// bin = []byte{}
case []byte:
bin = data
default:
buf := new(bytes.Buffer)
fmt.Fprint(buf, data)
bin = buf.Bytes()
}
return
}
// Str gets the nn-th value and returns it as string ("" if NULL).
func (tr Row) Str(nn int) (str string) {
switch data := tr[nn].(type) {
case nil:
// str = ""
case []byte:
str = string(data)
case time.Time:
str = TimeString(data)
case time.Duration:
str = DurationString(data)
default:
str = fmt.Sprint(data)
}
return
}
const _MAX_INT = int64(int(^uint(0) >> 1))
const _MIN_INT = -_MAX_INT - 1
// IntErr gets the nn-th value and returns it as int (0 if NULL). Returns error if
// conversion is impossible.
func (tr Row) IntErr(nn int) (val int, err error) {
switch data := tr[nn].(type) {
case nil:
// nop
case int32:
val = int(data)
case int16:
val = int(data)
case uint16:
val = int(data)
case int8:
val = int(data)
case uint8:
val = int(data)
case []byte:
val, err = strconv.Atoi(string(data))
case int64:
if data >= _MIN_INT && data <= _MAX_INT {
val = int(data)
} else {
err = strconv.ErrRange
}
case uint32:
if int64(data) <= _MAX_INT {
val = int(data)
} else {
err = strconv.ErrRange
}
case uint64:
if data <= uint64(_MAX_INT) {
val = int(data)
} else {
err = strconv.ErrRange
}
default:
err = os.ErrInvalid
}
return
}
// Int gets the nn-th value and returns it as int (0 if NULL). Panics if conversion is
// impossible.
func (tr Row) Int(nn int) (val int) {
val, err := tr.IntErr(nn)
if err != nil {
panic(err)
}
return
}
// ForceInt gets the nn-th value and returns it as int. Returns 0 if value is NULL or
// conversion is impossible.
func (tr Row) ForceInt(nn int) (val int) {
val, _ = tr.IntErr(nn)
return
}
const _MAX_UINT = uint64(^uint(0))
// UintErr gets the nn-th value and return it as uint (0 if NULL). Returns error if
// conversion is impossible.
func (tr Row) UintErr(nn int) (val uint, err error) {
switch data := tr[nn].(type) {
case nil:
// nop
case uint32:
val = uint(data)
case uint16:
val = uint(data)
case uint8:
val = uint(data)
case []byte:
var v uint64
v, err = strconv.ParseUint(string(data), 0, 0)
val = uint(v)
case uint64:
if data <= _MAX_UINT {
val = uint(data)
} else {
err = strconv.ErrRange
}
case int8, int16, int32, int64:
v := reflect.ValueOf(data).Int()
if v >= 0 && uint64(v) <= _MAX_UINT {
val = uint(v)
} else {
err = strconv.ErrRange
}
default:
err = os.ErrInvalid
}
return
}
// Uint gets the nn-th value and returns it as uint (0 if NULL). Panics if conversion is
// impossible.
func (tr Row) Uint(nn int) (val uint) {
val, err := tr.UintErr(nn)
if err != nil {
panic(err)
}
return
}
// ForceUint gets the nn-th value and returns it as uint. Returns 0 if value is NULL or
// conversion is impossible.
func (tr Row) ForceUint(nn int) (val uint) {
val, _ = tr.UintErr(nn)
return
}
// DateErr gets the nn-th value and returns it as Date (0000-00-00 if NULL). Returns error
// if conversion is impossible.
func (tr Row) DateErr(nn int) (val Date, err error) {
switch data := tr[nn].(type) {
case nil:
// nop
case Date:
val = data
case []byte:
val, err = ParseDate(string(data))
}
return
}
// Date is like DateErr but panics if conversion is impossible.
func (tr Row) Date(nn int) (val Date) {
val, err := tr.DateErr(nn)
if err != nil {
panic(err)
}
return
}
// ForceDate is like DateErr but returns 0000-00-00 if conversion is impossible.
func (tr Row) ForceDate(nn int) (val Date) {
val, _ = tr.DateErr(nn)
return
}
// TimeErr gets the nn-th value and returns it as time.Time in loc location (zero if NULL)
// Returns error if conversion is impossible. It can convert Date to time.Time.
func (tr Row) TimeErr(nn int, loc *time.Location) (t time.Time, err error) {
switch data := tr[nn].(type) {
case nil:
// nop
case time.Time:
if loc == time.Local {
t = data
} else {
y, mon, d := data.Date()
h, m, s := data.Clock()
t = time.Date(y, mon, d, h, m, s, t.Nanosecond(), loc)
}
case Date:
t = data.Time(loc)
case []byte:
t, err = ParseTime(string(data), loc)
}
return
}
// Time is like TimeErr but panics if conversion is impossible.
func (tr Row) Time(nn int, loc *time.Location) (val time.Time) {
val, err := tr.TimeErr(nn, loc)
if err != nil {
panic(err)
}
return
}
// ForceTime is like TimeErr but returns 0000-00-00 00:00:00 if conversion is
// impossible.
func (tr Row) ForceTime(nn int, loc *time.Location) (val time.Time) {
val, _ = tr.TimeErr(nn, loc)
return
}
// LocaltimeErr gets the nn-th value and returns it as time.Time in Local location
// (zero if NULL). Returns error if conversion is impossible.
// It can convert Date to time.Time.
func (tr Row) LocaltimeErr(nn int) (t time.Time, err error) {
switch data := tr[nn].(type) {
case nil:
// nop
case time.Time:
t = data
case Date:
t = data.Time(time.Local)
case []byte:
t, err = ParseTime(string(data), time.Local)
}
return
}
// Localtime is like LocaltimeErr but panics if conversion is impossible.
func (tr Row) Localtime(nn int) (val time.Time) {
val, err := tr.LocaltimeErr(nn)
if err != nil {
panic(err)
}
return
}
// ForceLocaltime is like LocaltimeErr but returns 0000-00-00 00:00:00 if conversion is
// impossible.
func (tr Row) ForceLocaltime(nn int) (val time.Time) {
val, _ = tr.LocaltimeErr(nn)
return
}
// DurationErr gets the nn-th value and returns it as time.Duration (0 if NULL). Returns error
// if conversion is impossible.
func (tr Row) DurationErr(nn int) (val time.Duration, err error) {
switch data := tr[nn].(type) {
case nil:
case time.Duration:
val = data
case []byte:
val, err = ParseDuration(string(data))
default:
err = fmt.Errorf("Can't convert `%v` to time.Duration", data)
}
return
}
// Duration is like DurationErr but panics if conversion is impossible.
func (tr Row) Duration(nn int) (val time.Duration) {
val, err := tr.DurationErr(nn)
if err != nil {
panic(err)
}
return
}
// ForceDuration is like DurationErr but returns 0 if conversion is impossible.
func (tr Row) ForceDuration(nn int) (val time.Duration) {
val, _ = tr.DurationErr(nn)
return
}
// BoolErr gets the nn-th value and returns it as bool. Returns error
// if conversion is impossible.
func (tr Row) BoolErr(nn int) (val bool, err error) {
switch data := tr[nn].(type) {
case nil:
// nop
case int8:
val = (data != 0)
case int32:
val = (data != 0)
case int16:
val = (data != 0)
case int64:
val = (data != 0)
case uint8:
val = (data != 0)
case uint32:
val = (data != 0)
case uint16:
val = (data != 0)
case uint64:
val = (data != 0)
case []byte:
var v int64
v, err = strconv.ParseInt(string(data), 0, 64)
val = (v != 0)
default:
err = os.ErrInvalid
}
return
}
// Bool is like BoolErr but panics if conversion is impossible.
func (tr Row) Bool(nn int) (val bool) {
val, err := tr.BoolErr(nn)
if err != nil {
panic(err)
}
return
}
// ForceBool is like BoolErr but returns false if conversion is impossible.
func (tr Row) ForceBool(nn int) (val bool) {
val, _ = tr.BoolErr(nn)
return
}
// Int64Err gets the nn-th value and returns it as int64 (0 if NULL). Returns error if
// conversion is impossible.
func (tr Row) Int64Err(nn int) (val int64, err error) {
switch data := tr[nn].(type) {
case nil:
// nop
case int64, int32, int16, int8:
val = reflect.ValueOf(data).Int()
case uint64, uint32, uint16, uint8:
u := reflect.ValueOf(data).Uint()
if u > math.MaxInt64 {
err = strconv.ErrRange
} else {
val = int64(u)
}
case []byte:
val, err = strconv.ParseInt(string(data), 10, 64)
default:
err = os.ErrInvalid
}
return
}
// Int64 gets the nn-th value and returns it as int64 (0 if NULL).
// Panics if conversion is impossible.
func (tr Row) Int64(nn int) (val int64) {
val, err := tr.Int64Err(nn)
if err != nil {
panic(err)
}
return
}
// ForceInt64 gets the nn-th value and returns it as int64. Returns 0 if value is NULL or
// conversion is impossible.
func (tr Row) ForceInt64(nn int) (val int64) {
val, _ = tr.Int64Err(nn)
return
}
// Uint64Err gets the nn-th value and returns it as uint64 (0 if NULL). Returns error if
// conversion is impossible.
func (tr Row) Uint64Err(nn int) (val uint64, err error) {
switch data := tr[nn].(type) {
case nil:
// nop
case uint64, uint32, uint16, uint8:
val = reflect.ValueOf(data).Uint()
case int64, int32, int16, int8:
i := reflect.ValueOf(data).Int()
if i < 0 {
err = strconv.ErrRange
} else {
val = uint64(i)
}
case []byte:
val, err = strconv.ParseUint(string(data), 10, 64)
default:
err = os.ErrInvalid
}
return
}
// Uint64 gets the nn-th value and returns it as uint64 (0 if NULL).
// Panic if conversion is impossible.
func (tr Row) Uint64(nn int) (val uint64) {
val, err := tr.Uint64Err(nn)
if err != nil {
panic(err)
}
return
}
// ForceUint64 gets the nn-th value and returns it as uint64. Returns 0 if value is NULL or
// conversion is impossible.
func (tr Row) ForceUint64(nn int) (val uint64) {
val, _ = tr.Uint64Err(nn)
return
}
// FloatErr gets the nn-th value and returns it as float64 (0 if NULL). Returns error if
// conversion is impossible.
func (tr Row) FloatErr(nn int) (val float64, err error) {
switch data := tr[nn].(type) {
case nil:
// nop
case float64, float32:
val = reflect.ValueOf(data).Float()
case int64, int32, int16, int8:
i := reflect.ValueOf(data).Int()
if i >= 2<<53 || i <= -(2<<53) {
err = strconv.ErrRange
} else {
val = float64(i)
}
case uint64, uint32, uint16, uint8:
u := reflect.ValueOf(data).Uint()
if u >= 2<<53 {
err = strconv.ErrRange
} else {
val = float64(u)
}
case []byte:
val, err = strconv.ParseFloat(string(data), 64)
default:
err = os.ErrInvalid
}
return
}
// Float gets the nn-th value and returns it as float64 (0 if NULL).
// Panics if conversion is impossible.
func (tr Row) Float(nn int) (val float64) {
val, err := tr.FloatErr(nn)
if err != nil {
panic(err)
}
return
}
// ForceFloat gets the nn-th value and returns it as float64. Returns 0 if value is NULL or
// if conversion is impossible.
func (tr Row) ForceFloat(nn int) (val float64) {
val, _ = tr.FloatErr(nn)
return
}
package mysql
type ConnStatus uint16
// Status of server connection
const (
SERVER_STATUS_IN_TRANS ConnStatus = 0x01 // Transaction has started
SERVER_STATUS_AUTOCOMMIT ConnStatus = 0x02 // Server in auto_commit mode
SERVER_STATUS_MORE_RESULTS ConnStatus = 0x04
SERVER_MORE_RESULTS_EXISTS ConnStatus = 0x08 // Multi query - next query exists
SERVER_QUERY_NO_GOOD_INDEX_USED ConnStatus = 0x10
SERVER_QUERY_NO_INDEX_USED ConnStatus = 0x20
SERVER_STATUS_CURSOR_EXISTS ConnStatus = 0x40 // Server opened a read-only non-scrollable cursor for a query
SERVER_STATUS_LAST_ROW_SENT ConnStatus = 0x80
SERVER_STATUS_DB_DROPPED ConnStatus = 0x100
SERVER_STATUS_NO_BACKSLASH_ESCAPES ConnStatus = 0x200
)
package mysql
import (
"errors"
"fmt"
"strconv"
"strings"
"time"
)
// For MySQL DATE type
type Date struct {
Year int16
Month, Day byte
}
func (dd Date) String() string {
return fmt.Sprintf("%04d-%02d-%02d", dd.Year, dd.Month, dd.Day)
}
// True if date is 0000-00-00
func (dd Date) IsZero() bool {
return dd.Day == 0 && dd.Month == 0 && dd.Year == 0
}
// Converts Date to time.Time using loc location.
// Converts MySQL zero to time.Time zero.
func (dd Date) Time(loc *time.Location) (t time.Time) {
if !dd.IsZero() {
t = time.Date(
int(dd.Year), time.Month(dd.Month), int(dd.Day),
0, 0, 0, 0,
loc,
)
}
return
}
// Converts Date to time.Time using Local location.
// Converts MySQL zero to time.Time zero.
func (dd Date) Localtime() time.Time {
return dd.Time(time.Local)
}
// Convert string date in format YYYY-MM-DD to Date.
// Leading and trailing spaces are ignored.
func ParseDate(str string) (dd Date, err error) {
str = strings.TrimSpace(str)
if str == "0000-00-00" {
return
}
var (
y, m, d int
)
if len(str) != 10 || str[4] != '-' || str[7] != '-' {
goto invalid
}
if y, err = strconv.Atoi(str[0:4]); err != nil {
return
}
if m, err = strconv.Atoi(str[5:7]); err != nil {
return
}
if m < 0 || m > 12 { // MySQL permits month == 0
goto invalid
}
if d, err = strconv.Atoi(str[8:10]); err != nil {
return
}
if d < 0 { // MySQL permits day == 0
goto invalid
}
switch m {
case 1, 3, 5, 7, 8, 10, 12:
if d > 31 {
goto invalid
}
case 4, 6, 9, 11:
if d > 30 {
goto invalid
}
case 2:
if d > 29 {
goto invalid
}
}
dd.Year = int16(y)
dd.Month = byte(m)
dd.Day = byte(d)
return
invalid:
err = errors.New("Invalid MySQL DATE string: " + str)
return
}
// Sandard MySQL datetime format
const TimeFormat = "2006-01-02 15:04:05.000000000"
// Returns t as string in MySQL format Converts time.Time zero to MySQL zero.
func TimeString(t time.Time) string {
if t.IsZero() {
return "0000-00-00 00:00:00"
}
if t.Nanosecond() == 0 {
return t.Format(TimeFormat[:19])
}
return t.Format(TimeFormat)
}
// Parses string datetime in TimeFormat using loc location.
// Converts MySQL zero to time.Time zero.
func ParseTime(str string, loc *time.Location) (t time.Time, err error) {
str = strings.TrimSpace(str)
format := TimeFormat[:19]
switch len(str) {
case 10:
if str == "0000-00-00" {
return
}
format = format[:10]
case 19:
if str == "0000-00-00 00:00:00" {
return
}
}
// Don't expect 0000-00-00 00:00:00.0+
t, err = time.ParseInLocation(format, str, loc)
return
}
// Convert time.Duration to string representation of mysql.TIME
func DurationString(d time.Duration) string {
sign := 1
if d < 0 {
sign = -1
d = -d
}
ns := int(d % 1e9)
d /= 1e9
sec := int(d % 60)
d /= 60
min := int(d % 60)
hour := int(d/60) * sign
if ns == 0 {
return fmt.Sprintf("%d:%02d:%02d", hour, min, sec)
}
return fmt.Sprintf("%d:%02d:%02d.%09d", hour, min, sec, ns)
}
// Parse duration from MySQL string format [+-]H+:MM:SS[.UUUUUUUUU].
// Leading and trailing spaces are ignored. If format is invalid returns nil.
func ParseDuration(str string) (dur time.Duration, err error) {
str = strings.TrimSpace(str)
orig := str
// Check sign
sign := int64(1)
switch str[0] {
case '-':
sign = -1
fallthrough
case '+':
str = str[1:]
}
var i, d int64
// Find houre
if nn := strings.IndexRune(str, ':'); nn != -1 {
if i, err = strconv.ParseInt(str[0:nn], 10, 64); err != nil {
return
}
d = i * 3600
str = str[nn+1:]
} else {
goto invalid
}
if len(str) != 5 && len(str) != 15 || str[2] != ':' {
goto invalid
}
if i, err = strconv.ParseInt(str[0:2], 10, 64); err != nil {
return
}
if i < 0 || i > 59 {
goto invalid
}
d += i * 60
if i, err = strconv.ParseInt(str[3:5], 10, 64); err != nil {
return
}
if i < 0 || i > 59 {
goto invalid
}
d += i
d *= 1e9
if len(str) == 15 {
if str[5] != '.' {
goto invalid
}
if i, err = strconv.ParseInt(str[6:15], 10, 64); err != nil {
return
}
d += i
}
dur = time.Duration(d * sign)
return
invalid:
err = errors.New("invalid MySQL TIME string: " + orig)
return
}
type Blob []byte
type Raw struct {
Typ uint16
Val *[]byte
}
type Timestamp struct {
time.Time
}
func (t Timestamp) String() string {
return TimeString(t.Time)
}
package mysql
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"strings"
"time"
"unicode"
)
// Version returns mymysql version string
func Version() string {
return "1.5.3"
}
func syntaxError(ln int) error {
return fmt.Errorf("syntax error at line: %d", ln)
}
// Creates new conneection handler using configuration in cfgFile. Returns
// connection handler and map contains unknown options.
//
// Config file format(example):
//
// # mymysql options (if some option isn't specified it defaults to "")
//
// DbRaddr 127.0.0.1:3306
// # DbRaddr /var/run/mysqld/mysqld.sock
// DbUser testuser
// DbPass TestPasswd9
// # optional: DbName test
// # optional: DbEncd utf8
// # optional: DbLaddr 127.0.0.1:0
// # optional: DbTimeout 15s
//
// # Your options (returned in unk)
//
// MyOpt some text
func NewFromCF(cfgFile string) (con Conn, unk map[string]string, err error) {
var cf *os.File
cf, err = os.Open(cfgFile)
if err != nil {
return
}
br := bufio.NewReader(cf)
um := make(map[string]string)
var proto, laddr, raddr, user, pass, name, encd, to string
for i := 1; ; i++ {
buf, isPrefix, e := br.ReadLine()
if e != nil {
if e == io.EOF {
break
}
err = e
return
}
l := string(buf)
if isPrefix {
err = fmt.Errorf("line %d is too long", i)
return
}
l = strings.TrimFunc(l, unicode.IsSpace)
if len(l) == 0 || l[0] == '#' {
continue
}
n := strings.IndexFunc(l, unicode.IsSpace)
if n == -1 {
err = fmt.Errorf("syntax error at line: %d", i)
return
}
v := l[:n]
l = strings.TrimLeftFunc(l[n:], unicode.IsSpace)
switch v {
case "DbLaddr":
laddr = l
case "DbRaddr":
raddr = l
proto = "tcp"
if strings.IndexRune(l, ':') == -1 {
proto = "unix"
}
case "DbUser":
user = l
case "DbPass":
pass = l
case "DbName":
name = l
case "DbEncd":
encd = l
case "DbTimeout":
to = l
default:
um[v] = l
}
}
if raddr == "" {
err = errors.New("DbRaddr option is empty")
return
}
unk = um
if name != "" {
con = New(proto, laddr, raddr, user, pass, name)
} else {
con = New(proto, laddr, raddr, user, pass)
}
if encd != "" {
con.Register(fmt.Sprintf("SET NAMES %s", encd))
}
if to != "" {
var timeout time.Duration
timeout, err = time.ParseDuration(to)
if err != nil {
return
}
con.SetTimeout(timeout)
}
return
}
// Calls Start and next calls GetRow as long as it reads all rows from the
// result. Next it returns all readed rows as the slice of rows.
func Query(c Conn, sql string, params ...interface{}) (rows []Row, res Result, err error) {
res, err = c.Start(sql, params...)
if err != nil {
return
}
rows, err = GetRows(res)
return
}
// Calls Start and next calls GetFirstRow
func QueryFirst(c Conn, sql string, params ...interface{}) (row Row, res Result, err error) {
res, err = c.Start(sql, params...)
if err != nil {
return
}
row, err = GetFirstRow(res)
return
}
// Calls Start and next calls GetLastRow
func QueryLast(c Conn, sql string, params ...interface{}) (row Row, res Result, err error) {
res, err = c.Start(sql, params...)
if err != nil {
return
}
row, err = GetLastRow(res)
return
}
// Calls Run and next call GetRow as long as it reads all rows from the
// result. Next it returns all readed rows as the slice of rows.
func Exec(s Stmt, params ...interface{}) (rows []Row, res Result, err error) {
res, err = s.Run(params...)
if err != nil {
return
}
rows, err = GetRows(res)
return
}
// Calls Run and next call GetFirstRow
func ExecFirst(s Stmt, params ...interface{}) (row Row, res Result, err error) {
res, err = s.Run(params...)
if err != nil {
return
}
row, err = GetFirstRow(res)
return
}
// Calls Run and next call GetLastRow
func ExecLast(s Stmt, params ...interface{}) (row Row, res Result, err error) {
res, err = s.Run(params...)
if err != nil {
return
}
row, err = GetLastRow(res)
return
}
// Calls r.MakeRow and next r.ScanRow. Doesn't return io.EOF error (returns nil
// row insted).
func GetRow(r Result) (Row, error) {
row := r.MakeRow()
err := r.ScanRow(row)
if err != nil {
if err == io.EOF {
return nil, nil
}
return nil, err
}
return row, nil
}
// Reads all rows from result and returns them as slice.
func GetRows(r Result) (rows []Row, err error) {
var row Row
for {
row, err = r.GetRow()
if err != nil || row == nil {
break
}
rows = append(rows, row)
}
return
}
// Returns last row and discard others
func GetLastRow(r Result) (Row, error) {
row := r.MakeRow()
err := r.ScanRow(row)
if err == io.EOF {
return nil, nil
}
for err == nil {
err = r.ScanRow(row)
}
if err == io.EOF {
return row, nil
}
return nil, err
}
// Read all unreaded rows and discard them. This function is useful if you
// don't want to use the remaining rows. It has an impact only on current
// result. If there is multi result query, you must use NextResult method and
// read/discard all rows in this result, before use other method that sends
// data to the server. You can't use this function if last GetRow returned nil.
func End(r Result) error {
_, err := GetLastRow(r)
return err
}
// Returns first row and discard others
func GetFirstRow(r Result) (row Row, err error) {
row, err = r.GetRow()
if err == nil && row != nil {
err = r.End()
}
return
}
func escapeString(txt string) string {
var (
esc string
buf bytes.Buffer
)
last := 0
for ii, bb := range txt {
switch bb {
case 0:
esc = `\0`
case '\n':
esc = `\n`
case '\r':
esc = `\r`
case '\\':
esc = `\\`
case '\'':
esc = `\'`
case '"':
esc = `\"`
case '\032':
esc = `\Z`
default:
continue
}
io.WriteString(&buf, txt[last:ii])
io.WriteString(&buf, esc)
last = ii + 1
}
io.WriteString(&buf, txt[last:])
return buf.String()
}
func escapeQuotes(txt string) string {
var buf bytes.Buffer
last := 0
for ii, bb := range txt {
if bb == '\'' {
io.WriteString(&buf, txt[last:ii])
io.WriteString(&buf, `''`)
last = ii + 1
}
}
io.WriteString(&buf, txt[last:])
return buf.String()
}
// Escapes special characters in the txt, so it is safe to place returned string
// to Query method.
func Escape(c Conn, txt string) string {
if c.Status()&SERVER_STATUS_NO_BACKSLASH_ESCAPES != 0 {
return escapeQuotes(txt)
}
return escapeString(txt)
}
Copyright (c) 2010, Michal Derkacz
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package native
func NbinToNstr(nbin *[]byte) *string {
if nbin == nil {
return nil
}
str := string(*nbin)
return &str
}
func NstrToNbin(nstr *string) *[]byte {
if nstr == nil {
return nil
}
bin := []byte(*nstr)
return &bin
}
package native
import (
"github.com/ziutek/mymysql/mysql"
"reflect"
"time"
)
var (
timeType = reflect.TypeOf(time.Time{})
timestampType = reflect.TypeOf(mysql.Timestamp{})
dateType = reflect.TypeOf(mysql.Date{})
durationType = reflect.TypeOf(time.Duration(0))
blobType = reflect.TypeOf(mysql.Blob{})
rawType = reflect.TypeOf(mysql.Raw{})
)
// val should be an addressable value
func bindValue(val reflect.Value) (out paramValue) {
if !val.IsValid() {
out.typ = MYSQL_TYPE_NULL
return
}
typ := val.Type()
if typ.Kind() == reflect.Ptr {
// We have addressable pointer
out.addr = val.Addr()
// Dereference pointer for next operation on its value
typ = typ.Elem()
val = val.Elem()
} else {
// We have addressable value. Create a pointer to it
pv := val.Addr()
// This pointer is unaddressable so copy it and return an address
out.addr = reflect.New(pv.Type())
out.addr.Elem().Set(pv)
}
// Obtain value type
switch typ.Kind() {
case reflect.String:
out.typ = MYSQL_TYPE_STRING
out.length = -1
return
case reflect.Int:
out.typ = _INT_TYPE
out.length = _SIZE_OF_INT
return
case reflect.Int8:
out.typ = MYSQL_TYPE_TINY
out.length = 1
return
case reflect.Int16:
out.typ = MYSQL_TYPE_SHORT
out.length = 2
return
case reflect.Int32:
out.typ = MYSQL_TYPE_LONG
out.length = 4
return
case reflect.Int64:
if typ == durationType {
out.typ = MYSQL_TYPE_TIME
out.length = -1
return
}
out.typ = MYSQL_TYPE_LONGLONG
out.length = 8
return
case reflect.Uint:
out.typ = _INT_TYPE | MYSQL_UNSIGNED_MASK
out.length = _SIZE_OF_INT
return
case reflect.Uint8:
out.typ = MYSQL_TYPE_TINY | MYSQL_UNSIGNED_MASK
out.length = 1
return
case reflect.Uint16:
out.typ = MYSQL_TYPE_SHORT | MYSQL_UNSIGNED_MASK
out.length = 2
return
case reflect.Uint32:
out.typ = MYSQL_TYPE_LONG | MYSQL_UNSIGNED_MASK
out.length = 4
return
case reflect.Uint64:
out.typ = MYSQL_TYPE_LONGLONG | MYSQL_UNSIGNED_MASK
out.length = 8
return
case reflect.Float32:
out.typ = MYSQL_TYPE_FLOAT
out.length = 4
return
case reflect.Float64:
out.typ = MYSQL_TYPE_DOUBLE
out.length = 8
return
case reflect.Slice:
out.length = -1
if typ == blobType {
out.typ = MYSQL_TYPE_BLOB
return
}
if typ.Elem().Kind() == reflect.Uint8 {
out.typ = MYSQL_TYPE_VAR_STRING
return
}
case reflect.Struct:
out.length = -1
if typ == timeType {
out.typ = MYSQL_TYPE_DATETIME
return
}
if typ == dateType {
out.typ = MYSQL_TYPE_DATE
return
}
if typ == timestampType {
out.typ = MYSQL_TYPE_TIMESTAMP
return
}
if typ == rawType {
out.typ = val.FieldByName("Typ").Interface().(uint16)
out.addr = val.FieldByName("Val").Addr()
out.raw = true
return
}
case reflect.Bool:
out.typ = MYSQL_TYPE_TINY
// bool implementation isn't documented so we treat it in special way
out.length = -1
return
}
panic(mysql.ErrBindUnkType)
}
package native
import (
"github.com/ziutek/mymysql/mysql"
"time"
)
// Integers
func DecodeU16(buf []byte) uint16 {
return uint16(buf[1])<<8 | uint16(buf[0])
}
func (pr *pktReader) readU16() uint16 {
buf := pr.buf[:2]
pr.readFull(buf)
return DecodeU16(buf)
}
func DecodeU24(buf []byte) uint32 {
return (uint32(buf[2])<<8|uint32(buf[1]))<<8 | uint32(buf[0])
}
func (pr *pktReader) readU24() uint32 {
buf := pr.buf[:3]
pr.readFull(buf)
return DecodeU24(buf)
}
func DecodeU32(buf []byte) uint32 {
return ((uint32(buf[3])<<8|uint32(buf[2]))<<8|
uint32(buf[1]))<<8 | uint32(buf[0])
}
func (pr *pktReader) readU32() uint32 {
buf := pr.buf[:4]
pr.readFull(buf)
return DecodeU32(buf)
}
func DecodeU64(buf []byte) (rv uint64) {
for ii, vv := range buf {
rv |= uint64(vv) << uint(ii*8)
}
return
}
func (pr *pktReader) readU64() (rv uint64) {
buf := pr.buf[:8]
pr.readFull(buf)
return DecodeU64(buf)
}
func EncodeU16(buf []byte, val uint16) {
buf[0] = byte(val)
buf[1] = byte(val >> 8)
}
func (pw *pktWriter) writeU16(val uint16) {
buf := pw.buf[:2]
EncodeU16(buf, val)
pw.write(buf)
}
func EncodeU24(buf []byte, val uint32) {
buf[0] = byte(val)
buf[1] = byte(val >> 8)
buf[2] = byte(val >> 16)
}
func (pw *pktWriter) writeU24(val uint32) {
buf := pw.buf[:3]
EncodeU24(buf, val)
pw.write(buf)
}
func EncodeU32(buf []byte, val uint32) {
buf[0] = byte(val)
buf[1] = byte(val >> 8)
buf[2] = byte(val >> 16)
buf[3] = byte(val >> 24)
}
func (pw *pktWriter) writeU32(val uint32) {
buf := pw.buf[:4]
EncodeU32(buf, val)
pw.write(buf)
}
func EncodeU64(buf []byte, val uint64) {
buf[0] = byte(val)
buf[1] = byte(val >> 8)
buf[2] = byte(val >> 16)
buf[3] = byte(val >> 24)
buf[4] = byte(val >> 32)
buf[5] = byte(val >> 40)
buf[6] = byte(val >> 48)
buf[7] = byte(val >> 56)
}
func (pw *pktWriter) writeU64(val uint64) {
buf := pw.buf[:8]
EncodeU64(buf, val)
pw.write(buf)
}
// Variable length values
func (pr *pktReader) readNullLCB() (lcb uint64, null bool) {
bb := pr.readByte()
switch bb {
case 251:
null = true
case 252:
lcb = uint64(pr.readU16())
case 253:
lcb = uint64(pr.readU24())
case 254:
lcb = pr.readU64()
default:
lcb = uint64(bb)
}
return
}
func (pr *pktReader) readLCB() uint64 {
lcb, null := pr.readNullLCB()
if null {
panic(mysql.ErrUnexpNullLCB)
}
return lcb
}
func (pw *pktWriter) writeLCB(val uint64) {
switch {
case val <= 250:
pw.writeByte(byte(val))
case val <= 0xffff:
pw.writeByte(252)
pw.writeU16(uint16(val))
case val <= 0xffffff:
pw.writeByte(253)
pw.writeU24(uint32(val))
default:
pw.writeByte(254)
pw.writeU64(val)
}
}
func lenLCB(val uint64) int {
switch {
case val <= 250:
return 1
case val <= 0xffff:
return 3
case val <= 0xffffff:
return 4
}
return 9
}
func (pr *pktReader) readNullBin() (buf []byte, null bool) {
var l uint64
l, null = pr.readNullLCB()
if null {
return
}
buf = make([]byte, l)
pr.readFull(buf)
return
}
func (pr *pktReader) readBin() []byte {
buf, null := pr.readNullBin()
if null {
panic(mysql.ErrUnexpNullLCS)
}
return buf
}
func (pr *pktReader) skipBin() {
n, _ := pr.readNullLCB()
pr.skipN(int(n))
}
func (pw *pktWriter) writeBin(buf []byte) {
pw.writeLCB(uint64(len(buf)))
pw.write(buf)
}
func lenBin(buf []byte) int {
return lenLCB(uint64(len(buf))) + len(buf)
}
func lenStr(str string) int {
return lenLCB(uint64(len(str))) + len(str)
}
func (pw *pktWriter) writeLC(v interface{}) {
switch val := v.(type) {
case []byte:
pw.writeBin(val)
case *[]byte:
pw.writeBin(*val)
case string:
pw.writeBin([]byte(val))
case *string:
pw.writeBin([]byte(*val))
default:
panic("Unknown data type for write as length coded string")
}
}
func lenLC(v interface{}) int {
switch val := v.(type) {
case []byte:
return lenBin(val)
case *[]byte:
return lenBin(*val)
case string:
return lenStr(val)
case *string:
return lenStr(*val)
}
panic("Unknown data type for write as length coded string")
}
func (pr *pktReader) readNTB() (buf []byte) {
for {
ch := pr.readByte()
if ch == 0 {
break
}
buf = append(buf, ch)
}
return
}
func (pw *pktWriter) writeNTB(buf []byte) {
pw.write(buf)
pw.writeByte(0)
}
func (pw *pktWriter) writeNT(v interface{}) {
switch val := v.(type) {
case []byte:
pw.writeNTB(val)
case string:
pw.writeNTB([]byte(val))
default:
panic("Unknown type for write as null terminated data")
}
}
// Date and time
func (pr *pktReader) readDuration() time.Duration {
dlen := pr.readByte()
switch dlen {
case 251:
// Null
panic(mysql.ErrUnexpNullTime)
case 0:
// 00:00:00
return 0
case 5, 8, 12:
// Properly time length
default:
panic(mysql.ErrWrongDateLen)
}
buf := pr.buf[:dlen]
pr.readFull(buf)
tt := int64(0)
switch dlen {
case 12:
// Nanosecond part
tt += int64(DecodeU32(buf[8:]))
fallthrough
case 8:
// HH:MM:SS part
tt += int64(int(buf[5])*3600+int(buf[6])*60+int(buf[7])) * 1e9
fallthrough
case 5:
// Day part
tt += int64(DecodeU32(buf[1:5])) * (24 * 3600 * 1e9)
}
if buf[0] != 0 {
tt = -tt
}
return time.Duration(tt)
}
func EncodeDuration(buf []byte, d time.Duration) int {
buf[0] = 0
if d < 0 {
buf[1] = 1
d = -d
}
if ns := uint32(d % 1e9); ns != 0 {
EncodeU32(buf[9:13], ns) // nanosecond
buf[0] += 4
}
d /= 1e9
if hms := int(d % (24 * 3600)); buf[0] != 0 || hms != 0 {
buf[8] = byte(hms % 60) // second
hms /= 60
buf[7] = byte(hms % 60) // minute
buf[6] = byte(hms / 60) // hour
buf[0] += 3
}
if day := uint32(d / (24 * 3600)); buf[0] != 0 || day != 0 {
EncodeU32(buf[2:6], day) // day
buf[0] += 4
}
buf[0]++ // For sign byte
return int(buf[0] + 1)
}
func (pw *pktWriter) writeDuration(d time.Duration) {
buf := pw.buf[:13]
n := EncodeDuration(buf, d)
pw.write(buf[:n])
}
func lenDuration(d time.Duration) int {
if d == 0 {
return 2
}
if d%1e9 != 0 {
return 13
}
d /= 1e9
if d%(24*3600) != 0 {
return 9
}
return 6
}
func (pr *pktReader) readTime() time.Time {
dlen := pr.readByte()
switch dlen {
case 251:
// Null
panic(mysql.ErrUnexpNullDate)
case 0:
// return 0000-00-00 converted to time.Time zero
return time.Time{}
case 4, 7, 11:
// Properly datetime length
default:
panic(mysql.ErrWrongDateLen)
}
buf := pr.buf[:dlen]
pr.readFull(buf)
var y, mon, d, h, m, s, u int
switch dlen {
case 11:
// 2006-01-02 15:04:05.001004005
u = int(DecodeU32(buf[7:]))
fallthrough
case 7:
// 2006-01-02 15:04:05
h = int(buf[4])
m = int(buf[5])
s = int(buf[6])
fallthrough
case 4:
// 2006-01-02
y = int(DecodeU16(buf[0:2]))
mon = int(buf[2])
d = int(buf[3])
}
n := u * int(time.Microsecond)
return time.Date(y, time.Month(mon), d, h, m, s, n, time.Local)
}
func encodeNonzeroTime(buf []byte, y int16, mon, d, h, m, s byte, u uint32) int {
buf[0] = 0
switch {
case u != 0:
EncodeU32(buf[8:12], u)
buf[0] += 4
fallthrough
case s != 0 || m != 0 || h != 0:
buf[7] = s
buf[6] = m
buf[5] = h
buf[0] += 3
}
buf[4] = d
buf[3] = mon
EncodeU16(buf[1:3], uint16(y))
buf[0] += 4
return int(buf[0] + 1)
}
func getTimeMicroseconds(t time.Time) int {
return (t.Nanosecond() + int(time.Microsecond/2)) / int(time.Microsecond)
}
func EncodeTime(buf []byte, t time.Time) int {
if t.IsZero() {
// MySQL zero
buf[0] = 0
return 1 // MySQL zero
}
y, mon, d := t.Date()
h, m, s := t.Clock()
u:= getTimeMicroseconds(t)
return encodeNonzeroTime(
buf,
int16(y), byte(mon), byte(d),
byte(h), byte(m), byte(s), uint32(u),
)
}
func (pw *pktWriter) writeTime(t time.Time) {
buf := pw.buf[:12]
n := EncodeTime(buf, t)
pw.write(buf[:n])
}
func lenTime(t time.Time) int {
switch {
case t.IsZero():
return 1
case getTimeMicroseconds(t) != 0:
return 12
case t.Second() != 0 || t.Minute() != 0 || t.Hour() != 0:
return 8
}
return 5
}
func (pr *pktReader) readDate() mysql.Date {
y, m, d := pr.readTime().Date()
return mysql.Date{int16(y), byte(m), byte(d)}
}
func EncodeDate(buf []byte, d mysql.Date) int {
if d.IsZero() {
// MySQL zero
buf[0] = 0
return 1
}
return encodeNonzeroTime(buf, d.Year, d.Month, d.Day, 0, 0, 0, 0)
}
func (pw *pktWriter) writeDate(d mysql.Date) {
buf := pw.buf[:5]
n := EncodeDate(buf, d)
pw.write(buf[:n])
}
func lenDate(d mysql.Date) int {
if d.IsZero() {
return 1
}
return 5
}
package native
import (
"log"
)
//import "log"
// _COM_QUIT, _COM_STATISTICS, _COM_PROCESS_INFO, _COM_DEBUG, _COM_PING:
func (my *Conn) sendCmd(cmd byte) {
my.seq = 0
pw := my.newPktWriter(1)
pw.writeByte(cmd)
if my.Debug {
log.Printf("[%2d <-] Command packet: Cmd=0x%x", my.seq-1, cmd)
}
}
// _COM_QUERY, _COM_INIT_DB, _COM_CREATE_DB, _COM_DROP_DB, _COM_STMT_PREPARE:
func (my *Conn) sendCmdStr(cmd byte, s string) {
my.seq = 0
pw := my.newPktWriter(1 + len(s))
pw.writeByte(cmd)
pw.write([]byte(s))
if my.Debug {
log.Printf("[%2d <-] Command packet: Cmd=0x%x %s", my.seq-1, cmd, s)
}
}
// _COM_PROCESS_KILL, _COM_STMT_CLOSE, _COM_STMT_RESET:
func (my *Conn) sendCmdU32(cmd byte, u uint32) {
my.seq = 0
pw := my.newPktWriter(1 + 4)
pw.writeByte(cmd)
pw.writeU32(u)
if my.Debug {
log.Printf("[%2d <-] Command packet: Cmd=0x%x %d", my.seq-1, cmd, u)
}
}
func (my *Conn) sendLongData(stmtid uint32, pnum uint16, data []byte) {
my.seq = 0
pw := my.newPktWriter(1 + 4 + 2 + len(data))
pw.writeByte(_COM_STMT_SEND_LONG_DATA)
pw.writeU32(stmtid) // Statement ID
pw.writeU16(pnum) // Parameter number
pw.write(data) // payload
if my.Debug {
log.Printf("[%2d <-] SendLongData packet: pnum=%d", my.seq-1, pnum)
}
}
/*func (my *Conn) sendCmd(cmd byte, argv ...interface{}) {
// Reset sequence number
my.seq = 0
// Write command
switch cmd {
case _COM_QUERY, _COM_INIT_DB, _COM_CREATE_DB, _COM_DROP_DB,
_COM_STMT_PREPARE:
pw := my.newPktWriter(1 + lenBS(argv[0]))
writeByte(pw, cmd)
writeBS(pw, argv[0])
case _COM_STMT_SEND_LONG_DATA:
pw := my.newPktWriter(1 + 4 + 2 + lenBS(argv[2]))
writeByte(pw, cmd)
writeU32(pw, argv[0].(uint32)) // Statement ID
writeU16(pw, argv[1].(uint16)) // Parameter number
writeBS(pw, argv[2]) // payload
case _COM_QUIT, _COM_STATISTICS, _COM_PROCESS_INFO, _COM_DEBUG, _COM_PING:
pw := my.newPktWriter(1)
writeByte(pw, cmd)
case _COM_FIELD_LIST:
pay_len := 1 + lenBS(argv[0]) + 1
if len(argv) > 1 {
pay_len += lenBS(argv[1])
}
pw := my.newPktWriter(pay_len)
writeByte(pw, cmd)
writeNT(pw, argv[0])
if len(argv) > 1 {
writeBS(pw, argv[1])
}
case _COM_TABLE_DUMP:
pw := my.newPktWriter(1 + lenLC(argv[0]) + lenLC(argv[1]))
writeByte(pw, cmd)
writeLC(pw, argv[0])
writeLC(pw, argv[1])
case _COM_REFRESH, _COM_SHUTDOWN:
pw := my.newPktWriter(1 + 1)
writeByte(pw, cmd)
writeByte(pw, argv[0].(byte))
case _COM_STMT_FETCH:
pw := my.newPktWriter(1 + 4 + 4)
writeByte(pw, cmd)
writeU32(pw, argv[0].(uint32))
writeU32(pw, argv[1].(uint32))
case _COM_PROCESS_KILL, _COM_STMT_CLOSE, _COM_STMT_RESET:
pw := my.newPktWriter(1 + 4)
writeByte(pw, cmd)
writeU32(pw, argv[0].(uint32))
case _COM_SET_OPTION:
pw := my.newPktWriter(1 + 2)
writeByte(pw, cmd)
writeU16(pw, argv[0].(uint16))
case _COM_CHANGE_USER:
pw := my.newPktWriter(
1 + lenBS(argv[0]) + 1 + lenLC(argv[1]) + lenBS(argv[2]) + 1,
)
writeByte(pw, cmd)
writeNT(pw, argv[0]) // User name
writeLC(pw, argv[1]) // Scrambled password
writeNT(pw, argv[2]) // Database name
//writeU16(pw, argv[3]) // Character set number (since 5.1.23?)
case _COM_BINLOG_DUMP:
pay_len := 1 + 4 + 2 + 4
if len(argv) > 3 {
pay_len += lenBS(argv[3])
}
pw := my.newPktWriter(pay_len)
writeByte(pw, cmd)
writeU32(pw, argv[0].(uint32)) // Start position
writeU16(pw, argv[1].(uint16)) // Flags
writeU32(pw, argv[2].(uint32)) // Slave server id
if len(argv) > 3 {
writeBS(pw, argv[3])
}
// TODO: case COM_REGISTER_SLAVE:
default:
panic("Unknown code for MySQL command")
}
if my.Debug {
log.Printf("[%2d <-] Command packet: Cmd=0x%x", my.seq-1, cmd)
}
}*/
package native
import (
"io"
"runtime"
)
var tab8s = " "
func catchError(err *error) {
if pv := recover(); pv != nil {
switch e := pv.(type) {
case runtime.Error:
panic(pv)
case error:
if e == io.EOF {
*err = io.ErrUnexpectedEOF
} else {
*err = e
}
default:
panic(pv)
}
}
}
package native
import "strconv"
// Client caps - borrowed from GoMySQL
const (
_CLIENT_LONG_PASSWORD = 1 << iota // new more secure passwords
_CLIENT_FOUND_ROWS // Found instead of affected rows
_CLIENT_LONG_FLAG // Get all column flags
_CLIENT_CONNECT_WITH_DB // One can specify db on connect
_CLIENT_NO_SCHEMA // Don't allow database.table.column
_CLIENT_COMPRESS // Can use compression protocol
_CLIENT_ODBC // Odbc client
_CLIENT_LOCAL_FILES // Can use LOAD DATA LOCAL
_CLIENT_IGNORE_SPACE // Ignore spaces before '('
_CLIENT_PROTOCOL_41 // New 4.1 protocol
_CLIENT_INTERACTIVE // This is an interactive client
_CLIENT_SSL // Switch to SSL after handshake
_CLIENT_IGNORE_SIGPIPE // IGNORE sigpipes
_CLIENT_TRANSACTIONS // Client knows about transactions
_CLIENT_RESERVED // Old flag for 4.1 protocol
_CLIENT_SECURE_CONN // New 4.1 authentication
_CLIENT_MULTI_STATEMENTS // Enable/disable multi-stmt support
_CLIENT_MULTI_RESULTS // Enable/disable multi-results
_CLIENT_PS_MULTI_RESULTS // Enable/disable multiple resultsets for COM_STMT_EXECUTE
_CLIENT_PLUGIN_AUTH // Supports authentication plugins
_CLIENT_CONNECT_ATTRS // Sends connection attributes in Protocol::HandshakeResponse41
_CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA // Length of auth response data in Protocol::HandshakeResponse41 is a length-encoded integer
_CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS // Enable/disable expired passwords
_CLIENT_SESSION_TRACK // Can set SERVER_SESSION_STATE_CHANGED in the Status Flags and send session-state change data after a OK packet.
_CLIENT_DEPRECATE_EOF // Expects an OK (instead of EOF) after the resultset rows of a Text Resultset
)
// Commands - borrowed from GoMySQL
const (
_COM_QUIT = 0x01
_COM_INIT_DB = 0x02
_COM_QUERY = 0x03
_COM_FIELD_LIST = 0x04
_COM_CREATE_DB = 0x05
_COM_DROP_DB = 0x06
_COM_REFRESH = 0x07
_COM_SHUTDOWN = 0x08
_COM_STATISTICS = 0x09
_COM_PROCESS_INFO = 0x0a
_COM_CONNECT = 0x0b
_COM_PROCESS_KILL = 0x0c
_COM_DEBUG = 0x0d
_COM_PING = 0x0e
_COM_TIME = 0x0f
_COM_DELAYED_INSERT = 0x10
_COM_CHANGE_USER = 0x11
_COM_BINLOG_DUMP = 0x12
_COM_TABLE_DUMP = 0x13
_COM_CONNECT_OUT = 0x14
_COM_REGISTER_SLAVE = 0x15
_COM_STMT_PREPARE = 0x16
_COM_STMT_EXECUTE = 0x17
_COM_STMT_SEND_LONG_DATA = 0x18
_COM_STMT_CLOSE = 0x19
_COM_STMT_RESET = 0x1a
_COM_SET_OPTION = 0x1b
_COM_STMT_FETCH = 0x1c
)
// MySQL protocol types.
//
// mymysql uses only some of them for send data to the MySQL server. Used
// MySQL types are marked with a comment contains mymysql type that uses it.
const (
MYSQL_TYPE_DECIMAL = 0x00
MYSQL_TYPE_TINY = 0x01 // int8, uint8, bool
MYSQL_TYPE_SHORT = 0x02 // int16, uint16
MYSQL_TYPE_LONG = 0x03 // int32, uint32
MYSQL_TYPE_FLOAT = 0x04 // float32
MYSQL_TYPE_DOUBLE = 0x05 // float64
MYSQL_TYPE_NULL = 0x06 // nil
MYSQL_TYPE_TIMESTAMP = 0x07 // Timestamp
MYSQL_TYPE_LONGLONG = 0x08 // int64, uint64
MYSQL_TYPE_INT24 = 0x09
MYSQL_TYPE_DATE = 0x0a // Date
MYSQL_TYPE_TIME = 0x0b // Time
MYSQL_TYPE_DATETIME = 0x0c // time.Time
MYSQL_TYPE_YEAR = 0x0d
MYSQL_TYPE_NEWDATE = 0x0e
MYSQL_TYPE_VARCHAR = 0x0f
MYSQL_TYPE_BIT = 0x10
MYSQL_TYPE_NEWDECIMAL = 0xf6
MYSQL_TYPE_ENUM = 0xf7
MYSQL_TYPE_SET = 0xf8
MYSQL_TYPE_TINY_BLOB = 0xf9
MYSQL_TYPE_MEDIUM_BLOB = 0xfa
MYSQL_TYPE_LONG_BLOB = 0xfb
MYSQL_TYPE_BLOB = 0xfc // Blob
MYSQL_TYPE_VAR_STRING = 0xfd // []byte
MYSQL_TYPE_STRING = 0xfe // string
MYSQL_TYPE_GEOMETRY = 0xff
MYSQL_UNSIGNED_MASK = uint16(1 << 15)
)
// Mapping of MySQL types to (prefered) protocol types. Use it if you create
// your own Raw value.
//
// Comments contains corresponding types used by mymysql. string type may be
// replaced by []byte type and vice versa. []byte type is native for sending
// on a network, so any string is converted to it before sending. Than for
// better performance use []byte.
const (
// Client send and receive, mymysql representation for send / receive
TINYINT = MYSQL_TYPE_TINY // int8 / int8
SMALLINT = MYSQL_TYPE_SHORT // int16 / int16
INT = MYSQL_TYPE_LONG // int32 / int32
BIGINT = MYSQL_TYPE_LONGLONG // int64 / int64
FLOAT = MYSQL_TYPE_FLOAT // float32 / float32
DOUBLE = MYSQL_TYPE_DOUBLE // float64 / float32
TIME = MYSQL_TYPE_TIME // Time / Time
DATE = MYSQL_TYPE_DATE // Date / Date
DATETIME = MYSQL_TYPE_DATETIME // time.Time / time.Time
TIMESTAMP = MYSQL_TYPE_TIMESTAMP // Timestamp / time.Time
CHAR = MYSQL_TYPE_STRING // string / []byte
BLOB = MYSQL_TYPE_BLOB // Blob / []byte
NULL = MYSQL_TYPE_NULL // nil
// Client send only, mymysql representation for send
OUT_TEXT = MYSQL_TYPE_STRING // string
OUT_VARCHAR = MYSQL_TYPE_STRING // string
OUT_BINARY = MYSQL_TYPE_BLOB // Blob
OUT_VARBINARY = MYSQL_TYPE_BLOB // Blob
// Client receive only, mymysql representation for receive
IN_MEDIUMINT = MYSQL_TYPE_LONG // int32
IN_YEAR = MYSQL_TYPE_SHORT // int16
IN_BINARY = MYSQL_TYPE_STRING // []byte
IN_VARCHAR = MYSQL_TYPE_VAR_STRING // []byte
IN_VARBINARY = MYSQL_TYPE_VAR_STRING // []byte
IN_TINYBLOB = MYSQL_TYPE_TINY_BLOB // []byte
IN_TINYTEXT = MYSQL_TYPE_TINY_BLOB // []byte
IN_TEXT = MYSQL_TYPE_BLOB // []byte
IN_MEDIUMBLOB = MYSQL_TYPE_MEDIUM_BLOB // []byte
IN_MEDIUMTEXT = MYSQL_TYPE_MEDIUM_BLOB // []byte
IN_LONGBLOB = MYSQL_TYPE_LONG_BLOB // []byte
IN_LONGTEXT = MYSQL_TYPE_LONG_BLOB // []byte
// MySQL 5.x specific
IN_DECIMAL = MYSQL_TYPE_NEWDECIMAL // TODO
IN_BIT = MYSQL_TYPE_BIT // []byte
)
// Flags - borrowed from GoMySQL
const (
_FLAG_NOT_NULL = 1 << iota
_FLAG_PRI_KEY
_FLAG_UNIQUE_KEY
_FLAG_MULTIPLE_KEY
_FLAG_BLOB
_FLAG_UNSIGNED
_FLAG_ZEROFILL
_FLAG_BINARY
_FLAG_ENUM
_FLAG_AUTO_INCREMENT
_FLAG_TIMESTAMP
_FLAG_SET
_FLAG_NO_DEFAULT_VALUE
)
var (
_SIZE_OF_INT int
_INT_TYPE uint16
)
func init() {
switch strconv.IntSize {
case 32:
_INT_TYPE = MYSQL_TYPE_LONG
_SIZE_OF_INT = 4
case 64:
_INT_TYPE = MYSQL_TYPE_LONGLONG
_SIZE_OF_INT = 8
default:
panic("bad int size")
}
}
package native
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/pem"
"log"
"github.com/ziutek/mymysql/mysql"
)
func (my *Conn) init() {
my.seq = 0 // Reset sequence number, mainly for reconnect
if my.Debug {
log.Printf("[%2d ->] Init packet:", my.seq)
}
pr := my.newPktReader()
my.info.prot_ver = pr.readByte()
my.info.serv_ver = pr.readNTB()
my.info.thr_id = pr.readU32()
pr.readFull(my.info.scramble[0:8])
pr.skipN(1)
my.info.caps = uint32(pr.readU16()) // lower two bytes
my.info.lang = pr.readByte()
my.status = mysql.ConnStatus(pr.readU16())
my.info.caps = uint32(pr.readU16())<<16 | my.info.caps // upper two bytes
pr.skipN(11)
if my.info.caps&_CLIENT_PROTOCOL_41 != 0 {
pr.readFull(my.info.scramble[8:])
}
pr.skipN(1) // reserved (all [00])
if my.info.caps&_CLIENT_PLUGIN_AUTH != 0 {
my.info.plugin = pr.readNTB()
}
pr.skipAll() // Skip other information
if my.Debug {
log.Printf(tab8s+"ProtVer=%d, ServVer=\"%s\" Status=0x%x",
my.info.prot_ver, my.info.serv_ver, my.status,
)
}
if my.info.caps&_CLIENT_PROTOCOL_41 == 0 {
panic(mysql.ErrOldProtocol)
}
}
func (my *Conn) auth() {
if my.Debug {
log.Printf("[%2d <-] Authentication packet", my.seq)
}
flags := uint32(
_CLIENT_PROTOCOL_41 |
_CLIENT_LONG_PASSWORD |
_CLIENT_LONG_FLAG |
_CLIENT_TRANSACTIONS |
_CLIENT_SECURE_CONN |
_CLIENT_LOCAL_FILES |
_CLIENT_MULTI_STATEMENTS |
_CLIENT_MULTI_RESULTS)
// Reset flags not supported by server
flags &= uint32(my.info.caps) | 0xffff0000
if my.plugin != string(my.info.plugin) {
my.plugin = string(my.info.plugin)
}
var scrPasswd []byte
switch my.plugin {
case "caching_sha2_password":
flags |= _CLIENT_PLUGIN_AUTH
scrPasswd = encryptedSHA256Passwd(my.passwd, my.info.scramble[:])
case "mysql_old_password":
my.oldPasswd()
return
default:
// mysql_native_password by default
scrPasswd = encryptedPasswd(my.passwd, my.info.scramble[:])
}
// encode length of the auth plugin data
var authRespLEIBuf [9]byte
authRespLEI := appendLengthEncodedInteger(authRespLEIBuf[:0], uint64(len(scrPasswd)))
if len(authRespLEI) > 1 {
// if the length can not be written in 1 byte, it must be written as a
// length encoded integer
flags |= _CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA
}
pay_len := 4 + 4 + 1 + 23 + len(my.user) + 1 + len(authRespLEI) + len(scrPasswd) + 21 + 1
if len(my.dbname) > 0 {
pay_len += len(my.dbname) + 1
flags |= _CLIENT_CONNECT_WITH_DB
}
pw := my.newPktWriter(pay_len)
pw.writeU32(flags)
pw.writeU32(uint32(my.max_pkt_size))
pw.writeByte(my.info.lang) // Charset number
pw.writeZeros(23) // Filler
pw.writeNTB([]byte(my.user)) // Username
pw.writeBin(scrPasswd) // Encrypted password
// write database name
if len(my.dbname) > 0 {
pw.writeNTB([]byte(my.dbname))
}
// write plugin name
if my.plugin != "" {
pw.writeNTB([]byte(my.plugin))
} else {
pw.writeNTB([]byte("mysql_native_password"))
}
return
}
func (my *Conn) authResponse() {
// Read Result Packet
authData, newPlugin := my.getAuthResult()
// handle auth plugin switch, if requested
if newPlugin != "" {
var scrPasswd []byte
if len(authData) >= 20 {
// old_password's len(authData) == 0
copy(my.info.scramble[:], authData[:20])
}
my.info.plugin = []byte(newPlugin)
my.plugin = newPlugin
switch my.plugin {
case "caching_sha2_password":
scrPasswd = encryptedSHA256Passwd(my.passwd, my.info.scramble[:])
case "mysql_old_password":
scrPasswd = encryptedOldPassword(my.passwd, my.info.scramble[:])
// append \0 after old_password
scrPasswd = append(scrPasswd, 0)
case "sha256_password":
// request public key from server
scrPasswd = []byte{1}
default: // mysql_native_password
scrPasswd = encryptedPasswd(my.passwd, my.info.scramble[:])
}
my.writeAuthSwitchPacket(scrPasswd)
// Read Result Packet
authData, newPlugin = my.getAuthResult()
// Do not allow to change the auth plugin more than once
if newPlugin != "" {
return
}
}
switch my.plugin {
// https://insidemysql.com/preparing-your-community-connector-for-mysql-8-part-2-sha256/
case "caching_sha2_password":
switch len(authData) {
case 0:
return // auth successful
case 1:
switch authData[0] {
case 3: // cachingSha2PasswordFastAuthSuccess
my.getResult(nil, nil)
case 4: // cachingSha2PasswordPerformFullAuthentication
// request public key from server
pw := my.newPktWriter(1)
pw.writeByte(2)
// parse public key
pr := my.newPktReader()
pr.skipN(1)
data := pr.readAll()
block, _ := pem.Decode(data)
pkix, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
panic(mysql.ErrAuthentication)
}
pubKey := pkix.(*rsa.PublicKey)
// send encrypted password
my.sendEncryptedPassword(my.info.scramble[:], pubKey)
my.getResult(nil, nil)
}
}
case "sha256_password":
switch len(authData) {
case 0:
return // auth successful
default:
// parse public key
block, _ := pem.Decode(authData)
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
panic(mysql.ErrAuthentication)
}
// send encrypted password
my.sendEncryptedPassword(my.info.scramble[:], pub.(*rsa.PublicKey))
my.getResult(nil, nil)
}
}
return
}
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
func (my *Conn) writeAuthSwitchPacket(scrPasswd []byte) {
pw := my.newPktWriter(len(scrPasswd))
pw.write(scrPasswd) // Encrypted password
return
}
func (my *Conn) sendEncryptedPassword(seed []byte, pub *rsa.PublicKey) {
enc, err := encryptPassword(my.passwd, seed, pub)
if err != nil {
panic(mysql.ErrAuthentication)
}
my.writeAuthSwitchPacket(enc)
}
func encryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte, error) {
plain := make([]byte, len(password)+1)
copy(plain, password)
for i := range plain {
j := i % len(seed)
plain[i] ^= seed[j]
}
sha1 := sha1.New()
return rsa.EncryptOAEP(sha1, rand.Reader, pub, plain, nil)
}
func (my *Conn) oldPasswd() {
if my.Debug {
log.Printf("[%2d <-] Password packet", my.seq)
}
scrPasswd := encryptedOldPassword(my.passwd, my.info.scramble[:])
pw := my.newPktWriter(len(scrPasswd) + 1)
pw.write(scrPasswd)
pw.writeByte(0)
}
// Package native is a thread unsafe engine for MyMySQL.
package native
import (
"bufio"
"fmt"
"io"
"net"
"reflect"
"strings"
"time"
"github.com/ziutek/mymysql/mysql"
)
type serverInfo struct {
prot_ver byte
serv_ver []byte
thr_id uint32
scramble [20]byte
caps uint32
lang byte
plugin []byte
}
// MySQL connection handler
type Conn struct {
proto string // Network protocol
laddr string // Local address
raddr string // Remote (server) address
user string // MySQL username
passwd string // MySQL password
dbname string // Database name
plugin string // authentication plugin
net_conn net.Conn // MySQL connection
rd *bufio.Reader
wr *bufio.Writer
info serverInfo // MySQL server information
seq byte // MySQL sequence number
unreaded_reply bool
init_cmds []string // MySQL commands/queries executed after connect
stmt_map map[uint32]*Stmt // For reprepare during reconnect
// Current status of MySQL server connection
status mysql.ConnStatus
// Maximum packet size that client can accept from server.
// Default 16*1024*1024-1. You may change it before connect.
max_pkt_size int
// Timeout for connect
timeout time.Duration
dialer mysql.Dialer
// Return only types accepted by godrv
narrowTypeSet bool
// Store full information about fields in result
fullFieldInfo bool
// Debug logging. You may change it at any time.
Debug bool
}
// Create new MySQL handler. The first three arguments are passed to net.Bind
// for create connection. user and passwd are for authentication. Optional db
// is database name (you may not specify it and use Use() method later).
func New(proto, laddr, raddr, user, passwd string, args ...string) mysql.Conn {
my := Conn{
proto: proto,
laddr: laddr,
raddr: raddr,
plugin: "mysql_native_password",
user: user,
passwd: passwd,
stmt_map: make(map[uint32]*Stmt),
max_pkt_size: 16*1024*1024 - 1,
timeout: 2 * time.Minute,
fullFieldInfo: true,
}
if len(args) == 1 {
my.dbname = args[0]
} else if len(args) == 2 {
my.dbname = args[0]
my.plugin = args[1]
} else if len(args) > 2 {
panic("mymy.New: too many arguments")
}
return &my
}
func (my *Conn) Credentials() (user, passwd string) {
return my.user, my.passwd
}
func (my *Conn) NarrowTypeSet(narrow bool) {
my.narrowTypeSet = narrow
}
func (my *Conn) FullFieldInfo(full bool) {
my.fullFieldInfo = full
}
// Creates new (not connected) connection using configuration from current
// connection.
func (my *Conn) Clone() mysql.Conn {
var c *Conn
if my.dbname == "" {
c = New(my.proto, my.laddr, my.raddr, my.user, my.passwd).(*Conn)
} else {
c = New(my.proto, my.laddr, my.raddr, my.user, my.passwd, my.dbname).(*Conn)
}
c.max_pkt_size = my.max_pkt_size
c.timeout = my.timeout
c.Debug = my.Debug
return c
}
// If new_size > 0 sets maximum packet size. Returns old size.
func (my *Conn) SetMaxPktSize(new_size int) int {
old_size := my.max_pkt_size
if new_size > 0 {
my.max_pkt_size = new_size
}
return old_size
}
// SetTimeout sets timeout for Connect and Reconnect
func (my *Conn) SetTimeout(timeout time.Duration) {
my.timeout = timeout
}
// NetConn return internall net.Conn
func (my *Conn) NetConn() net.Conn {
return my.net_conn
}
type timeoutError struct{}
func (e *timeoutError) Error() string { return "i/o timeout" }
func (e *timeoutError) Timeout() bool { return true }
func (e *timeoutError) Temporary() bool { return true }
type stringAddr struct {
net, addr string
}
func (a stringAddr) Network() string { return a.net }
func (a stringAddr) String() string { return a.addr }
var DefaultDialer mysql.Dialer = func(proto, laddr, raddr string,
timeout time.Duration) (net.Conn, error) {
if proto == "" {
proto = "unix"
if strings.IndexRune(raddr, ':') != -1 {
proto = "tcp"
}
}
// Make a connection
d := &net.Dialer{Timeout: timeout}
if laddr != "" {
var err error
switch proto {
case "tcp", "tcp4", "tcp6":
d.LocalAddr, err = net.ResolveTCPAddr(proto, laddr)
case "unix":
d.LocalAddr, err = net.ResolveTCPAddr(proto, laddr)
default:
err = net.UnknownNetworkError(proto)
}
if err != nil {
return nil, err
}
}
return d.Dial(proto, raddr)
}
func (my *Conn) SetDialer(d mysql.Dialer) {
my.dialer = d
}
func (my *Conn) connect() (err error) {
defer catchError(&err)
my.net_conn = nil
if my.dialer != nil {
my.net_conn, err = my.dialer(my.proto, my.laddr, my.raddr, my.timeout)
if err != nil {
my.net_conn = nil
return
}
}
if my.net_conn == nil {
my.net_conn, err = DefaultDialer(my.proto, my.laddr, my.raddr, my.timeout)
if err != nil {
my.net_conn = nil
return
}
}
my.rd = bufio.NewReader(my.net_conn)
my.wr = bufio.NewWriter(my.net_conn)
// Initialisation
my.init()
my.auth()
my.authResponse()
// Execute all registered commands
for _, cmd := range my.init_cmds {
// Send command
my.sendCmdStr(_COM_QUERY, cmd)
// Get command response
res := my.getResponse()
// Read and discard all result rows
row := res.MakeRow()
for res != nil {
// Only read rows if they exist
if !res.StatusOnly() {
//read each row in this set
for {
err = res.getRow(row)
if err == io.EOF {
break
} else if err != nil {
return
}
}
}
// Move to the next result
if res, err = res.nextResult(); err != nil {
return
}
}
}
return
}
// Establishes a connection with MySQL server version 4.1 or later.
func (my *Conn) Connect() (err error) {
if my.net_conn != nil {
return mysql.ErrAlredyConn
}
return my.connect()
}
// Check if connection is established
func (my *Conn) IsConnected() bool {
return my.net_conn != nil
}
func (my *Conn) closeConn() (err error) {
defer catchError(&err)
// Always close and invalidate connection, even if
// COM_QUIT returns an error
defer func() {
err = my.net_conn.Close()
my.net_conn = nil // Mark that we disconnect
}()
// Close the connection
my.sendCmd(_COM_QUIT)
return
}
// Close connection to the server
func (my *Conn) Close() (err error) {
if my.net_conn == nil {
return mysql.ErrNotConn
}
if my.unreaded_reply {
return mysql.ErrUnreadedReply
}
return my.closeConn()
}
// Close and reopen connection.
// Ignore unreaded rows, reprepare all prepared statements.
func (my *Conn) Reconnect() (err error) {
if my.net_conn != nil {
// Close connection, ignore all errors
my.closeConn()
}
// Reopen the connection.
if err = my.connect(); err != nil {
return
}
// Reprepare all prepared statements
var (
new_stmt *Stmt
new_map = make(map[uint32]*Stmt)
)
for _, stmt := range my.stmt_map {
new_stmt, err = my.prepare(stmt.sql)
if err != nil {
return
}
// Assume that fields set in new_stmt by prepare() are indentical to
// corresponding fields in stmt. Why can they be different?
stmt.id = new_stmt.id
stmt.rebind = true
new_map[stmt.id] = stmt
}
// Replace the stmt_map
my.stmt_map = new_map
return
}
// Change database
func (my *Conn) Use(dbname string) (err error) {
defer catchError(&err)
if my.net_conn == nil {
return mysql.ErrNotConn
}
if my.unreaded_reply {
return mysql.ErrUnreadedReply
}
// Send command
my.sendCmdStr(_COM_INIT_DB, dbname)
// Get server response
my.getResult(nil, nil)
// Save new database name if no errors
my.dbname = dbname
return
}
func (my *Conn) getResponse() (res *Result) {
res = my.getResult(nil, nil)
if res == nil {
panic(mysql.ErrBadResult)
}
my.unreaded_reply = !res.StatusOnly()
return
}
// Start new query.
//
// If you specify the parameters, the SQL string will be a result of
// fmt.Sprintf(sql, params...).
// You must get all result rows (if they exists) before next query.
func (my *Conn) Start(sql string, params ...interface{}) (res mysql.Result, err error) {
defer catchError(&err)
if my.net_conn == nil {
return nil, mysql.ErrNotConn
}
if my.unreaded_reply {
return nil, mysql.ErrUnreadedReply
}
if len(params) != 0 {
sql = fmt.Sprintf(sql, params...)
}
// Send query
my.sendCmdStr(_COM_QUERY, sql)
// Get command response
res = my.getResponse()
return
}
func (res *Result) getRow(row mysql.Row) (err error) {
defer catchError(&err)
if res.my.getResult(res, row) != nil {
return io.EOF
}
return nil
}
// Returns true if more results exixts. You don't have to call it before
// NextResult method (NextResult returns nil if there is no more results).
func (res *Result) MoreResults() bool {
return res.status&mysql.SERVER_MORE_RESULTS_EXISTS != 0
}
// Get the data row from server. This method reads one row of result set
// directly from network connection (without rows buffering on client side).
// Returns io.EOF if there is no more rows in current result set.
func (res *Result) ScanRow(row mysql.Row) error {
if row == nil {
return mysql.ErrRowLength
}
if res.eor_returned {
return mysql.ErrReadAfterEOR
}
if res.StatusOnly() {
// There is no fields in result (OK result)
res.eor_returned = true
return io.EOF
}
err := res.getRow(row)
if err == io.EOF {
res.eor_returned = true
if !res.MoreResults() {
res.my.unreaded_reply = false
}
}
return err
}
// Like ScanRow but allocates memory for every row.
// Returns nil row insted of io.EOF error.
func (res *Result) GetRow() (mysql.Row, error) {
return mysql.GetRow(res)
}
func (res *Result) nextResult() (next *Result, err error) {
defer catchError(&err)
if res.MoreResults() {
next = res.my.getResponse()
}
return
}
// This function is used when last query was the multi result query or
// procedure call. Returns the next result or nil if no more resuts exists.
//
// Statements within the procedure may produce unknown number of result sets.
// The final result from the procedure is a status result that includes no
// result set (Result.StatusOnly() == true) .
func (res *Result) NextResult() (mysql.Result, error) {
if !res.MoreResults() {
return nil, nil
}
res, err := res.nextResult()
return res, err
}
// Send MySQL PING to the server.
func (my *Conn) Ping() (err error) {
defer catchError(&err)
if my.net_conn == nil {
return mysql.ErrNotConn
}
if my.unreaded_reply {
return mysql.ErrUnreadedReply
}
// Send command
my.sendCmd(_COM_PING)
// Get server response
my.getResult(nil, nil)
return
}
func (my *Conn) prepare(sql string) (stmt *Stmt, err error) {
defer catchError(&err)
// Send command
my.sendCmdStr(_COM_STMT_PREPARE, sql)
// Get server response
stmt, ok := my.getPrepareResult(nil).(*Stmt)
if !ok {
return nil, mysql.ErrBadResult
}
if len(stmt.params) > 0 {
// Get param fields
my.getPrepareResult(stmt)
}
if len(stmt.fields) > 0 {
// Get column fields
my.getPrepareResult(stmt)
}
return
}
// Prepare server side statement. Return statement handler.
func (my *Conn) Prepare(sql string) (mysql.Stmt, error) {
if my.net_conn == nil {
return nil, mysql.ErrNotConn
}
if my.unreaded_reply {
return nil, mysql.ErrUnreadedReply
}
stmt, err := my.prepare(sql)
if err != nil {
return nil, err
}
// Connect statement with database handler
my.stmt_map[stmt.id] = stmt
// Save SQL for reconnect
stmt.sql = sql
return stmt, nil
}
// Bind input data for the parameter markers in the SQL statement that was
// passed to Prepare.
//
// params may be a parameter list (slice), a struct or a pointer to the struct.
// A struct field can by value or pointer to value. A parameter (slice element)
// can be value, pointer to value or pointer to pointer to value.
// Values may be of the folowind types: intXX, uintXX, floatXX, bool, []byte,
// Blob, string, Time, Date, Time, Timestamp, Raw.
func (stmt *Stmt) Bind(params ...interface{}) {
stmt.rebind = true
if len(params) == 1 {
// Check for struct binding
pval := reflect.ValueOf(params[0])
kind := pval.Kind()
if kind == reflect.Ptr {
// Dereference pointer
pval = pval.Elem()
kind = pval.Kind()
}
typ := pval.Type()
if kind == reflect.Struct &&
typ != timeType &&
typ != dateType &&
typ != timestampType &&
typ != rawType {
// We have a struct to bind
if pval.NumField() != stmt.param_count {
panic(mysql.ErrBindCount)
}
if !pval.CanAddr() {
// Make an addressable structure
v := reflect.New(pval.Type()).Elem()
v.Set(pval)
pval = v
}
for ii := 0; ii < stmt.param_count; ii++ {
stmt.params[ii] = bindValue(pval.Field(ii))
}
stmt.binded = true
return
}
}
// There isn't struct to bind
if len(params) != stmt.param_count {
panic(mysql.ErrBindCount)
}
for ii, par := range params {
pval := reflect.ValueOf(par)
if pval.IsValid() {
if pval.Kind() == reflect.Ptr {
// Dereference pointer - this value i addressable
pval = pval.Elem()
} else {
// Make an addressable value
v := reflect.New(pval.Type()).Elem()
v.Set(pval)
pval = v
}
}
stmt.params[ii] = bindValue(pval)
}
stmt.binded = true
}
// Execute prepared statement. If statement requires parameters you may bind
// them first or specify directly. After this command you may use GetRow to
// retrieve data.
func (stmt *Stmt) Run(params ...interface{}) (res mysql.Result, err error) {
defer catchError(&err)
if stmt.my.net_conn == nil {
return nil, mysql.ErrNotConn
}
if stmt.my.unreaded_reply {
return nil, mysql.ErrUnreadedReply
}
// Bind parameters if any
if len(params) != 0 {
stmt.Bind(params...)
} else if stmt.param_count != 0 && !stmt.binded {
panic(mysql.ErrBindCount)
}
// Send EXEC command with binded parameters
stmt.sendCmdExec()
// Get response
r := stmt.my.getResponse()
r.binary = true
res = r
return
}
// Destroy statement on server side. Client side handler is invalid after this
// command.
func (stmt *Stmt) Delete() (err error) {
defer catchError(&err)
if stmt.my.net_conn == nil {
return mysql.ErrNotConn
}
if stmt.my.unreaded_reply {
return mysql.ErrUnreadedReply
}
// Allways delete statement on client side, even if
// the command return an error.
defer func() {
// Delete statement from stmt_map
delete(stmt.my.stmt_map, stmt.id)
// Invalidate handler
*stmt = Stmt{}
}()
// Send command
stmt.my.sendCmdU32(_COM_STMT_CLOSE, stmt.id)
return
}
// Resets a prepared statement on server: data sent to the server, unbuffered
// result sets and current errors.
func (stmt *Stmt) Reset() (err error) {
defer catchError(&err)
if stmt.my.net_conn == nil {
return mysql.ErrNotConn
}
if stmt.my.unreaded_reply {
return mysql.ErrUnreadedReply
}
// Next exec must send type information. We set rebind flag regardless of
// whether the command succeeds or not.
stmt.rebind = true
// Send command
stmt.my.sendCmdU32(_COM_STMT_RESET, stmt.id)
// Get result
stmt.my.getResult(nil, nil)
return
}
// Send long data to MySQL server in chunks.
// You can call this method after Bind and before Exec. It can be called
// multiple times for one parameter to send TEXT or BLOB data in chunks.
//
// pnum - Parameter number to associate the data with.
//
// data - Data source string, []byte or io.Reader.
//
// pkt_size - It must be must be greater than 6 and less or equal to MySQL
// max_allowed_packet variable. You can obtain value of this variable
// using such query: SHOW variables WHERE Variable_name = 'max_allowed_packet'
// If data source is io.Reader then (pkt_size - 6) is size of a buffer that
// will be allocated for reading.
//
// If you have data source of type string or []byte in one piece you may
// properly set pkt_size and call this method once. If you have data in
// multiple pieces you can call this method multiple times. If data source is
// io.Reader you should properly set pkt_size. Data will be readed from
// io.Reader and send in pieces to the server until EOF.
func (stmt *Stmt) SendLongData(pnum int, data interface{}, pkt_size int) (err error) {
defer catchError(&err)
if stmt.my.net_conn == nil {
return mysql.ErrNotConn
}
if stmt.my.unreaded_reply {
return mysql.ErrUnreadedReply
}
if pnum < 0 || pnum >= stmt.param_count {
return mysql.ErrWrongParamNum
}
if pkt_size -= 6; pkt_size < 0 {
return mysql.ErrSmallPktSize
}
switch dd := data.(type) {
case io.Reader:
buf := make([]byte, pkt_size)
for {
nn, ee := dd.Read(buf)
if nn != 0 {
stmt.my.sendLongData(stmt.id, uint16(pnum), buf[0:nn])
}
if ee == io.EOF {
return
}
if ee != nil {
return ee
}
}
case []byte:
for len(dd) > pkt_size {
stmt.my.sendLongData(stmt.id, uint16(pnum), dd[0:pkt_size])
dd = dd[pkt_size:]
}
stmt.my.sendLongData(stmt.id, uint16(pnum), dd)
return
case string:
for len(dd) > pkt_size {
stmt.my.sendLongData(
stmt.id,
uint16(pnum),
[]byte(dd[0:pkt_size]),
)
dd = dd[pkt_size:]
}
stmt.my.sendLongData(stmt.id, uint16(pnum), []byte(dd))
return
}
return mysql.ErrUnkDataType
}
// Returns the thread ID of the current connection.
func (my *Conn) ThreadId() uint32 {
return my.info.thr_id
}
// Register MySQL command/query to be executed immediately after connecting to
// the server. You may register multiple commands. They will be executed in
// the order of registration. Yhis method is mainly useful for reconnect.
func (my *Conn) Register(sql string) {
my.init_cmds = append(my.init_cmds, sql)
}
// See mysql.Query
func (my *Conn) Query(sql string, params ...interface{}) ([]mysql.Row, mysql.Result, error) {
return mysql.Query(my, sql, params...)
}
// See mysql.QueryFirst
func (my *Conn) QueryFirst(sql string, params ...interface{}) (mysql.Row, mysql.Result, error) {
return mysql.QueryFirst(my, sql, params...)
}
// See mysql.QueryLast
func (my *Conn) QueryLast(sql string, params ...interface{}) (mysql.Row, mysql.Result, error) {
return mysql.QueryLast(my, sql, params...)
}
// See mysql.Exec
func (stmt *Stmt) Exec(params ...interface{}) ([]mysql.Row, mysql.Result, error) {
return mysql.Exec(stmt, params...)
}
// See mysql.ExecFirst
func (stmt *Stmt) ExecFirst(params ...interface{}) (mysql.Row, mysql.Result, error) {
return mysql.ExecFirst(stmt, params...)
}
// See mysql.ExecLast
func (stmt *Stmt) ExecLast(params ...interface{}) (mysql.Row, mysql.Result, error) {
return mysql.ExecLast(stmt, params...)
}
// See mysql.End
func (res *Result) End() error {
return mysql.End(res)
}
// See mysql.GetFirstRow
func (res *Result) GetFirstRow() (mysql.Row, error) {
return mysql.GetFirstRow(res)
}
// See mysql.GetLastRow
func (res *Result) GetLastRow() (mysql.Row, error) {
return mysql.GetLastRow(res)
}
// See mysql.GetRows
func (res *Result) GetRows() ([]mysql.Row, error) {
return mysql.GetRows(res)
}
// Escapes special characters in the txt, so it is safe to place returned string
// to Query method.
func (my *Conn) Escape(txt string) string {
return mysql.Escape(my, txt)
}
func (my *Conn) Status() mysql.ConnStatus {
return my.status
}
type Transaction struct {
*Conn
}
// Starts a new transaction
func (my *Conn) Begin() (mysql.Transaction, error) {
_, err := my.Start("START TRANSACTION")
return &Transaction{my}, err
}
// Commit a transaction
func (tr Transaction) Commit() error {
_, err := tr.Start("COMMIT")
tr.Conn = nil // Invalidate this transaction
return err
}
// Rollback a transaction
func (tr Transaction) Rollback() error {
_, err := tr.Start("ROLLBACK")
tr.Conn = nil // Invalidate this transaction
return err
}
func (tr Transaction) IsValid() bool {
return tr.Conn != nil
}
// Binds statement to the context of transaction. For native engine this is
// identity function.
func (tr Transaction) Do(st mysql.Stmt) mysql.Stmt {
if s, ok := st.(*Stmt); !ok || s.my != tr.Conn {
panic("Transaction and statement doesn't belong to the same connection")
}
return st
}
func init() {
mysql.New = New
}
package native
import (
"bufio"
"github.com/ziutek/mymysql/mysql"
"io"
"io/ioutil"
)
type pktReader struct {
rd *bufio.Reader
seq *byte
remain int
last bool
buf [12]byte
ibuf [3]byte
}
func (my *Conn) newPktReader() *pktReader {
return &pktReader{rd: my.rd, seq: &my.seq}
}
func (pr *pktReader) readHeader() {
// Read next packet header
buf := pr.ibuf[:]
for {
n, err := pr.rd.Read(buf)
if err != nil {
panic(err)
}
buf = buf[n:]
if len(buf) == 0 {
break
}
}
pr.remain = int(DecodeU24(pr.ibuf[:]))
seq, err := pr.rd.ReadByte()
if err != nil {
panic(err)
}
// Chceck sequence number
if *pr.seq != seq {
panic(mysql.ErrSeq)
}
*pr.seq++
// Last packet?
pr.last = (pr.remain != 0xffffff)
}
func (pr *pktReader) readFull(buf []byte) {
for len(buf) > 0 {
if pr.remain == 0 {
if pr.last {
// No more packets
panic(io.EOF)
}
pr.readHeader()
}
n := len(buf)
if n > pr.remain {
n = pr.remain
}
n, err := pr.rd.Read(buf[:n])
pr.remain -= n
if err != nil {
panic(err)
}
buf = buf[n:]
}
return
}
func (pr *pktReader) readByte() byte {
if pr.remain == 0 {
if pr.last {
// No more packets
panic(io.EOF)
}
pr.readHeader()
}
b, err := pr.rd.ReadByte()
if err != nil {
panic(err)
}
pr.remain--
return b
}
func (pr *pktReader) readAll() (buf []byte) {
m := 0
for {
if pr.remain == 0 {
if pr.last {
break
}
pr.readHeader()
}
new_buf := make([]byte, m+pr.remain)
copy(new_buf, buf)
buf = new_buf
n, err := pr.rd.Read(buf[m:])
pr.remain -= n
m += n
if err != nil {
panic(err)
}
}
return
}
func (pr *pktReader) skipAll() {
for {
if pr.remain == 0 {
if pr.last {
break
}
pr.readHeader()
}
n, err := io.CopyN(ioutil.Discard, pr.rd, int64(pr.remain))
pr.remain -= int(n)
if err != nil {
panic(err)
}
}
return
}
func (pr *pktReader) skipN(n int) {
for n > 0 {
if pr.remain == 0 {
if pr.last {
panic(io.EOF)
}
pr.readHeader()
}
m := int64(n)
if n > pr.remain {
m = int64(pr.remain)
}
m, err := io.CopyN(ioutil.Discard, pr.rd, m)
pr.remain -= int(m)
n -= int(m)
if err != nil {
panic(err)
}
}
return
}
func (pr *pktReader) unreadByte() {
if err := pr.rd.UnreadByte(); err != nil {
panic(err)
}
pr.remain++
}
func (pr *pktReader) eof() bool {
return pr.remain == 0 && pr.last
}
func (pr *pktReader) checkEof() {
if !pr.eof() {
panic(mysql.ErrPktLong)
}
}
type pktWriter struct {
wr *bufio.Writer
seq *byte
remain int
to_write int
last bool
buf [23]byte
ibuf [3]byte
}
func (my *Conn) newPktWriter(to_write int) *pktWriter {
return &pktWriter{wr: my.wr, seq: &my.seq, to_write: to_write}
}
func (pw *pktWriter) writeHeader(l int) {
buf := pw.ibuf[:]
EncodeU24(buf, uint32(l))
if _, err := pw.wr.Write(buf); err != nil {
panic(err)
}
if err := pw.wr.WriteByte(*pw.seq); err != nil {
panic(err)
}
// Update sequence number
*pw.seq++
}
func (pw *pktWriter) write(buf []byte) {
if len(buf) == 0 {
return
}
var nn int
for len(buf) != 0 {
if pw.remain == 0 {
if pw.to_write == 0 {
panic("too many data for write as packet")
}
if pw.to_write >= 0xffffff {
pw.remain = 0xffffff
} else {
pw.remain = pw.to_write
pw.last = true
}
pw.to_write -= pw.remain
pw.writeHeader(pw.remain)
}
nn = len(buf)
if nn > pw.remain {
nn = pw.remain
}
var err error
nn, err = pw.wr.Write(buf[0:nn])
pw.remain -= nn
if err != nil {
panic(err)
}
buf = buf[nn:]
}
if pw.remain+pw.to_write == 0 {
if !pw.last {
// Write header for empty packet
pw.writeHeader(0)
}
// Flush bufio buffers
if err := pw.wr.Flush(); err != nil {
panic(err)
}
}
return
}
func (pw *pktWriter) writeByte(b byte) {
pw.buf[0] = b
pw.write(pw.buf[:1])
}
// n should be <= 23
func (pw *pktWriter) writeZeros(n int) {
buf := pw.buf[:n]
for i := range buf {
buf[i] = 0
}
pw.write(buf)
}
package native
import (
"github.com/ziutek/mymysql/mysql"
"math"
"reflect"
"time"
)
type paramValue struct {
typ uint16
addr reflect.Value
raw bool
length int // >=0 - length of value, <0 - unknown length
}
func (val *paramValue) Len() int {
if !val.addr.IsValid() {
// Invalid Value was binded
return 0
}
// val.addr always points to the pointer - lets dereference it
v := val.addr.Elem()
if v.IsNil() {
// Binded Ptr Value is nil
return 0
}
v = v.Elem()
if val.length >= 0 {
return val.length
}
switch val.typ {
case MYSQL_TYPE_STRING:
return lenStr(v.String())
case MYSQL_TYPE_DATE:
return lenDate(v.Interface().(mysql.Date))
case MYSQL_TYPE_TIMESTAMP:
return lenTime(v.Interface().(mysql.Timestamp).Time)
case MYSQL_TYPE_DATETIME:
return lenTime(v.Interface().(time.Time))
case MYSQL_TYPE_TIME:
return lenDuration(v.Interface().(time.Duration))
case MYSQL_TYPE_TINY: // val.length < 0 so this is bool
return 1
}
// MYSQL_TYPE_VAR_STRING, MYSQL_TYPE_BLOB and type of Raw value
return lenBin(v.Bytes())
}
func (pw *pktWriter) writeValue(val *paramValue) {
if !val.addr.IsValid() {
// Invalid Value was binded
return
}
// val.addr always points to the pointer - lets dereference it
v := val.addr.Elem()
if v.IsNil() {
// Binded Ptr Value is nil
return
}
v = v.Elem()
if val.raw || val.typ == MYSQL_TYPE_VAR_STRING ||
val.typ == MYSQL_TYPE_BLOB {
pw.writeBin(v.Bytes())
return
}
// We don't need unsigned bit to check type
unsign := (val.typ & MYSQL_UNSIGNED_MASK) != 0
switch val.typ & ^MYSQL_UNSIGNED_MASK {
case MYSQL_TYPE_NULL:
// Don't write null values
case MYSQL_TYPE_STRING:
pw.writeBin([]byte(v.String()))
case MYSQL_TYPE_LONG:
i := v.Interface()
if unsign {
l, ok := i.(uint32)
if !ok {
l = uint32(i.(uint))
}
pw.writeU32(l)
} else {
l, ok := i.(int32)
if !ok {
l = int32(i.(int))
}
pw.writeU32(uint32(l))
}
case MYSQL_TYPE_FLOAT:
pw.writeU32(math.Float32bits(v.Interface().(float32)))
case MYSQL_TYPE_SHORT:
if unsign {
pw.writeU16(v.Interface().(uint16))
} else {
pw.writeU16(uint16(v.Interface().(int16)))
}
case MYSQL_TYPE_TINY:
if val.length == -1 {
// Translate bool value to MySQL tiny
if v.Bool() {
pw.writeByte(1)
} else {
pw.writeByte(0)
}
} else {
if unsign {
pw.writeByte(v.Interface().(uint8))
} else {
pw.writeByte(uint8(v.Interface().(int8)))
}
}
case MYSQL_TYPE_LONGLONG:
i := v.Interface()
if unsign {
l, ok := i.(uint64)
if !ok {
l = uint64(i.(uint))
}
pw.writeU64(l)
} else {
l, ok := i.(int64)
if !ok {
l = int64(i.(int))
}
pw.writeU64(uint64(l))
}
case MYSQL_TYPE_DOUBLE:
pw.writeU64(math.Float64bits(v.Interface().(float64)))
case MYSQL_TYPE_DATE:
pw.writeDate(v.Interface().(mysql.Date))
case MYSQL_TYPE_TIMESTAMP:
pw.writeTime(v.Interface().(mysql.Timestamp).Time)
case MYSQL_TYPE_DATETIME:
pw.writeTime(v.Interface().(time.Time))
case MYSQL_TYPE_TIME:
pw.writeDuration(v.Interface().(time.Duration))
default:
panic(mysql.ErrBindUnkType)
}
return
}
// encodes a uint64 value and appends it to the given bytes slice
func appendLengthEncodedInteger(b []byte, n uint64) []byte {
switch {
case n <= 250:
return append(b, byte(n))
case n <= 0xffff:
return append(b, 0xfc, byte(n), byte(n>>8))
case n <= 0xffffff:
return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16))
}
return append(b, 0xfe, byte(n), byte(n>>8), byte(n>>16), byte(n>>24),
byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56))
}
\ No newline at end of file
package native
import (
"crypto/sha1"
"crypto/sha256"
"math"
)
// Borrowed from GoMySQL
// SHA1(SHA1(SHA1(password)), scramble) XOR SHA1(password)
func encryptedPasswd(password string, scramble []byte) (out []byte) {
if len(password) == 0 {
return
}
// stage1_hash = SHA1(password)
// SHA1 encode
crypt := sha1.New()
crypt.Write([]byte(password))
stg1Hash := crypt.Sum(nil)
// token = SHA1(SHA1(stage1_hash), scramble) XOR stage1_hash
// SHA1 encode again
crypt.Reset()
crypt.Write(stg1Hash)
stg2Hash := crypt.Sum(nil)
// SHA1 2nd hash and scramble
crypt.Reset()
crypt.Write(scramble)
crypt.Write(stg2Hash)
stg3Hash := crypt.Sum(nil)
// XOR with first hash
out = make([]byte, len(scramble))
for ii := range scramble {
out[ii] = stg3Hash[ii] ^ stg1Hash[ii]
}
return
}
// Hash password using MySQL 8+ method (SHA256)
func encryptedSHA256Passwd(password string, scramble []byte) []byte {
if len(password) == 0 {
return nil
}
// XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble))
crypt := sha256.New()
crypt.Write([]byte(password))
message1 := crypt.Sum(nil)
crypt.Reset()
crypt.Write(message1)
message1Hash := crypt.Sum(nil)
crypt.Reset()
crypt.Write(message1Hash)
crypt.Write(scramble)
message2 := crypt.Sum(nil)
for i := range message1 {
message1[i] ^= message2[i]
}
return message1
}
// Old password handling based on translating to Go some functions from
// libmysql
// The main idea is that no password are sent between client & server on
// connection and that no password are saved in mysql in a decodable form.
//
// On connection a random string is generated and sent to the client.
// The client generates a new string with a random generator inited with
// the hash values from the password and the sent string.
// This 'check' string is sent to the server where it is compared with
// a string generated from the stored hash_value of the password and the
// random string.
// libmysql/my_rnd.c
type myRnd struct {
seed1, seed2 uint32
}
const myRndMaxVal = 0x3FFFFFFF
func newMyRnd(seed1, seed2 uint32) *myRnd {
r := new(myRnd)
r.seed1 = seed1 % myRndMaxVal
r.seed2 = seed2 % myRndMaxVal
return r
}
func (r *myRnd) Float64() float64 {
r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
return float64(r.seed1) / myRndMaxVal
}
// libmysql/password.c
func pwHash(password []byte) (result [2]uint32) {
var nr, add, nr2, tmp uint32
nr, add, nr2 = 1345345333, 7, 0x12345671
for _, c := range password {
if c == ' ' || c == '\t' {
continue // skip space in password
}
tmp = uint32(c)
nr ^= (((nr & 63) + add) * tmp) + (nr << 8)
nr2 += (nr2 << 8) ^ nr
add += tmp
}
result[0] = nr & ((1 << 31) - 1) // Don't use sign bit (str2int)
result[1] = nr2 & ((1 << 31) - 1)
return
}
func encryptedOldPassword(password string, scramble []byte) []byte {
if len(password) == 0 {
return nil
}
scramble = scramble[:8]
hashPw := pwHash([]byte(password))
hashSc := pwHash(scramble)
r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
var out [8]byte
for i := range out {
out[i] = byte(math.Floor(r.Float64()*31) + 64)
}
extra := byte(math.Floor(r.Float64() * 31))
for i := range out {
out[i] ^= extra
}
return out[:]
}
package native
import (
"github.com/ziutek/mymysql/mysql"
"log"
)
type Stmt struct {
my *Conn
id uint32
sql string // For reprepare during reconnect
params []paramValue // Parameters binding
rebind bool
binded bool
fields []*mysql.Field
field_count int
param_count int
warning_count int
status mysql.ConnStatus
null_bitmap []byte
}
func (stmt *Stmt) Fields() []*mysql.Field {
return stmt.fields
}
func (stmt *Stmt) NumParam() int {
return stmt.param_count
}
func (stmt *Stmt) WarnCount() int {
return stmt.warning_count
}
func (stmt *Stmt) sendCmdExec() {
// Calculate packet length and NULL bitmap
pkt_len := 1 + 4 + 1 + 4 + 1 + len(stmt.null_bitmap)
for ii := range stmt.null_bitmap {
stmt.null_bitmap[ii] = 0
}
for ii, param := range stmt.params {
par_len := param.Len()
pkt_len += par_len
if par_len == 0 {
null_byte := ii >> 3
null_mask := byte(1) << uint(ii-(null_byte<<3))
stmt.null_bitmap[null_byte] |= null_mask
}
}
if stmt.rebind {
pkt_len += stmt.param_count * 2
}
// Reset sequence number
stmt.my.seq = 0
// Packet sending
pw := stmt.my.newPktWriter(pkt_len)
pw.writeByte(_COM_STMT_EXECUTE)
pw.writeU32(stmt.id)
pw.writeByte(0) // flags = CURSOR_TYPE_NO_CURSOR
pw.writeU32(1) // iteration_count
pw.write(stmt.null_bitmap)
if stmt.rebind {
pw.writeByte(1)
// Types
for _, param := range stmt.params {
pw.writeU16(param.typ)
}
} else {
pw.writeByte(0)
}
// Values
for i := range stmt.params {
pw.writeValue(&stmt.params[i])
}
if stmt.my.Debug {
log.Printf("[%2d <-] Exec command packet: len=%d, null_bitmap=%v, rebind=%t",
stmt.my.seq-1, pkt_len, stmt.null_bitmap, stmt.rebind)
}
// Mark that we sended information about binded types
stmt.rebind = false
}
func (my *Conn) getPrepareResult(stmt *Stmt) interface{} {
loop:
pr := my.newPktReader() // New reader for next packet
pkt0 := pr.readByte()
//log.Println("pkt0:", pkt0, "stmt:", stmt)
if pkt0 == 255 {
// Error packet
my.getErrorPacket(pr)
}
if stmt == nil {
if pkt0 == 0 {
// OK packet
return my.getPrepareOkPacket(pr)
}
} else {
unreaded_params := (stmt.param_count < len(stmt.params))
switch {
case pkt0 == 254:
// EOF packet
stmt.warning_count, stmt.status = my.getEofPacket(pr)
stmt.my.status = stmt.status
return stmt
case pkt0 > 0 && pkt0 < 251 && (stmt.field_count < len(stmt.fields) ||
unreaded_params):
// Field packet
if unreaded_params {
// Read and ignore parameter field. Sentence from MySQL source:
/* skip parameters data: we don't support it yet */
pr.skipAll()
// Increment param_count count
stmt.param_count++
} else {
field := my.getFieldPacket(pr)
stmt.fields[stmt.field_count] = field
// Increment field count
stmt.field_count++
}
// Read next packet
goto loop
}
}
panic(mysql.ErrUnkResultPkt)
}
func (my *Conn) getPrepareOkPacket(pr *pktReader) (stmt *Stmt) {
if my.Debug {
log.Printf("[%2d ->] Perpared OK packet:", my.seq-1)
}
stmt = new(Stmt)
stmt.my = my
// First byte was readed by getPrepRes
stmt.id = pr.readU32()
stmt.fields = make([]*mysql.Field, int(pr.readU16())) // FieldCount
pl := int(pr.readU16()) // ParamCount
if pl > 0 {
stmt.params = make([]paramValue, pl)
stmt.null_bitmap = make([]byte, (pl+7)>>3)
}
pr.skipN(1)
stmt.warning_count = int(pr.readU16())
pr.checkEof()
if my.Debug {
log.Printf(tab8s+"ID=0x%x ParamCount=%d FieldsCount=%d WarnCount=%d",
stmt.id, len(stmt.params), len(stmt.fields), stmt.warning_count,
)
}
return
}
package native
import (
"bytes"
"errors"
"github.com/ziutek/mymysql/mysql"
"log"
"math"
"strconv"
)
type Result struct {
my *Conn
status_only bool // true if result doesn't contain result set
binary bool // Binary result expected
field_count int
fields []*mysql.Field // Fields table
fc_map map[string]int // Maps field name to column number
message []byte
affected_rows uint64
// Primary key value (useful for AUTO_INCREMENT primary keys)
insert_id uint64
// Number of warinigs during command execution
// You can use the SHOW WARNINGS query for details.
warning_count int
// MySQL server status immediately after the query execution
status mysql.ConnStatus
// Seted by GetRow if it returns nil row
eor_returned bool
}
// Returns true if this is status result that includes no result set
func (res *Result) StatusOnly() bool {
return res.status_only
}
// Returns a table containing descriptions of the columns
func (res *Result) Fields() []*mysql.Field {
return res.fields
}
// Returns index for given name or -1 if field of that name doesn't exist
func (res *Result) Map(field_name string) int {
if fi, ok := res.fc_map[field_name]; ok {
return fi
}
return -1
}
func (res *Result) Message() string {
return string(res.message)
}
func (res *Result) AffectedRows() uint64 {
return res.affected_rows
}
func (res *Result) InsertId() uint64 {
return res.insert_id
}
func (res *Result) WarnCount() int {
return res.warning_count
}
func (res *Result) MakeRow() mysql.Row {
return make(mysql.Row, res.field_count)
}
// getAuthResult After sending login request
// use this func get server return packet
func (my *Conn) getAuthResult() ([]byte, string) {
pr := my.newPktReader()
pkt := pr.readAll()
pkt0 := pkt[0]
// packet indicator
switch pkt0 {
case 0: // OK
return nil, ""
case 1: // AuthMoreData
return pkt[1:], ""
case 254: // EOF
if len(pkt) == 1 {
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
return nil, "mysql_old_password"
}
pluginEndIndex := bytes.IndexByte(pkt, 0x00)
if pluginEndIndex < 0 {
return nil, ""
}
plugin := string(pkt[1:pluginEndIndex])
authData := pkt[pluginEndIndex+1:]
return authData, plugin
case 255: // Error packet
panic(mysql.ErrAuthentication)
return nil, ""
default: // Error otherwise
panic(mysql.ErrUnkResultPkt)
return nil, ""
}
}
func (my *Conn) getResult(res *Result, row mysql.Row) *Result {
loop:
pr := my.newPktReader() // New reader for next packet
pkt0 := pr.readByte()
if pkt0 == 255 {
// Error packet
my.getErrorPacket(pr)
}
if res == nil {
switch {
case pkt0 == 0:
// OK packet
return my.getOkPacket(pr)
case pkt0 > 0 && pkt0 < 251:
// Result set header packet
res = my.getResSetHeadPacket(pr)
// Read next packet
goto loop
case pkt0 == 251:
// Load infile response
// Handle response
goto loop
case pkt0 == 254:
// EOF packet (without body)
return nil
}
} else {
switch {
case pkt0 == 254:
// EOF packet
res.warning_count, res.status = my.getEofPacket(pr)
my.status = res.status
return res
case pkt0 > 0 && pkt0 < 251 && res.field_count < len(res.fields):
// Field packet
field := my.getFieldPacket(pr)
res.fields[res.field_count] = field
res.fc_map[field.Name] = res.field_count
// Increment field count
res.field_count++
// Read next packet
goto loop
case pkt0 < 254 && res.field_count == len(res.fields):
// Row Data Packet
if len(row) != res.field_count {
panic(mysql.ErrRowLength)
}
if res.binary {
my.getBinRowPacket(pr, res, row)
} else {
my.getTextRowPacket(pr, res, row)
}
return nil
}
}
panic(mysql.ErrUnkResultPkt)
}
func (my *Conn) getOkPacket(pr *pktReader) (res *Result) {
if my.Debug {
log.Printf("[%2d ->] OK packet:", my.seq-1)
}
res = new(Result)
res.status_only = true
res.my = my
// First byte was readed by getResult
res.affected_rows = pr.readLCB()
res.insert_id = pr.readLCB()
res.status = mysql.ConnStatus(pr.readU16())
my.status = res.status
res.warning_count = int(pr.readU16())
res.message = pr.readAll()
pr.checkEof()
if my.Debug {
log.Printf(tab8s+"AffectedRows=%d InsertId=0x%x Status=0x%x "+
"WarningCount=%d Message=\"%s\"", res.affected_rows, res.insert_id,
res.status, res.warning_count, res.message,
)
}
return
}
func (my *Conn) getErrorPacket(pr *pktReader) {
if my.Debug {
log.Printf("[%2d ->] Error packet:", my.seq-1)
}
var err mysql.Error
err.Code = pr.readU16()
if pr.readByte() != '#' {
panic(mysql.ErrPkt)
}
pr.skipN(5)
err.Msg = pr.readAll()
pr.checkEof()
if my.Debug {
log.Printf(tab8s+"code=0x%x msg=\"%s\"", err.Code, err.Msg)
}
panic(&err)
}
func (my *Conn) getEofPacket(pr *pktReader) (warn_count int, status mysql.ConnStatus) {
if my.Debug {
if pr.eof() {
log.Printf("[%2d ->] EOF packet without body", my.seq-1)
} else {
log.Printf("[%2d ->] EOF packet:", my.seq-1)
}
}
if pr.eof() {
return
}
warn_count = int(pr.readU16())
if pr.eof() {
return
}
status = mysql.ConnStatus(pr.readU16())
pr.checkEof()
if my.Debug {
log.Printf(tab8s+"WarningCount=%d Status=0x%x", warn_count, status)
}
return
}
func (my *Conn) getResSetHeadPacket(pr *pktReader) (res *Result) {
if my.Debug {
log.Printf("[%2d ->] Result set header packet:", my.seq-1)
}
pr.unreadByte()
field_count := int(pr.readLCB())
pr.checkEof()
res = &Result{
my: my,
fields: make([]*mysql.Field, field_count),
fc_map: make(map[string]int),
}
if my.Debug {
log.Printf(tab8s+"FieldCount=%d", field_count)
}
return
}
func (my *Conn) getFieldPacket(pr *pktReader) (field *mysql.Field) {
if my.Debug {
log.Printf("[%2d ->] Field packet:", my.seq-1)
}
pr.unreadByte()
field = new(mysql.Field)
if my.fullFieldInfo {
field.Catalog = string(pr.readBin())
field.Db = string(pr.readBin())
field.Table = string(pr.readBin())
field.OrgTable = string(pr.readBin())
} else {
pr.skipBin()
pr.skipBin()
pr.skipBin()
pr.skipBin()
}
field.Name = string(pr.readBin())
if my.fullFieldInfo {
field.OrgName = string(pr.readBin())
} else {
pr.skipBin()
}
pr.skipN(1 + 2)
//field.Charset= pr.readU16()
field.DispLen = pr.readU32()
field.Type = pr.readByte()
field.Flags = pr.readU16()
field.Scale = pr.readByte()
pr.skipN(2)
pr.checkEof()
if my.Debug {
log.Printf(tab8s+"Name=\"%s\" Type=0x%x", field.Name, field.Type)
}
return
}
func (my *Conn) getTextRowPacket(pr *pktReader, res *Result, row mysql.Row) {
if my.Debug {
log.Printf("[%2d ->] Text row data packet", my.seq-1)
}
pr.unreadByte()
for ii := 0; ii < res.field_count; ii++ {
bin, null := pr.readNullBin()
if null {
row[ii] = nil
} else {
row[ii] = bin
}
}
pr.checkEof()
}
func (my *Conn) getBinRowPacket(pr *pktReader, res *Result, row mysql.Row) {
if my.Debug {
log.Printf("[%2d ->] Binary row data packet", my.seq-1)
}
// First byte was readed by getResult
null_bitmap := make([]byte, (res.field_count+7+2)>>3)
pr.readFull(null_bitmap)
for ii, field := range res.fields {
null_byte := (ii + 2) >> 3
null_mask := byte(1) << uint(2+ii-(null_byte<<3))
if null_bitmap[null_byte]&null_mask != 0 {
// Null field
row[ii] = nil
continue
}
unsigned := (field.Flags & _FLAG_UNSIGNED) != 0
if my.narrowTypeSet {
row[ii] = readValueNarrow(pr, field.Type, unsigned)
} else {
row[ii] = readValue(pr, field.Type, unsigned)
}
}
}
func readValue(pr *pktReader, typ byte, unsigned bool) interface{} {
switch typ {
case MYSQL_TYPE_STRING, MYSQL_TYPE_VAR_STRING, MYSQL_TYPE_VARCHAR,
MYSQL_TYPE_BIT, MYSQL_TYPE_BLOB, MYSQL_TYPE_TINY_BLOB,
MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_SET,
MYSQL_TYPE_ENUM, MYSQL_TYPE_GEOMETRY:
return pr.readBin()
case MYSQL_TYPE_TINY:
if unsigned {
return pr.readByte()
} else {
return int8(pr.readByte())
}
case MYSQL_TYPE_SHORT, MYSQL_TYPE_YEAR:
if unsigned {
return pr.readU16()
} else {
return int16(pr.readU16())
}
case MYSQL_TYPE_LONG, MYSQL_TYPE_INT24:
if unsigned {
return pr.readU32()
} else {
return int32(pr.readU32())
}
case MYSQL_TYPE_LONGLONG:
if unsigned {
return pr.readU64()
} else {
return int64(pr.readU64())
}
case MYSQL_TYPE_FLOAT:
return math.Float32frombits(pr.readU32())
case MYSQL_TYPE_DOUBLE:
return math.Float64frombits(pr.readU64())
case MYSQL_TYPE_DECIMAL, MYSQL_TYPE_NEWDECIMAL:
dec := string(pr.readBin())
r, err := strconv.ParseFloat(dec, 64)
if err != nil {
panic(errors.New("MySQL server returned wrong decimal value: " + dec))
}
return r
case MYSQL_TYPE_DATE, MYSQL_TYPE_NEWDATE:
return pr.readDate()
case MYSQL_TYPE_DATETIME, MYSQL_TYPE_TIMESTAMP:
return pr.readTime()
case MYSQL_TYPE_TIME:
return pr.readDuration()
}
panic(mysql.ErrUnkMySQLType)
}
func readValueNarrow(pr *pktReader, typ byte, unsigned bool) interface{} {
switch typ {
case MYSQL_TYPE_STRING, MYSQL_TYPE_VAR_STRING, MYSQL_TYPE_VARCHAR,
MYSQL_TYPE_BIT, MYSQL_TYPE_BLOB, MYSQL_TYPE_TINY_BLOB,
MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_SET,
MYSQL_TYPE_ENUM, MYSQL_TYPE_GEOMETRY:
return pr.readBin()
case MYSQL_TYPE_TINY:
if unsigned {
return int64(pr.readByte())
}
return int64(int8(pr.readByte()))
case MYSQL_TYPE_SHORT, MYSQL_TYPE_YEAR:
if unsigned {
return int64(pr.readU16())
}
return int64(int16(pr.readU16()))
case MYSQL_TYPE_LONG, MYSQL_TYPE_INT24:
if unsigned {
return int64(pr.readU32())
}
return int64(int32(pr.readU32()))
case MYSQL_TYPE_LONGLONG:
v := pr.readU64()
if unsigned && v > math.MaxInt64 {
panic(errors.New("Value to large for int64 type"))
}
return int64(v)
case MYSQL_TYPE_FLOAT:
return float64(math.Float32frombits(pr.readU32()))
case MYSQL_TYPE_DOUBLE:
return math.Float64frombits(pr.readU64())
case MYSQL_TYPE_DECIMAL, MYSQL_TYPE_NEWDECIMAL:
dec := string(pr.readBin())
r, err := strconv.ParseFloat(dec, 64)
if err != nil {
panic("MySQL server returned wrong decimal value: " + dec)
}
return r
case MYSQL_TYPE_DATETIME, MYSQL_TYPE_TIMESTAMP, MYSQL_TYPE_DATE, MYSQL_TYPE_NEWDATE:
return pr.readTime()
case MYSQL_TYPE_TIME:
return int64(pr.readDuration())
}
panic(mysql.ErrUnkMySQLType)
}
package native
import (
"github.com/ziutek/mymysql/mysql"
"time"
"unsafe"
)
type paramValue struct {
typ uint16
addr unsafe.Pointer
raw bool
length int // >=0 - length of value, <0 - unknown length
}
func (pv *paramValue) SetAddr(addr uintptr) {
pv.addr = unsafe.Pointer(addr)
}
func (val *paramValue) Len() int {
if val.addr == nil {
// Invalid Value was binded
return 0
}
// val.addr always points to the pointer - lets dereference it
ptr := *(*unsafe.Pointer)(val.addr)
if ptr == nil {
// Binded Ptr Value is nil
return 0
}
if val.length >= 0 {
return val.length
}
switch val.typ {
case MYSQL_TYPE_STRING:
return lenStr(*(*string)(ptr))
case MYSQL_TYPE_DATE:
return lenDate(*(*mysql.Date)(ptr))
case MYSQL_TYPE_TIMESTAMP, MYSQL_TYPE_DATETIME:
return lenTime(*(*time.Time)(ptr))
case MYSQL_TYPE_TIME:
return lenDuration(*(*time.Duration)(ptr))
case MYSQL_TYPE_TINY: // val.length < 0 so this is bool
return 1
}
// MYSQL_TYPE_VAR_STRING, MYSQL_TYPE_BLOB and type of Raw value
return lenBin(*(*[]byte)(ptr))
}
func (pw *pktWriter) writeValue(val *paramValue) {
if val.addr == nil {
// Invalid Value was binded
return
}
// val.addr always points to the pointer - lets dereference it
ptr := *(*unsafe.Pointer)(val.addr)
if ptr == nil {
// Binded Ptr Value is nil
return
}
if val.raw || val.typ == MYSQL_TYPE_VAR_STRING ||
val.typ == MYSQL_TYPE_BLOB {
pw.writeBin(*(*[]byte)(ptr))
return
}
// We don't need unsigned bit to check type
switch val.typ & ^MYSQL_UNSIGNED_MASK {
case MYSQL_TYPE_NULL:
// Don't write null values
case MYSQL_TYPE_STRING:
s := *(*string)(ptr)
pw.writeBin([]byte(s))
case MYSQL_TYPE_LONG, MYSQL_TYPE_FLOAT:
pw.writeU32(*(*uint32)(ptr))
case MYSQL_TYPE_SHORT:
pw.writeU16(*(*uint16)(ptr))
case MYSQL_TYPE_TINY:
if val.length == -1 {
// Translate bool value to MySQL tiny
if *(*bool)(ptr) {
pw.writeByte(1)
} else {
pw.writeByte(0)
}
} else {
pw.writeByte(*(*byte)(ptr))
}
case MYSQL_TYPE_LONGLONG, MYSQL_TYPE_DOUBLE:
pw.writeU64(*(*uint64)(ptr))
case MYSQL_TYPE_DATE:
pw.writeDate(*(*mysql.Date)(ptr))
case MYSQL_TYPE_TIMESTAMP, MYSQL_TYPE_DATETIME:
pw.writeTime(*(*time.Time)(ptr))
case MYSQL_TYPE_TIME:
pw.writeDuration(*(*time.Duration)(ptr))
default:
panic(mysql.ErrBindUnkType)
}
return
}
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
// Copyright 2013 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
Package cloudsql exposes access to Google Cloud SQL databases.
This package does not work in App Engine "flexible environment".
This package is intended for MySQL drivers to make App Engine-specific
connections. Applications should use this package through database/sql:
Select a pure Go MySQL driver that supports this package, and use sql.Open
with protocol "cloudsql" and an address of the Cloud SQL instance.
A Go MySQL driver that has been tested to work well with Cloud SQL
is the go-sql-driver:
import "database/sql"
import _ "github.com/go-sql-driver/mysql"
db, err := sql.Open("mysql", "user@cloudsql(project-id:instance-name)/dbname")
Another driver that works well with Cloud SQL is the mymysql driver:
import "database/sql"
import _ "github.com/ziutek/mymysql/godrv"
db, err := sql.Open("mymysql", "cloudsql:instance-name*dbname/user/password")
Using either of these drivers, you can perform a standard SQL query.
This example assumes there is a table named 'users' with
columns 'first_name' and 'last_name':
rows, err := db.Query("SELECT first_name, last_name FROM users")
if err != nil {
log.Errorf(ctx, "db.Query: %v", err)
}
defer rows.Close()
for rows.Next() {
var firstName string
var lastName string
if err := rows.Scan(&firstName, &lastName); err != nil {
log.Errorf(ctx, "rows.Scan: %v", err)
continue
}
log.Infof(ctx, "First: %v - Last: %v", firstName, lastName)
}
if err := rows.Err(); err != nil {
log.Errorf(ctx, "Row error: %v", err)
}
*/
package cloudsql
import (
"net"
)
// Dial connects to the named Cloud SQL instance.
func Dial(instance string) (net.Conn, error) {
return connect(instance)
}
// Copyright 2013 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// +build appengine
package cloudsql
import (
"net"
"appengine/cloudsql"
)
func connect(instance string) (net.Conn, error) {
return cloudsql.Dial(instance)
}
// Copyright 2013 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// +build !appengine
package cloudsql
import (
"errors"
"net"
)
func connect(instance string) (net.Conn, error) {
return nil, errors.New(`cloudsql: not supported in App Engine "flexible environment"`)
}
...@@ -44,6 +44,12 @@ ...@@ -44,6 +44,12 @@
"revision": "ae2e2a20879aabdd3a51104ab6a4328af2773948", "revision": "ae2e2a20879aabdd3a51104ab6a4328af2773948",
"revisionTime": "2018-11-22T09:33:36Z" "revisionTime": "2018-11-22T09:33:36Z"
}, },
{
"checksumSHA1": "wmiJbuFVCxg22zwXIeLsVCudyeY=",
"path": "github.com/go-sql-driver/mysql",
"revision": "c45f530f8e7fe40f4687eaa50d0c8c5f1b66f9e0",
"revisionTime": "2018-12-18T12:36:37Z"
},
{ {
"checksumSHA1": "HmbftipkadrLlCfzzVQ+iFHbl6g=", "checksumSHA1": "HmbftipkadrLlCfzzVQ+iFHbl6g=",
"path": "github.com/golang/glog", "path": "github.com/golang/glog",
...@@ -272,12 +278,6 @@ ...@@ -272,12 +278,6 @@
"revision": "6b05b0cf718f8ee11bc5e18fe78ea7cef0388dc6", "revision": "6b05b0cf718f8ee11bc5e18fe78ea7cef0388dc6",
"revisionTime": "2018-08-27T08:56:28Z" "revisionTime": "2018-08-27T08:56:28Z"
}, },
{
"checksumSHA1": "m3s+OrOHAMX0seOVfuDwt766BQE=",
"path": "github.com/ziutek/mymysql/native",
"revision": "6b05b0cf718f8ee11bc5e18fe78ea7cef0388dc6",
"revisionTime": "2018-08-27T08:56:28Z"
},
{ {
"checksumSHA1": "BGm8lKZmvJbf/YOJLeL1rw2WVjA=", "checksumSHA1": "BGm8lKZmvJbf/YOJLeL1rw2WVjA=",
"path": "golang.org/x/crypto/ssh/terminal", "path": "golang.org/x/crypto/ssh/terminal",
...@@ -374,6 +374,12 @@ ...@@ -374,6 +374,12 @@
"revision": "6f44c5a2ea40ee3593d98cdcc905cc1fdaa660e2", "revision": "6f44c5a2ea40ee3593d98cdcc905cc1fdaa660e2",
"revisionTime": "2018-10-29T18:00:05Z" "revisionTime": "2018-10-29T18:00:05Z"
}, },
{
"checksumSHA1": "LiyXfqOzaeQ8vgYZH3t2hUEdVTw=",
"path": "google.golang.org/appengine/cloudsql",
"revision": "e9657d882bb81064595ca3b56cbe2546bbabf7b1",
"revisionTime": "2018-12-17T20:59:03Z"
},
{ {
"checksumSHA1": "MgYFT27I9gfAtSVBpGVqkCYOj3U=", "checksumSHA1": "MgYFT27I9gfAtSVBpGVqkCYOj3U=",
"path": "google.golang.org/genproto/googleapis/rpc/status", "path": "google.golang.org/genproto/googleapis/rpc/status",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册