First commit
This commit is contained in:
commit
a165cd9f91
|
@ -0,0 +1,40 @@
|
||||||
|
# This is the official list of Go-MySQL-Driver authors for copyright purposes.
|
||||||
|
|
||||||
|
# If you are submitting a patch, please add your name or the name of the
|
||||||
|
# organization which holds the copyright to this list in alphabetical order.
|
||||||
|
|
||||||
|
# Names should be added to this file as
|
||||||
|
# Name <email address>
|
||||||
|
# The email address is not required for organizations.
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
|
||||||
|
# Individual Persons
|
||||||
|
|
||||||
|
Aaron Hopkins <go-sql-driver at die.net>
|
||||||
|
Arne Hormann <arnehormann at gmail.com>
|
||||||
|
Carlos Nieto <jose.carlos at menteslibres.net>
|
||||||
|
Chris Moos <chris at tech9computers.com>
|
||||||
|
DisposaBoy <disposaboy at dby.me>
|
||||||
|
Frederick Mayle <frederickmayle at gmail.com>
|
||||||
|
Gustavo Kristic <gkristic at gmail.com>
|
||||||
|
Hanno Braun <mail at hannobraun.com>
|
||||||
|
Henri Yandell <flamefew at gmail.com>
|
||||||
|
INADA Naoki <songofacandy at gmail.com>
|
||||||
|
James Harr <james.harr at gmail.com>
|
||||||
|
Jian Zhen <zhenjl at gmail.com>
|
||||||
|
Julien Schmidt <go-sql-driver at julienschmidt.com>
|
||||||
|
Leonardo YongUk Kim <dalinaum at gmail.com>
|
||||||
|
Lucas Liu <extrafliu at gmail.com>
|
||||||
|
Luke Scott <luke at webconnex.com>
|
||||||
|
Michael Woolnough <michael.woolnough at gmail.com>
|
||||||
|
Nicola Peduzzi <thenikso at gmail.com>
|
||||||
|
Runrioter Wung <runrioter at gmail.com>
|
||||||
|
Xiaobing Jiang <s7v7nislands at gmail.com>
|
||||||
|
Xiuming Chen <cc at cxm.cc>
|
||||||
|
|
||||||
|
# Organizations
|
||||||
|
|
||||||
|
Barracuda Networks, Inc.
|
||||||
|
Google Inc.
|
||||||
|
Stripe Inc.
|
|
@ -0,0 +1,83 @@
|
||||||
|
## HEAD
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
- Use decimals field from MySQL to format time types
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
- Enable microsecond resolution on TIME, DATETIME and TIMESTAMP
|
||||||
|
|
||||||
|
|
||||||
|
## 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
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Contributing Guidelines
|
||||||
|
|
||||||
|
## Reporting Issues
|
||||||
|
|
||||||
|
Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed).
|
||||||
|
|
||||||
|
Please provide the following minimum information:
|
||||||
|
* Your Go-MySQL-Driver version (or git SHA)
|
||||||
|
* Your Go version (run `go version` in your console)
|
||||||
|
* A detailed issue description
|
||||||
|
* Error Log if present
|
||||||
|
* If possible, a short example
|
||||||
|
|
||||||
|
|
||||||
|
## Contributing Code
|
||||||
|
|
||||||
|
By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file.
|
||||||
|
Don't forget to add yourself to the AUTHORS file.
|
||||||
|
|
||||||
|
### Pull Requests Checklist
|
||||||
|
|
||||||
|
Please check the following points before submitting your pull request:
|
||||||
|
- [x] Code compiles correctly
|
||||||
|
- [x] Created tests, if possible
|
||||||
|
- [x] All tests pass
|
||||||
|
- [x] Extended the README / documentation, if necessary
|
||||||
|
- [x] Added yourself to the AUTHORS file
|
||||||
|
|
||||||
|
### Code Review
|
||||||
|
|
||||||
|
Everyone is invited to review and comment on pull requests.
|
||||||
|
If it looks fine to you, comment with "LGTM" (Looks good to me).
|
||||||
|
|
||||||
|
If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes.
|
||||||
|
|
||||||
|
Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM".
|
||||||
|
|
||||||
|
## Development Ideas
|
||||||
|
|
||||||
|
If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/go-sql-driver/mysql/wiki/Development-Ideas) Wiki page.
|
|
@ -0,0 +1,373 @@
|
||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
|
@ -0,0 +1,361 @@
|
||||||
|
# Go-MySQL-Driver
|
||||||
|
|
||||||
|
A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) package
|
||||||
|
|
||||||
|
![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin")
|
||||||
|
|
||||||
|
**Latest stable Release:** [Version 1.2 (June 03, 2014)](https://github.com/go-sql-driver/mysql/releases)
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/go-sql-driver/mysql.png?branch=master)](https://travis-ci.org/go-sql-driver/mysql)
|
||||||
|
|
||||||
|
---------------------------------------
|
||||||
|
* [Features](#features)
|
||||||
|
* [Requirements](#requirements)
|
||||||
|
* [Installation](#installation)
|
||||||
|
* [Usage](#usage)
|
||||||
|
* [DSN (Data Source Name)](#dsn-data-source-name)
|
||||||
|
* [Password](#password)
|
||||||
|
* [Protocol](#protocol)
|
||||||
|
* [Address](#address)
|
||||||
|
* [Parameters](#parameters)
|
||||||
|
* [Examples](#examples)
|
||||||
|
* [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support)
|
||||||
|
* [time.Time support](#timetime-support)
|
||||||
|
* [Unicode support](#unicode-support)
|
||||||
|
* [Testing / Development](#testing--development)
|
||||||
|
* [License](#license)
|
||||||
|
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
## Features
|
||||||
|
* Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance")
|
||||||
|
* Native Go implementation. No C-bindings, just pure Go
|
||||||
|
* Connections over TCP/IPv4, TCP/IPv6 or Unix domain sockets
|
||||||
|
* Automatic handling of broken connections
|
||||||
|
* Automatic Connection Pooling *(by database/sql package)*
|
||||||
|
* Supports queries larger than 16MB
|
||||||
|
* Full [`sql.RawBytes`](http://golang.org/pkg/database/sql/#RawBytes) support.
|
||||||
|
* Intelligent `LONG DATA` handling in prepared statements
|
||||||
|
* Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support
|
||||||
|
* Optional `time.Time` parsing
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
* Go 1.1 or higher
|
||||||
|
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
|
||||||
|
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
Simple install the package to your [$GOPATH](http://code.google.com/p/go-wiki/wiki/GOPATH "GOPATH") with the [go tool](http://golang.org/cmd/go/ "go command") from shell:
|
||||||
|
```bash
|
||||||
|
$ go get github.com/go-sql-driver/mysql
|
||||||
|
```
|
||||||
|
Make sure [Git is installed](http://git-scm.com/downloads) on your machine and in your system's `PATH`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
_Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](http://golang.org/pkg/database/sql) API then.
|
||||||
|
|
||||||
|
Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name) as `dataSourceName`:
|
||||||
|
```go
|
||||||
|
import "database/sql"
|
||||||
|
import _ "github.com/go-sql-driver/mysql"
|
||||||
|
|
||||||
|
db, err := sql.Open("mysql", "user:password@/dbname")
|
||||||
|
```
|
||||||
|
|
||||||
|
[Examples are available in our Wiki](https://github.com/go-sql-driver/mysql/wiki/Examples "Go-MySQL-Driver Examples").
|
||||||
|
|
||||||
|
|
||||||
|
### DSN (Data Source Name)
|
||||||
|
|
||||||
|
The Data Source Name has a common format, like e.g. [PEAR DB](http://pear.php.net/manual/en/package.database.db.intro-dsn.php) uses it, but without type-prefix (optional parts marked by squared brackets):
|
||||||
|
```
|
||||||
|
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
|
||||||
|
```
|
||||||
|
|
||||||
|
A DSN in its fullest form:
|
||||||
|
```
|
||||||
|
username:password@protocol(address)/dbname?param=value
|
||||||
|
```
|
||||||
|
|
||||||
|
Except for the databasename, all values are optional. So the minimal DSN is:
|
||||||
|
```
|
||||||
|
/dbname
|
||||||
|
```
|
||||||
|
|
||||||
|
If you do not want to preselect a database, leave `dbname` empty:
|
||||||
|
```
|
||||||
|
/
|
||||||
|
```
|
||||||
|
This has the same effect as an empty DSN string:
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Password
|
||||||
|
Passwords can consist of any character. Escaping is **not** necessary.
|
||||||
|
|
||||||
|
#### Protocol
|
||||||
|
See [net.Dial](http://golang.org/pkg/net/#Dial) for more information which networks are available.
|
||||||
|
In general you should use an Unix domain socket if available and TCP otherwise for best performance.
|
||||||
|
|
||||||
|
#### Address
|
||||||
|
For TCP and UDP networks, addresses have the form `host:port`.
|
||||||
|
If `host` is a literal IPv6 address, it must be enclosed in square brackets.
|
||||||
|
The functions [net.JoinHostPort](http://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](http://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
|
||||||
|
|
||||||
|
For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
*Parameters are case-sensitive!*
|
||||||
|
|
||||||
|
Notice that any of `true`, `TRUE`, `True` or `1` is accepted to stand for a true boolean value. Not surprisingly, false can be specified as any of: `false`, `FALSE`, `False` or `0`.
|
||||||
|
|
||||||
|
##### `allowAllFiles`
|
||||||
|
|
||||||
|
```
|
||||||
|
Type: bool
|
||||||
|
Valid Values: true, false
|
||||||
|
Default: false
|
||||||
|
```
|
||||||
|
|
||||||
|
`allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files.
|
||||||
|
[*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
|
||||||
|
|
||||||
|
##### `allowOldPasswords`
|
||||||
|
|
||||||
|
```
|
||||||
|
Type: bool
|
||||||
|
Valid Values: true, false
|
||||||
|
Default: false
|
||||||
|
```
|
||||||
|
`allowOldPasswords=true` allows the usage of the insecure old password method. This should be avoided, but is necessary in some cases. See also [the old_passwords wiki page](https://github.com/go-sql-driver/mysql/wiki/old_passwords).
|
||||||
|
|
||||||
|
##### `charset`
|
||||||
|
|
||||||
|
```
|
||||||
|
Type: string
|
||||||
|
Valid Values: <name>
|
||||||
|
Default: none
|
||||||
|
```
|
||||||
|
|
||||||
|
Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
|
||||||
|
|
||||||
|
Usage of the `charset` parameter is discouraged because it issues additional queries to the server.
|
||||||
|
Unless you need the fallback behavior, please use `collation` instead.
|
||||||
|
|
||||||
|
##### `collation`
|
||||||
|
|
||||||
|
```
|
||||||
|
Type: string
|
||||||
|
Valid Values: <name>
|
||||||
|
Default: utf8_general_ci
|
||||||
|
```
|
||||||
|
|
||||||
|
Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail.
|
||||||
|
|
||||||
|
A list of valid charsets for a server is retrievable with `SHOW COLLATION`.
|
||||||
|
|
||||||
|
##### `clientFoundRows`
|
||||||
|
|
||||||
|
```
|
||||||
|
Type: bool
|
||||||
|
Valid Values: true, false
|
||||||
|
Default: false
|
||||||
|
```
|
||||||
|
|
||||||
|
`clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed.
|
||||||
|
|
||||||
|
##### `columnsWithAlias`
|
||||||
|
|
||||||
|
```
|
||||||
|
Type: bool
|
||||||
|
Valid Values: true, false
|
||||||
|
Default: false
|
||||||
|
```
|
||||||
|
|
||||||
|
When `columnsWithAlias` is true, calls to `sql.Rows.Columns()` will return the table alias and the column name separated by a dot. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
SELECT u.id FROM users as u
|
||||||
|
```
|
||||||
|
|
||||||
|
will return `u.id` instead of just `id` if `columnsWithAlias=true`.
|
||||||
|
|
||||||
|
##### `loc`
|
||||||
|
|
||||||
|
```
|
||||||
|
Type: string
|
||||||
|
Valid Values: <escaped name>
|
||||||
|
Default: UTC
|
||||||
|
```
|
||||||
|
|
||||||
|
Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details.
|
||||||
|
|
||||||
|
Please keep in mind, that param values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`.
|
||||||
|
|
||||||
|
|
||||||
|
##### `parseTime`
|
||||||
|
|
||||||
|
```
|
||||||
|
Type: bool
|
||||||
|
Valid Values: true, false
|
||||||
|
Default: false
|
||||||
|
```
|
||||||
|
|
||||||
|
`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
|
||||||
|
|
||||||
|
|
||||||
|
##### `strict`
|
||||||
|
|
||||||
|
```
|
||||||
|
Type: bool
|
||||||
|
Valid Values: true, false
|
||||||
|
Default: false
|
||||||
|
```
|
||||||
|
|
||||||
|
`strict=true` enables the strict mode in which MySQL warnings are treated as errors.
|
||||||
|
|
||||||
|
By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_notes) to ignore notes. See the [examples](#examples) for an DSN example.
|
||||||
|
|
||||||
|
|
||||||
|
##### `timeout`
|
||||||
|
|
||||||
|
```
|
||||||
|
Type: decimal number
|
||||||
|
Default: OS default
|
||||||
|
```
|
||||||
|
|
||||||
|
*Driver* side connection timeout. The value must be a string of decimal numbers, each with optional fraction and a unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout).
|
||||||
|
|
||||||
|
|
||||||
|
##### `tls`
|
||||||
|
|
||||||
|
```
|
||||||
|
Type: bool / string
|
||||||
|
Valid Values: true, false, skip-verify, <name>
|
||||||
|
Default: false
|
||||||
|
```
|
||||||
|
|
||||||
|
`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](http://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
|
||||||
|
|
||||||
|
|
||||||
|
##### System Variables
|
||||||
|
|
||||||
|
All other parameters are interpreted as system variables:
|
||||||
|
* `autocommit`: `"SET autocommit=<value>"`
|
||||||
|
* `time_zone`: `"SET time_zone=<value>"`
|
||||||
|
* [`tx_isolation`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `"SET tx_isolation=<value>"`
|
||||||
|
* `param`: `"SET <param>=<value>"`
|
||||||
|
|
||||||
|
*The values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!*
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
```
|
||||||
|
user@unix(/path/to/socket)/dbname
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the [strict mode](#strict) but ignore notes:
|
||||||
|
```
|
||||||
|
user:password@/dbname?strict=true&sql_notes=false
|
||||||
|
```
|
||||||
|
|
||||||
|
TCP via IPv6:
|
||||||
|
```
|
||||||
|
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s&collation=utf8mb4_unicode_ci
|
||||||
|
```
|
||||||
|
|
||||||
|
TCP on a remote host, e.g. Amazon RDS:
|
||||||
|
```
|
||||||
|
id:password@tcp(your-amazonaws-uri.com:3306)/dbname
|
||||||
|
```
|
||||||
|
|
||||||
|
Google Cloud SQL on App Engine:
|
||||||
|
```
|
||||||
|
user@cloudsql(project-id:instance-name)/dbname
|
||||||
|
```
|
||||||
|
|
||||||
|
TCP using default port (3306) on localhost:
|
||||||
|
```
|
||||||
|
user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the default protocol (tcp) and host (localhost:3306):
|
||||||
|
```
|
||||||
|
user:password@/dbname
|
||||||
|
```
|
||||||
|
|
||||||
|
No Database preselected:
|
||||||
|
```
|
||||||
|
user:password@/
|
||||||
|
```
|
||||||
|
|
||||||
|
### `LOAD DATA LOCAL INFILE` support
|
||||||
|
For this feature you need direct access to the package. Therefore you must change the import path (no `_`):
|
||||||
|
```go
|
||||||
|
import "github.com/go-sql-driver/mysql"
|
||||||
|
```
|
||||||
|
|
||||||
|
Files must be whitelisted by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the Whitelist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)).
|
||||||
|
|
||||||
|
To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then.
|
||||||
|
|
||||||
|
See the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
|
||||||
|
|
||||||
|
|
||||||
|
### `time.Time` support
|
||||||
|
The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your programm.
|
||||||
|
|
||||||
|
However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](http://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
|
||||||
|
|
||||||
|
**Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
|
||||||
|
|
||||||
|
Alternatively you can use the [`NullTime`](http://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`.
|
||||||
|
|
||||||
|
|
||||||
|
### Unicode support
|
||||||
|
Since version 1.1 Go-MySQL-Driver automatically uses the collation `utf8_general_ci` by default.
|
||||||
|
|
||||||
|
Other collations / charsets can be set using the [`collation`](#collation) DSN parameter.
|
||||||
|
|
||||||
|
Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAMES utf8`) to the DSN to enable proper UTF-8 support. This is not necessary anymore. The [`collation`](#collation) parameter should be preferred to set another collation / charset than the default.
|
||||||
|
|
||||||
|
See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
|
||||||
|
|
||||||
|
|
||||||
|
## Testing / Development
|
||||||
|
To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
|
||||||
|
|
||||||
|
Go-MySQL-Driver is not feature-complete yet. Your help is very appreciated.
|
||||||
|
If you want to contribute, you can work on an [open issue](https://github.com/go-sql-driver/mysql/issues?state=open) or review a [pull request](https://github.com/go-sql-driver/mysql/pulls).
|
||||||
|
|
||||||
|
See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/CONTRIBUTING.md) for details.
|
||||||
|
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
## License
|
||||||
|
Go-MySQL-Driver is licensed under the [Mozilla Public License Version 2.0](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
|
||||||
|
|
||||||
|
Mozilla summarizes the license scope as follows:
|
||||||
|
> MPL: The copyleft applies to any files containing MPLed code.
|
||||||
|
|
||||||
|
|
||||||
|
That means:
|
||||||
|
* You can **use** the **unchanged** source code both in private and commercially
|
||||||
|
* When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0)
|
||||||
|
* You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**
|
||||||
|
|
||||||
|
Please read the [MPL 2.0 FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html) if you have further questions regarding the license.
|
||||||
|
|
||||||
|
You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
|
||||||
|
|
||||||
|
![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow")
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"appengine/cloudsql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterDial("cloudsql", cloudsql.Dial)
|
||||||
|
}
|
|
@ -0,0 +1,208 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TB testing.B
|
||||||
|
|
||||||
|
func (tb *TB) check(err error) {
|
||||||
|
if err != nil {
|
||||||
|
tb.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb *TB) checkDB(db *sql.DB, err error) *sql.DB {
|
||||||
|
tb.check(err)
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb *TB) checkRows(rows *sql.Rows, err error) *sql.Rows {
|
||||||
|
tb.check(err)
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb *TB) checkStmt(stmt *sql.Stmt, err error) *sql.Stmt {
|
||||||
|
tb.check(err)
|
||||||
|
return stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDB(b *testing.B, queries ...string) *sql.DB {
|
||||||
|
tb := (*TB)(b)
|
||||||
|
db := tb.checkDB(sql.Open("mysql", dsn))
|
||||||
|
for _, query := range queries {
|
||||||
|
if _, err := db.Exec(query); err != nil {
|
||||||
|
b.Fatalf("Error on %q: %v", query, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
const concurrencyLevel = 10
|
||||||
|
|
||||||
|
func BenchmarkQuery(b *testing.B) {
|
||||||
|
tb := (*TB)(b)
|
||||||
|
b.StopTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
db := initDB(b,
|
||||||
|
"DROP TABLE IF EXISTS foo",
|
||||||
|
"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
|
||||||
|
`INSERT INTO foo VALUES (1, "one")`,
|
||||||
|
`INSERT INTO foo VALUES (2, "two")`,
|
||||||
|
)
|
||||||
|
db.SetMaxIdleConns(concurrencyLevel)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
stmt := tb.checkStmt(db.Prepare("SELECT val FROM foo WHERE id=?"))
|
||||||
|
defer stmt.Close()
|
||||||
|
|
||||||
|
remain := int64(b.N)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(concurrencyLevel)
|
||||||
|
defer wg.Wait()
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < concurrencyLevel; i++ {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if atomic.AddInt64(&remain, -1) < 0 {
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var got string
|
||||||
|
tb.check(stmt.QueryRow(1).Scan(&got))
|
||||||
|
if got != "one" {
|
||||||
|
b.Errorf("query = %q; want one", got)
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkExec(b *testing.B) {
|
||||||
|
tb := (*TB)(b)
|
||||||
|
b.StopTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
db := tb.checkDB(sql.Open("mysql", dsn))
|
||||||
|
db.SetMaxIdleConns(concurrencyLevel)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
stmt := tb.checkStmt(db.Prepare("DO 1"))
|
||||||
|
defer stmt.Close()
|
||||||
|
|
||||||
|
remain := int64(b.N)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(concurrencyLevel)
|
||||||
|
defer wg.Wait()
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < concurrencyLevel; i++ {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if atomic.AddInt64(&remain, -1) < 0 {
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := stmt.Exec(); err != nil {
|
||||||
|
b.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// data, but no db writes
|
||||||
|
var roundtripSample []byte
|
||||||
|
|
||||||
|
func initRoundtripBenchmarks() ([]byte, int, int) {
|
||||||
|
if roundtripSample == nil {
|
||||||
|
roundtripSample = []byte(strings.Repeat("0123456789abcdef", 1024*1024))
|
||||||
|
}
|
||||||
|
return roundtripSample, 16, len(roundtripSample)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRoundtripTxt(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
sample, min, max := initRoundtripBenchmarks()
|
||||||
|
sampleString := string(sample)
|
||||||
|
b.ReportAllocs()
|
||||||
|
tb := (*TB)(b)
|
||||||
|
db := tb.checkDB(sql.Open("mysql", dsn))
|
||||||
|
defer db.Close()
|
||||||
|
b.StartTimer()
|
||||||
|
var result string
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
length := min + i
|
||||||
|
if length > max {
|
||||||
|
length = max
|
||||||
|
}
|
||||||
|
test := sampleString[0:length]
|
||||||
|
rows := tb.checkRows(db.Query(`SELECT "` + test + `"`))
|
||||||
|
if !rows.Next() {
|
||||||
|
rows.Close()
|
||||||
|
b.Fatalf("crashed")
|
||||||
|
}
|
||||||
|
err := rows.Scan(&result)
|
||||||
|
if err != nil {
|
||||||
|
rows.Close()
|
||||||
|
b.Fatalf("crashed")
|
||||||
|
}
|
||||||
|
if result != test {
|
||||||
|
rows.Close()
|
||||||
|
b.Errorf("mismatch")
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRoundtripBin(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
sample, min, max := initRoundtripBenchmarks()
|
||||||
|
b.ReportAllocs()
|
||||||
|
tb := (*TB)(b)
|
||||||
|
db := tb.checkDB(sql.Open("mysql", dsn))
|
||||||
|
defer db.Close()
|
||||||
|
stmt := tb.checkStmt(db.Prepare("SELECT ?"))
|
||||||
|
defer stmt.Close()
|
||||||
|
b.StartTimer()
|
||||||
|
var result sql.RawBytes
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
length := min + i
|
||||||
|
if length > max {
|
||||||
|
length = max
|
||||||
|
}
|
||||||
|
test := sample[0:length]
|
||||||
|
rows := tb.checkRows(stmt.Query(test))
|
||||||
|
if !rows.Next() {
|
||||||
|
rows.Close()
|
||||||
|
b.Fatalf("crashed")
|
||||||
|
}
|
||||||
|
err := rows.Scan(&result)
|
||||||
|
if err != nil {
|
||||||
|
rows.Close()
|
||||||
|
b.Fatalf("crashed")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(result, test) {
|
||||||
|
rows.Close()
|
||||||
|
b.Errorf("mismatch")
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
const defaultBufSize = 4096
|
||||||
|
|
||||||
|
// A buffer which is used for both reading and writing.
|
||||||
|
// This is possible since communication on each connection is synchronous.
|
||||||
|
// In other words, we can't write and read simultaneously on the same connection.
|
||||||
|
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
|
||||||
|
// Also highly optimized for this particular use case.
|
||||||
|
type buffer struct {
|
||||||
|
buf []byte
|
||||||
|
rd io.Reader
|
||||||
|
idx int
|
||||||
|
length int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBuffer(rd io.Reader) buffer {
|
||||||
|
var b [defaultBufSize]byte
|
||||||
|
return buffer{
|
||||||
|
buf: b[:],
|
||||||
|
rd: rd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill reads into the buffer until at least _need_ bytes are in it
|
||||||
|
func (b *buffer) fill(need int) error {
|
||||||
|
n := b.length
|
||||||
|
|
||||||
|
// move existing data to the beginning
|
||||||
|
if n > 0 && b.idx > 0 {
|
||||||
|
copy(b.buf[0:n], b.buf[b.idx:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// grow buffer if necessary
|
||||||
|
// TODO: let the buffer shrink again at some point
|
||||||
|
// Maybe keep the org buf slice and swap back?
|
||||||
|
if need > len(b.buf) {
|
||||||
|
// Round up to the next multiple of the default size
|
||||||
|
newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
|
||||||
|
copy(newBuf, b.buf)
|
||||||
|
b.buf = newBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
b.idx = 0
|
||||||
|
|
||||||
|
for {
|
||||||
|
nn, err := b.rd.Read(b.buf[n:])
|
||||||
|
n += nn
|
||||||
|
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
if n < need {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b.length = n
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case io.EOF:
|
||||||
|
if n >= need {
|
||||||
|
b.length = n
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns next N bytes from buffer.
|
||||||
|
// The returned slice is only guaranteed to be valid until the next read
|
||||||
|
func (b *buffer) readNext(need int) ([]byte, error) {
|
||||||
|
if b.length < need {
|
||||||
|
// refill
|
||||||
|
if err := b.fill(need); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := b.idx
|
||||||
|
b.idx += need
|
||||||
|
b.length -= need
|
||||||
|
return b.buf[offset:b.idx], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a buffer with the requested size.
|
||||||
|
// If possible, a slice from the existing buffer is returned.
|
||||||
|
// Otherwise a bigger buffer is made.
|
||||||
|
// Only one buffer (total) can be used at a time.
|
||||||
|
func (b *buffer) takeBuffer(length int) []byte {
|
||||||
|
if b.length > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// test (cheap) general case first
|
||||||
|
if length <= defaultBufSize || length <= cap(b.buf) {
|
||||||
|
return b.buf[:length]
|
||||||
|
}
|
||||||
|
|
||||||
|
if length < maxPacketSize {
|
||||||
|
b.buf = make([]byte, length)
|
||||||
|
return b.buf
|
||||||
|
}
|
||||||
|
return make([]byte, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortcut which can be used if the requested buffer is guaranteed to be
|
||||||
|
// smaller than defaultBufSize
|
||||||
|
// Only one buffer (total) can be used at a time.
|
||||||
|
func (b *buffer) takeSmallBuffer(length int) []byte {
|
||||||
|
if b.length == 0 {
|
||||||
|
return b.buf[:length]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// takeCompleteBuffer returns the complete existing buffer.
|
||||||
|
// This can be used if the necessary buffer size is unknown.
|
||||||
|
// Only one buffer (total) can be used at a time.
|
||||||
|
func (b *buffer) takeCompleteBuffer() []byte {
|
||||||
|
if b.length == 0 {
|
||||||
|
return b.buf
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,236 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
const defaultCollation byte = 33 // utf8_general_ci
|
||||||
|
|
||||||
|
// A list of available collations mapped to the internal ID.
|
||||||
|
// To update this map use the following MySQL query:
|
||||||
|
// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS
|
||||||
|
var collations = map[string]byte{
|
||||||
|
"big5_chinese_ci": 1,
|
||||||
|
"latin2_czech_cs": 2,
|
||||||
|
"dec8_swedish_ci": 3,
|
||||||
|
"cp850_general_ci": 4,
|
||||||
|
"latin1_german1_ci": 5,
|
||||||
|
"hp8_english_ci": 6,
|
||||||
|
"koi8r_general_ci": 7,
|
||||||
|
"latin1_swedish_ci": 8,
|
||||||
|
"latin2_general_ci": 9,
|
||||||
|
"swe7_swedish_ci": 10,
|
||||||
|
"ascii_general_ci": 11,
|
||||||
|
"ujis_japanese_ci": 12,
|
||||||
|
"sjis_japanese_ci": 13,
|
||||||
|
"cp1251_bulgarian_ci": 14,
|
||||||
|
"latin1_danish_ci": 15,
|
||||||
|
"hebrew_general_ci": 16,
|
||||||
|
"tis620_thai_ci": 18,
|
||||||
|
"euckr_korean_ci": 19,
|
||||||
|
"latin7_estonian_cs": 20,
|
||||||
|
"latin2_hungarian_ci": 21,
|
||||||
|
"koi8u_general_ci": 22,
|
||||||
|
"cp1251_ukrainian_ci": 23,
|
||||||
|
"gb2312_chinese_ci": 24,
|
||||||
|
"greek_general_ci": 25,
|
||||||
|
"cp1250_general_ci": 26,
|
||||||
|
"latin2_croatian_ci": 27,
|
||||||
|
"gbk_chinese_ci": 28,
|
||||||
|
"cp1257_lithuanian_ci": 29,
|
||||||
|
"latin5_turkish_ci": 30,
|
||||||
|
"latin1_german2_ci": 31,
|
||||||
|
"armscii8_general_ci": 32,
|
||||||
|
"utf8_general_ci": 33,
|
||||||
|
"cp1250_czech_cs": 34,
|
||||||
|
"ucs2_general_ci": 35,
|
||||||
|
"cp866_general_ci": 36,
|
||||||
|
"keybcs2_general_ci": 37,
|
||||||
|
"macce_general_ci": 38,
|
||||||
|
"macroman_general_ci": 39,
|
||||||
|
"cp852_general_ci": 40,
|
||||||
|
"latin7_general_ci": 41,
|
||||||
|
"latin7_general_cs": 42,
|
||||||
|
"macce_bin": 43,
|
||||||
|
"cp1250_croatian_ci": 44,
|
||||||
|
"utf8mb4_general_ci": 45,
|
||||||
|
"utf8mb4_bin": 46,
|
||||||
|
"latin1_bin": 47,
|
||||||
|
"latin1_general_ci": 48,
|
||||||
|
"latin1_general_cs": 49,
|
||||||
|
"cp1251_bin": 50,
|
||||||
|
"cp1251_general_ci": 51,
|
||||||
|
"cp1251_general_cs": 52,
|
||||||
|
"macroman_bin": 53,
|
||||||
|
"utf16_general_ci": 54,
|
||||||
|
"utf16_bin": 55,
|
||||||
|
"utf16le_general_ci": 56,
|
||||||
|
"cp1256_general_ci": 57,
|
||||||
|
"cp1257_bin": 58,
|
||||||
|
"cp1257_general_ci": 59,
|
||||||
|
"utf32_general_ci": 60,
|
||||||
|
"utf32_bin": 61,
|
||||||
|
"utf16le_bin": 62,
|
||||||
|
"binary": 63,
|
||||||
|
"armscii8_bin": 64,
|
||||||
|
"ascii_bin": 65,
|
||||||
|
"cp1250_bin": 66,
|
||||||
|
"cp1256_bin": 67,
|
||||||
|
"cp866_bin": 68,
|
||||||
|
"dec8_bin": 69,
|
||||||
|
"greek_bin": 70,
|
||||||
|
"hebrew_bin": 71,
|
||||||
|
"hp8_bin": 72,
|
||||||
|
"keybcs2_bin": 73,
|
||||||
|
"koi8r_bin": 74,
|
||||||
|
"koi8u_bin": 75,
|
||||||
|
"latin2_bin": 77,
|
||||||
|
"latin5_bin": 78,
|
||||||
|
"latin7_bin": 79,
|
||||||
|
"cp850_bin": 80,
|
||||||
|
"cp852_bin": 81,
|
||||||
|
"swe7_bin": 82,
|
||||||
|
"utf8_bin": 83,
|
||||||
|
"big5_bin": 84,
|
||||||
|
"euckr_bin": 85,
|
||||||
|
"gb2312_bin": 86,
|
||||||
|
"gbk_bin": 87,
|
||||||
|
"sjis_bin": 88,
|
||||||
|
"tis620_bin": 89,
|
||||||
|
"ucs2_bin": 90,
|
||||||
|
"ujis_bin": 91,
|
||||||
|
"geostd8_general_ci": 92,
|
||||||
|
"geostd8_bin": 93,
|
||||||
|
"latin1_spanish_ci": 94,
|
||||||
|
"cp932_japanese_ci": 95,
|
||||||
|
"cp932_bin": 96,
|
||||||
|
"eucjpms_japanese_ci": 97,
|
||||||
|
"eucjpms_bin": 98,
|
||||||
|
"cp1250_polish_ci": 99,
|
||||||
|
"utf16_unicode_ci": 101,
|
||||||
|
"utf16_icelandic_ci": 102,
|
||||||
|
"utf16_latvian_ci": 103,
|
||||||
|
"utf16_romanian_ci": 104,
|
||||||
|
"utf16_slovenian_ci": 105,
|
||||||
|
"utf16_polish_ci": 106,
|
||||||
|
"utf16_estonian_ci": 107,
|
||||||
|
"utf16_spanish_ci": 108,
|
||||||
|
"utf16_swedish_ci": 109,
|
||||||
|
"utf16_turkish_ci": 110,
|
||||||
|
"utf16_czech_ci": 111,
|
||||||
|
"utf16_danish_ci": 112,
|
||||||
|
"utf16_lithuanian_ci": 113,
|
||||||
|
"utf16_slovak_ci": 114,
|
||||||
|
"utf16_spanish2_ci": 115,
|
||||||
|
"utf16_roman_ci": 116,
|
||||||
|
"utf16_persian_ci": 117,
|
||||||
|
"utf16_esperanto_ci": 118,
|
||||||
|
"utf16_hungarian_ci": 119,
|
||||||
|
"utf16_sinhala_ci": 120,
|
||||||
|
"utf16_german2_ci": 121,
|
||||||
|
"utf16_croatian_ci": 122,
|
||||||
|
"utf16_unicode_520_ci": 123,
|
||||||
|
"utf16_vietnamese_ci": 124,
|
||||||
|
"ucs2_unicode_ci": 128,
|
||||||
|
"ucs2_icelandic_ci": 129,
|
||||||
|
"ucs2_latvian_ci": 130,
|
||||||
|
"ucs2_romanian_ci": 131,
|
||||||
|
"ucs2_slovenian_ci": 132,
|
||||||
|
"ucs2_polish_ci": 133,
|
||||||
|
"ucs2_estonian_ci": 134,
|
||||||
|
"ucs2_spanish_ci": 135,
|
||||||
|
"ucs2_swedish_ci": 136,
|
||||||
|
"ucs2_turkish_ci": 137,
|
||||||
|
"ucs2_czech_ci": 138,
|
||||||
|
"ucs2_danish_ci": 139,
|
||||||
|
"ucs2_lithuanian_ci": 140,
|
||||||
|
"ucs2_slovak_ci": 141,
|
||||||
|
"ucs2_spanish2_ci": 142,
|
||||||
|
"ucs2_roman_ci": 143,
|
||||||
|
"ucs2_persian_ci": 144,
|
||||||
|
"ucs2_esperanto_ci": 145,
|
||||||
|
"ucs2_hungarian_ci": 146,
|
||||||
|
"ucs2_sinhala_ci": 147,
|
||||||
|
"ucs2_german2_ci": 148,
|
||||||
|
"ucs2_croatian_ci": 149,
|
||||||
|
"ucs2_unicode_520_ci": 150,
|
||||||
|
"ucs2_vietnamese_ci": 151,
|
||||||
|
"ucs2_general_mysql500_ci": 159,
|
||||||
|
"utf32_unicode_ci": 160,
|
||||||
|
"utf32_icelandic_ci": 161,
|
||||||
|
"utf32_latvian_ci": 162,
|
||||||
|
"utf32_romanian_ci": 163,
|
||||||
|
"utf32_slovenian_ci": 164,
|
||||||
|
"utf32_polish_ci": 165,
|
||||||
|
"utf32_estonian_ci": 166,
|
||||||
|
"utf32_spanish_ci": 167,
|
||||||
|
"utf32_swedish_ci": 168,
|
||||||
|
"utf32_turkish_ci": 169,
|
||||||
|
"utf32_czech_ci": 170,
|
||||||
|
"utf32_danish_ci": 171,
|
||||||
|
"utf32_lithuanian_ci": 172,
|
||||||
|
"utf32_slovak_ci": 173,
|
||||||
|
"utf32_spanish2_ci": 174,
|
||||||
|
"utf32_roman_ci": 175,
|
||||||
|
"utf32_persian_ci": 176,
|
||||||
|
"utf32_esperanto_ci": 177,
|
||||||
|
"utf32_hungarian_ci": 178,
|
||||||
|
"utf32_sinhala_ci": 179,
|
||||||
|
"utf32_german2_ci": 180,
|
||||||
|
"utf32_croatian_ci": 181,
|
||||||
|
"utf32_unicode_520_ci": 182,
|
||||||
|
"utf32_vietnamese_ci": 183,
|
||||||
|
"utf8_unicode_ci": 192,
|
||||||
|
"utf8_icelandic_ci": 193,
|
||||||
|
"utf8_latvian_ci": 194,
|
||||||
|
"utf8_romanian_ci": 195,
|
||||||
|
"utf8_slovenian_ci": 196,
|
||||||
|
"utf8_polish_ci": 197,
|
||||||
|
"utf8_estonian_ci": 198,
|
||||||
|
"utf8_spanish_ci": 199,
|
||||||
|
"utf8_swedish_ci": 200,
|
||||||
|
"utf8_turkish_ci": 201,
|
||||||
|
"utf8_czech_ci": 202,
|
||||||
|
"utf8_danish_ci": 203,
|
||||||
|
"utf8_lithuanian_ci": 204,
|
||||||
|
"utf8_slovak_ci": 205,
|
||||||
|
"utf8_spanish2_ci": 206,
|
||||||
|
"utf8_roman_ci": 207,
|
||||||
|
"utf8_persian_ci": 208,
|
||||||
|
"utf8_esperanto_ci": 209,
|
||||||
|
"utf8_hungarian_ci": 210,
|
||||||
|
"utf8_sinhala_ci": 211,
|
||||||
|
"utf8_german2_ci": 212,
|
||||||
|
"utf8_croatian_ci": 213,
|
||||||
|
"utf8_unicode_520_ci": 214,
|
||||||
|
"utf8_vietnamese_ci": 215,
|
||||||
|
"utf8_general_mysql500_ci": 223,
|
||||||
|
"utf8mb4_unicode_ci": 224,
|
||||||
|
"utf8mb4_icelandic_ci": 225,
|
||||||
|
"utf8mb4_latvian_ci": 226,
|
||||||
|
"utf8mb4_romanian_ci": 227,
|
||||||
|
"utf8mb4_slovenian_ci": 228,
|
||||||
|
"utf8mb4_polish_ci": 229,
|
||||||
|
"utf8mb4_estonian_ci": 230,
|
||||||
|
"utf8mb4_spanish_ci": 231,
|
||||||
|
"utf8mb4_swedish_ci": 232,
|
||||||
|
"utf8mb4_turkish_ci": 233,
|
||||||
|
"utf8mb4_czech_ci": 234,
|
||||||
|
"utf8mb4_danish_ci": 235,
|
||||||
|
"utf8mb4_lithuanian_ci": 236,
|
||||||
|
"utf8mb4_slovak_ci": 237,
|
||||||
|
"utf8mb4_spanish2_ci": 238,
|
||||||
|
"utf8mb4_roman_ci": 239,
|
||||||
|
"utf8mb4_persian_ci": 240,
|
||||||
|
"utf8mb4_esperanto_ci": 241,
|
||||||
|
"utf8mb4_hungarian_ci": 242,
|
||||||
|
"utf8mb4_sinhala_ci": 243,
|
||||||
|
"utf8mb4_german2_ci": 244,
|
||||||
|
"utf8mb4_croatian_ci": 245,
|
||||||
|
"utf8mb4_unicode_520_ci": 246,
|
||||||
|
"utf8mb4_vietnamese_ci": 247,
|
||||||
|
}
|
|
@ -0,0 +1,269 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mysqlConn struct {
|
||||||
|
buf buffer
|
||||||
|
netConn net.Conn
|
||||||
|
affectedRows uint64
|
||||||
|
insertId uint64
|
||||||
|
cfg *config
|
||||||
|
maxPacketAllowed int
|
||||||
|
maxWriteSize int
|
||||||
|
flags clientFlag
|
||||||
|
sequence uint8
|
||||||
|
parseTime bool
|
||||||
|
strict bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
user string
|
||||||
|
passwd string
|
||||||
|
net string
|
||||||
|
addr string
|
||||||
|
dbname string
|
||||||
|
params map[string]string
|
||||||
|
loc *time.Location
|
||||||
|
tls *tls.Config
|
||||||
|
timeout time.Duration
|
||||||
|
collation uint8
|
||||||
|
allowAllFiles bool
|
||||||
|
allowOldPasswords bool
|
||||||
|
clientFoundRows bool
|
||||||
|
columnsWithAlias bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles parameters set in DSN after the connection is established
|
||||||
|
func (mc *mysqlConn) handleParams() (err error) {
|
||||||
|
for param, val := range mc.cfg.params {
|
||||||
|
switch param {
|
||||||
|
// Charset
|
||||||
|
case "charset":
|
||||||
|
charsets := strings.Split(val, ",")
|
||||||
|
for i := range charsets {
|
||||||
|
// ignore errors here - a charset may not exist
|
||||||
|
err = mc.exec("SET NAMES " + charsets[i])
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// time.Time parsing
|
||||||
|
case "parseTime":
|
||||||
|
var isBool bool
|
||||||
|
mc.parseTime, isBool = readBool(val)
|
||||||
|
if !isBool {
|
||||||
|
return errors.New("Invalid Bool value: " + val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strict mode
|
||||||
|
case "strict":
|
||||||
|
var isBool bool
|
||||||
|
mc.strict, isBool = readBool(val)
|
||||||
|
if !isBool {
|
||||||
|
return errors.New("Invalid Bool value: " + val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compression
|
||||||
|
case "compress":
|
||||||
|
err = errors.New("Compression not implemented yet")
|
||||||
|
return
|
||||||
|
|
||||||
|
// System Vars
|
||||||
|
default:
|
||||||
|
err = mc.exec("SET " + param + "=" + val + "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) Begin() (driver.Tx, error) {
|
||||||
|
if mc.netConn == nil {
|
||||||
|
errLog.Print(ErrInvalidConn)
|
||||||
|
return nil, driver.ErrBadConn
|
||||||
|
}
|
||||||
|
err := mc.exec("START TRANSACTION")
|
||||||
|
if err == nil {
|
||||||
|
return &mysqlTx{mc}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) Close() (err error) {
|
||||||
|
// Makes Close idempotent
|
||||||
|
if mc.netConn != nil {
|
||||||
|
err = mc.writeCommandPacket(comQuit)
|
||||||
|
if err == nil {
|
||||||
|
err = mc.netConn.Close()
|
||||||
|
} else {
|
||||||
|
mc.netConn.Close()
|
||||||
|
}
|
||||||
|
mc.netConn = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mc.cfg = nil
|
||||||
|
mc.buf.rd = nil
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
|
||||||
|
if mc.netConn == nil {
|
||||||
|
errLog.Print(ErrInvalidConn)
|
||||||
|
return nil, driver.ErrBadConn
|
||||||
|
}
|
||||||
|
// Send command
|
||||||
|
err := mc.writeCommandPacketStr(comStmtPrepare, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt := &mysqlStmt{
|
||||||
|
mc: mc,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read Result
|
||||||
|
columnCount, err := stmt.readPrepareResultPacket()
|
||||||
|
if err == nil {
|
||||||
|
if stmt.paramCount > 0 {
|
||||||
|
if err = mc.readUntilEOF(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if columnCount > 0 {
|
||||||
|
err = mc.readUntilEOF()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stmt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
||||||
|
if mc.netConn == nil {
|
||||||
|
errLog.Print(ErrInvalidConn)
|
||||||
|
return nil, driver.ErrBadConn
|
||||||
|
}
|
||||||
|
if len(args) == 0 { // no args, fastpath
|
||||||
|
mc.affectedRows = 0
|
||||||
|
mc.insertId = 0
|
||||||
|
|
||||||
|
err := mc.exec(query)
|
||||||
|
if err == nil {
|
||||||
|
return &mysqlResult{
|
||||||
|
affectedRows: int64(mc.affectedRows),
|
||||||
|
insertId: int64(mc.insertId),
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// with args, must use prepared stmt
|
||||||
|
return nil, driver.ErrSkip
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal function to execute commands
|
||||||
|
func (mc *mysqlConn) exec(query string) error {
|
||||||
|
// Send command
|
||||||
|
err := mc.writeCommandPacketStr(comQuery, query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read Result
|
||||||
|
resLen, err := mc.readResultSetHeaderPacket()
|
||||||
|
if err == nil && resLen > 0 {
|
||||||
|
if err = mc.readUntilEOF(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mc.readUntilEOF()
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
||||||
|
if mc.netConn == nil {
|
||||||
|
errLog.Print(ErrInvalidConn)
|
||||||
|
return nil, driver.ErrBadConn
|
||||||
|
}
|
||||||
|
if len(args) == 0 { // no args, fastpath
|
||||||
|
// Send command
|
||||||
|
err := mc.writeCommandPacketStr(comQuery, query)
|
||||||
|
if err == nil {
|
||||||
|
// Read Result
|
||||||
|
var resLen int
|
||||||
|
resLen, err = mc.readResultSetHeaderPacket()
|
||||||
|
if err == nil {
|
||||||
|
rows := new(textRows)
|
||||||
|
rows.mc = mc
|
||||||
|
|
||||||
|
if resLen == 0 {
|
||||||
|
// no columns, no more data
|
||||||
|
return emptyRows{}, nil
|
||||||
|
}
|
||||||
|
// Columns
|
||||||
|
rows.columns, err = mc.readColumns(resLen)
|
||||||
|
return rows, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// with args, must use prepared stmt
|
||||||
|
return nil, driver.ErrSkip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the value of the given MySQL System Variable
|
||||||
|
// The returned byte slice is only valid until the next read
|
||||||
|
func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
|
||||||
|
// Send command
|
||||||
|
if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read Result
|
||||||
|
resLen, err := mc.readResultSetHeaderPacket()
|
||||||
|
if err == nil {
|
||||||
|
rows := new(textRows)
|
||||||
|
rows.mc = mc
|
||||||
|
|
||||||
|
if resLen > 0 {
|
||||||
|
// Columns
|
||||||
|
if err := mc.readUntilEOF(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dest := make([]driver.Value, resLen)
|
||||||
|
if err = rows.readRow(dest); err == nil {
|
||||||
|
return dest[0].([]byte), mc.readUntilEOF()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
const (
|
||||||
|
minProtocolVersion byte = 10
|
||||||
|
maxPacketSize = 1<<24 - 1
|
||||||
|
timeFormat = "2006-01-02 15:04:05.999999"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MySQL constants documentation:
|
||||||
|
// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
|
||||||
|
|
||||||
|
const (
|
||||||
|
iOK byte = 0x00
|
||||||
|
iLocalInFile byte = 0xfb
|
||||||
|
iEOF byte = 0xfe
|
||||||
|
iERR byte = 0xff
|
||||||
|
)
|
||||||
|
|
||||||
|
type clientFlag uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
clientLongPassword clientFlag = 1 << iota
|
||||||
|
clientFoundRows
|
||||||
|
clientLongFlag
|
||||||
|
clientConnectWithDB
|
||||||
|
clientNoSchema
|
||||||
|
clientCompress
|
||||||
|
clientODBC
|
||||||
|
clientLocalFiles
|
||||||
|
clientIgnoreSpace
|
||||||
|
clientProtocol41
|
||||||
|
clientInteractive
|
||||||
|
clientSSL
|
||||||
|
clientIgnoreSIGPIPE
|
||||||
|
clientTransactions
|
||||||
|
clientReserved
|
||||||
|
clientSecureConn
|
||||||
|
clientMultiStatements
|
||||||
|
clientMultiResults
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
comQuit byte = iota + 1
|
||||||
|
comInitDB
|
||||||
|
comQuery
|
||||||
|
comFieldList
|
||||||
|
comCreateDB
|
||||||
|
comDropDB
|
||||||
|
comRefresh
|
||||||
|
comShutdown
|
||||||
|
comStatistics
|
||||||
|
comProcessInfo
|
||||||
|
comConnect
|
||||||
|
comProcessKill
|
||||||
|
comDebug
|
||||||
|
comPing
|
||||||
|
comTime
|
||||||
|
comDelayedInsert
|
||||||
|
comChangeUser
|
||||||
|
comBinlogDump
|
||||||
|
comTableDump
|
||||||
|
comConnectOut
|
||||||
|
comRegisterSlave
|
||||||
|
comStmtPrepare
|
||||||
|
comStmtExecute
|
||||||
|
comStmtSendLongData
|
||||||
|
comStmtClose
|
||||||
|
comStmtReset
|
||||||
|
comSetOption
|
||||||
|
comStmtFetch
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
fieldTypeDecimal byte = iota
|
||||||
|
fieldTypeTiny
|
||||||
|
fieldTypeShort
|
||||||
|
fieldTypeLong
|
||||||
|
fieldTypeFloat
|
||||||
|
fieldTypeDouble
|
||||||
|
fieldTypeNULL
|
||||||
|
fieldTypeTimestamp
|
||||||
|
fieldTypeLongLong
|
||||||
|
fieldTypeInt24
|
||||||
|
fieldTypeDate
|
||||||
|
fieldTypeTime
|
||||||
|
fieldTypeDateTime
|
||||||
|
fieldTypeYear
|
||||||
|
fieldTypeNewDate
|
||||||
|
fieldTypeVarChar
|
||||||
|
fieldTypeBit
|
||||||
|
)
|
||||||
|
const (
|
||||||
|
fieldTypeNewDecimal byte = iota + 0xf6
|
||||||
|
fieldTypeEnum
|
||||||
|
fieldTypeSet
|
||||||
|
fieldTypeTinyBLOB
|
||||||
|
fieldTypeMediumBLOB
|
||||||
|
fieldTypeLongBLOB
|
||||||
|
fieldTypeBLOB
|
||||||
|
fieldTypeVarString
|
||||||
|
fieldTypeString
|
||||||
|
fieldTypeGeometry
|
||||||
|
)
|
||||||
|
|
||||||
|
type fieldFlag uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagNotNULL fieldFlag = 1 << iota
|
||||||
|
flagPriKey
|
||||||
|
flagUniqueKey
|
||||||
|
flagMultipleKey
|
||||||
|
flagBLOB
|
||||||
|
flagUnsigned
|
||||||
|
flagZeroFill
|
||||||
|
flagBinary
|
||||||
|
flagEnum
|
||||||
|
flagAutoIncrement
|
||||||
|
flagTimestamp
|
||||||
|
flagSet
|
||||||
|
flagUnknown1
|
||||||
|
flagUnknown2
|
||||||
|
flagUnknown3
|
||||||
|
flagUnknown4
|
||||||
|
)
|
|
@ -0,0 +1,140 @@
|
||||||
|
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// The driver should be used via the database/sql package:
|
||||||
|
//
|
||||||
|
// import "database/sql"
|
||||||
|
// import _ "github.com/go-sql-driver/mysql"
|
||||||
|
//
|
||||||
|
// db, err := sql.Open("mysql", "user:password@/dbname")
|
||||||
|
//
|
||||||
|
// See https://github.com/go-sql-driver/mysql#usage for details
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This struct is exported to make the driver directly accessible.
|
||||||
|
// In general the driver is used via the database/sql package.
|
||||||
|
type MySQLDriver struct{}
|
||||||
|
|
||||||
|
// DialFunc is a function which can be used to establish the network connection.
|
||||||
|
// Custom dial functions must be registered with RegisterDial
|
||||||
|
type DialFunc func(addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
var dials map[string]DialFunc
|
||||||
|
|
||||||
|
// RegisterDial registers a custom dial function. It can then be used by the
|
||||||
|
// network address mynet(addr), where mynet is the registered new network.
|
||||||
|
// addr is passed as a parameter to the dial function.
|
||||||
|
func RegisterDial(net string, dial DialFunc) {
|
||||||
|
if dials == nil {
|
||||||
|
dials = make(map[string]DialFunc)
|
||||||
|
}
|
||||||
|
dials[net] = dial
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open new Connection.
|
||||||
|
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
|
||||||
|
// the DSN string is formated
|
||||||
|
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// New mysqlConn
|
||||||
|
mc := &mysqlConn{
|
||||||
|
maxPacketAllowed: maxPacketSize,
|
||||||
|
maxWriteSize: maxPacketSize - 1,
|
||||||
|
}
|
||||||
|
mc.cfg, err = parseDSN(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to Server
|
||||||
|
if dial, ok := dials[mc.cfg.net]; ok {
|
||||||
|
mc.netConn, err = dial(mc.cfg.addr)
|
||||||
|
} else {
|
||||||
|
nd := net.Dialer{Timeout: mc.cfg.timeout}
|
||||||
|
mc.netConn, err = nd.Dial(mc.cfg.net, mc.cfg.addr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable TCP Keepalives on TCP connections
|
||||||
|
if tc, ok := mc.netConn.(*net.TCPConn); ok {
|
||||||
|
if err := tc.SetKeepAlive(true); err != nil {
|
||||||
|
// Don't send COM_QUIT before handshake.
|
||||||
|
mc.netConn.Close()
|
||||||
|
mc.netConn = nil
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mc.buf = newBuffer(mc.netConn)
|
||||||
|
|
||||||
|
// Reading Handshake Initialization Packet
|
||||||
|
cipher, err := mc.readInitPacket()
|
||||||
|
if err != nil {
|
||||||
|
mc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send Client Authentication Packet
|
||||||
|
if err = mc.writeAuthPacket(cipher); err != nil {
|
||||||
|
mc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read Result Packet
|
||||||
|
err = mc.readResultOK()
|
||||||
|
if err != nil {
|
||||||
|
// Retry with old authentication method, if allowed
|
||||||
|
if mc.cfg != nil && mc.cfg.allowOldPasswords && err == ErrOldPassword {
|
||||||
|
if err = mc.writeOldAuthPacket(cipher); err != nil {
|
||||||
|
mc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = mc.readResultOK(); err != nil {
|
||||||
|
mc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get max allowed packet size
|
||||||
|
maxap, err := mc.getSystemVar("max_allowed_packet")
|
||||||
|
if err != nil {
|
||||||
|
mc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mc.maxPacketAllowed = stringToInt(maxap) - 1
|
||||||
|
if mc.maxPacketAllowed < maxPacketSize {
|
||||||
|
mc.maxWriteSize = mc.maxPacketAllowed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle DSN Params
|
||||||
|
err = mc.handleParams()
|
||||||
|
if err != nil {
|
||||||
|
mc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sql.Register("mysql", &MySQLDriver{})
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,129 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Various errors the driver might return. Can change between driver versions.
|
||||||
|
var (
|
||||||
|
ErrInvalidConn = errors.New("Invalid Connection")
|
||||||
|
ErrMalformPkt = errors.New("Malformed Packet")
|
||||||
|
ErrNoTLS = errors.New("TLS encryption requested but server does not support TLS")
|
||||||
|
ErrOldPassword = errors.New("This server only supports the insecure old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
|
||||||
|
ErrOldProtocol = errors.New("MySQL-Server does not support required Protocol 41+")
|
||||||
|
ErrPktSync = errors.New("Commands out of sync. You can't run this command now")
|
||||||
|
ErrPktSyncMul = errors.New("Commands out of sync. Did you run multiple statements at once?")
|
||||||
|
ErrPktTooLarge = errors.New("Packet for query is too large. You can change this value on the server by adjusting the 'max_allowed_packet' variable.")
|
||||||
|
ErrBusyBuffer = errors.New("Busy buffer")
|
||||||
|
)
|
||||||
|
|
||||||
|
var errLog Logger = log.New(os.Stderr, "[MySQL] ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||||
|
|
||||||
|
// Logger is used to log critical error messages.
|
||||||
|
type Logger interface {
|
||||||
|
Print(v ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger is used to set the logger for critical errors.
|
||||||
|
// The initial logger is os.Stderr.
|
||||||
|
func SetLogger(logger Logger) error {
|
||||||
|
if logger == nil {
|
||||||
|
return errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
errLog = logger
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MySQLError is an error type which represents a single MySQL error
|
||||||
|
type MySQLError struct {
|
||||||
|
Number uint16
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (me *MySQLError) Error() string {
|
||||||
|
return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MySQLWarnings is an error type which represents a group of one or more MySQL
|
||||||
|
// warnings
|
||||||
|
type MySQLWarnings []MySQLWarning
|
||||||
|
|
||||||
|
func (mws MySQLWarnings) Error() string {
|
||||||
|
var msg string
|
||||||
|
for i, warning := range mws {
|
||||||
|
if i > 0 {
|
||||||
|
msg += "\r\n"
|
||||||
|
}
|
||||||
|
msg += fmt.Sprintf(
|
||||||
|
"%s %s: %s",
|
||||||
|
warning.Level,
|
||||||
|
warning.Code,
|
||||||
|
warning.Message,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// MySQLWarning is an error type which represents a single MySQL warning.
|
||||||
|
// Warnings are returned in groups only. See MySQLWarnings
|
||||||
|
type MySQLWarning struct {
|
||||||
|
Level string
|
||||||
|
Code string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) getWarnings() (err error) {
|
||||||
|
rows, err := mc.Query("SHOW WARNINGS", nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var warnings = MySQLWarnings{}
|
||||||
|
var values = make([]driver.Value, 3)
|
||||||
|
|
||||||
|
for {
|
||||||
|
err = rows.Next(values)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
warning := MySQLWarning{}
|
||||||
|
|
||||||
|
if raw, ok := values[0].([]byte); ok {
|
||||||
|
warning.Level = string(raw)
|
||||||
|
} else {
|
||||||
|
warning.Level = fmt.Sprintf("%s", values[0])
|
||||||
|
}
|
||||||
|
if raw, ok := values[1].([]byte); ok {
|
||||||
|
warning.Code = string(raw)
|
||||||
|
} else {
|
||||||
|
warning.Code = fmt.Sprintf("%s", values[1])
|
||||||
|
}
|
||||||
|
if raw, ok := values[2].([]byte); ok {
|
||||||
|
warning.Message = string(raw)
|
||||||
|
} else {
|
||||||
|
warning.Message = fmt.Sprintf("%s", values[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
warnings = append(warnings, warning)
|
||||||
|
|
||||||
|
case io.EOF:
|
||||||
|
return warnings
|
||||||
|
|
||||||
|
default:
|
||||||
|
rows.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestErrorsSetLogger(t *testing.T) {
|
||||||
|
previous := errLog
|
||||||
|
defer func() {
|
||||||
|
errLog = previous
|
||||||
|
}()
|
||||||
|
|
||||||
|
// set up logger
|
||||||
|
const expected = "prefix: test\n"
|
||||||
|
buffer := bytes.NewBuffer(make([]byte, 0, 64))
|
||||||
|
logger := log.New(buffer, "prefix: ", 0)
|
||||||
|
|
||||||
|
// print
|
||||||
|
SetLogger(logger)
|
||||||
|
errLog.Print("test")
|
||||||
|
|
||||||
|
// check result
|
||||||
|
if actual := buffer.String(); actual != expected {
|
||||||
|
t.Errorf("expected %q, got %q", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorsStrictIgnoreNotes(t *testing.T) {
|
||||||
|
runTests(t, dsn+"&sql_notes=false", func(dbt *DBTest) {
|
||||||
|
dbt.mustExec("DROP TABLE IF EXISTS does_not_exist")
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fileRegister map[string]bool
|
||||||
|
readerRegister map[string]func() io.Reader
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterLocalFile adds the given file to the file whitelist,
|
||||||
|
// so that it can be used by "LOAD DATA LOCAL INFILE <filepath>".
|
||||||
|
// Alternatively you can allow the use of all local files with
|
||||||
|
// the DSN parameter 'allowAllFiles=true'
|
||||||
|
//
|
||||||
|
// filePath := "/home/gopher/data.csv"
|
||||||
|
// mysql.RegisterLocalFile(filePath)
|
||||||
|
// err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo")
|
||||||
|
// if err != nil {
|
||||||
|
// ...
|
||||||
|
//
|
||||||
|
func RegisterLocalFile(filePath string) {
|
||||||
|
// lazy map init
|
||||||
|
if fileRegister == nil {
|
||||||
|
fileRegister = make(map[string]bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileRegister[strings.Trim(filePath, `"`)] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeregisterLocalFile removes the given filepath from the whitelist.
|
||||||
|
func DeregisterLocalFile(filePath string) {
|
||||||
|
delete(fileRegister, strings.Trim(filePath, `"`))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterReaderHandler registers a handler function which is used
|
||||||
|
// to receive a io.Reader.
|
||||||
|
// The Reader can be used by "LOAD DATA LOCAL INFILE Reader::<name>".
|
||||||
|
// If the handler returns a io.ReadCloser Close() is called when the
|
||||||
|
// request is finished.
|
||||||
|
//
|
||||||
|
// mysql.RegisterReaderHandler("data", func() io.Reader {
|
||||||
|
// var csvReader io.Reader // Some Reader that returns CSV data
|
||||||
|
// ... // Open Reader here
|
||||||
|
// return csvReader
|
||||||
|
// })
|
||||||
|
// err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo")
|
||||||
|
// if err != nil {
|
||||||
|
// ...
|
||||||
|
//
|
||||||
|
func RegisterReaderHandler(name string, handler func() io.Reader) {
|
||||||
|
// lazy map init
|
||||||
|
if readerRegister == nil {
|
||||||
|
readerRegister = make(map[string]func() io.Reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
readerRegister[name] = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeregisterReaderHandler removes the ReaderHandler function with
|
||||||
|
// the given name from the registry.
|
||||||
|
func DeregisterReaderHandler(name string) {
|
||||||
|
delete(readerRegister, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deferredClose(err *error, closer io.Closer) {
|
||||||
|
closeErr := closer.Close()
|
||||||
|
if *err == nil {
|
||||||
|
*err = closeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
|
||||||
|
var rdr io.Reader
|
||||||
|
var data []byte
|
||||||
|
|
||||||
|
if strings.HasPrefix(name, "Reader::") { // io.Reader
|
||||||
|
name = name[8:]
|
||||||
|
if handler, inMap := readerRegister[name]; inMap {
|
||||||
|
rdr = handler()
|
||||||
|
if rdr != nil {
|
||||||
|
data = make([]byte, 4+mc.maxWriteSize)
|
||||||
|
|
||||||
|
if cl, ok := rdr.(io.Closer); ok {
|
||||||
|
defer deferredClose(&err, cl)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("Reader '%s' is <nil>", name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("Reader '%s' is not registered", name)
|
||||||
|
}
|
||||||
|
} else { // File
|
||||||
|
name = strings.Trim(name, `"`)
|
||||||
|
if mc.cfg.allowAllFiles || fileRegister[name] {
|
||||||
|
var file *os.File
|
||||||
|
var fi os.FileInfo
|
||||||
|
|
||||||
|
if file, err = os.Open(name); err == nil {
|
||||||
|
defer deferredClose(&err, file)
|
||||||
|
|
||||||
|
// get file size
|
||||||
|
if fi, err = file.Stat(); err == nil {
|
||||||
|
rdr = file
|
||||||
|
if fileSize := int(fi.Size()); fileSize <= mc.maxWriteSize {
|
||||||
|
data = make([]byte, 4+fileSize)
|
||||||
|
} else if fileSize <= mc.maxPacketAllowed {
|
||||||
|
data = make([]byte, 4+mc.maxWriteSize)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("Local File '%s' too large: Size: %d, Max: %d", name, fileSize, mc.maxPacketAllowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("Local File '%s' is not registered. Use the DSN parameter 'allowAllFiles=true' to allow all files", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send content packets
|
||||||
|
if err == nil {
|
||||||
|
var n int
|
||||||
|
for err == nil {
|
||||||
|
n, err = rdr.Read(data[4:])
|
||||||
|
if n > 0 {
|
||||||
|
if ioErr := mc.writePacket(data[:4+n]); ioErr != nil {
|
||||||
|
return ioErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send empty packet (termination)
|
||||||
|
if data == nil {
|
||||||
|
data = make([]byte, 4)
|
||||||
|
}
|
||||||
|
if ioErr := mc.writePacket(data[:4]); ioErr != nil {
|
||||||
|
return ioErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// read OK packet
|
||||||
|
if err == nil {
|
||||||
|
return mc.readResultOK()
|
||||||
|
} else {
|
||||||
|
mc.readPacket()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,22 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
type mysqlResult struct {
|
||||||
|
affectedRows int64
|
||||||
|
insertId int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res *mysqlResult) LastInsertId() (int64, error) {
|
||||||
|
return res.insertId, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res *mysqlResult) RowsAffected() (int64, error) {
|
||||||
|
return res.affectedRows, nil
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mysqlField struct {
|
||||||
|
tableName string
|
||||||
|
name string
|
||||||
|
flags fieldFlag
|
||||||
|
fieldType byte
|
||||||
|
decimals byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type mysqlRows struct {
|
||||||
|
mc *mysqlConn
|
||||||
|
columns []mysqlField
|
||||||
|
}
|
||||||
|
|
||||||
|
type binaryRows struct {
|
||||||
|
mysqlRows
|
||||||
|
}
|
||||||
|
|
||||||
|
type textRows struct {
|
||||||
|
mysqlRows
|
||||||
|
}
|
||||||
|
|
||||||
|
type emptyRows struct{}
|
||||||
|
|
||||||
|
func (rows *mysqlRows) Columns() []string {
|
||||||
|
columns := make([]string, len(rows.columns))
|
||||||
|
if rows.mc.cfg.columnsWithAlias {
|
||||||
|
for i := range columns {
|
||||||
|
columns[i] = rows.columns[i].tableName + "." + rows.columns[i].name
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := range columns {
|
||||||
|
columns[i] = rows.columns[i].name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return columns
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *mysqlRows) Close() error {
|
||||||
|
mc := rows.mc
|
||||||
|
if mc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if mc.netConn == nil {
|
||||||
|
return ErrInvalidConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove unread packets from stream
|
||||||
|
err := mc.readUntilEOF()
|
||||||
|
rows.mc = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *binaryRows) Next(dest []driver.Value) error {
|
||||||
|
if mc := rows.mc; mc != nil {
|
||||||
|
if mc.netConn == nil {
|
||||||
|
return ErrInvalidConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch next row from stream
|
||||||
|
if err := rows.readRow(dest); err != io.EOF {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rows.mc = nil
|
||||||
|
}
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *textRows) Next(dest []driver.Value) error {
|
||||||
|
if mc := rows.mc; mc != nil {
|
||||||
|
if mc.netConn == nil {
|
||||||
|
return ErrInvalidConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch next row from stream
|
||||||
|
if err := rows.readRow(dest); err != io.EOF {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rows.mc = nil
|
||||||
|
}
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows emptyRows) Columns() []string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows emptyRows) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows emptyRows) Next(dest []driver.Value) error {
|
||||||
|
return io.EOF
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mysqlStmt struct {
|
||||||
|
mc *mysqlConn
|
||||||
|
id uint32
|
||||||
|
paramCount int
|
||||||
|
columns []mysqlField // cached from the first query
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *mysqlStmt) Close() error {
|
||||||
|
if stmt.mc == nil || stmt.mc.netConn == nil {
|
||||||
|
errLog.Print(ErrInvalidConn)
|
||||||
|
return driver.ErrBadConn
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id)
|
||||||
|
stmt.mc = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *mysqlStmt) NumInput() int {
|
||||||
|
return stmt.paramCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||||
|
if stmt.mc.netConn == nil {
|
||||||
|
errLog.Print(ErrInvalidConn)
|
||||||
|
return nil, driver.ErrBadConn
|
||||||
|
}
|
||||||
|
// Send command
|
||||||
|
err := stmt.writeExecutePacket(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mc := stmt.mc
|
||||||
|
|
||||||
|
mc.affectedRows = 0
|
||||||
|
mc.insertId = 0
|
||||||
|
|
||||||
|
// Read Result
|
||||||
|
resLen, err := mc.readResultSetHeaderPacket()
|
||||||
|
if err == nil {
|
||||||
|
if resLen > 0 {
|
||||||
|
// Columns
|
||||||
|
err = mc.readUntilEOF()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rows
|
||||||
|
err = mc.readUntilEOF()
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
return &mysqlResult{
|
||||||
|
affectedRows: int64(mc.affectedRows),
|
||||||
|
insertId: int64(mc.insertId),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||||
|
if stmt.mc.netConn == nil {
|
||||||
|
errLog.Print(ErrInvalidConn)
|
||||||
|
return nil, driver.ErrBadConn
|
||||||
|
}
|
||||||
|
// Send command
|
||||||
|
err := stmt.writeExecutePacket(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mc := stmt.mc
|
||||||
|
|
||||||
|
// Read Result
|
||||||
|
resLen, err := mc.readResultSetHeaderPacket()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := new(binaryRows)
|
||||||
|
rows.mc = mc
|
||||||
|
|
||||||
|
if resLen > 0 {
|
||||||
|
// Columns
|
||||||
|
// If not cached, read them and cache them
|
||||||
|
if stmt.columns == nil {
|
||||||
|
rows.columns, err = mc.readColumns(resLen)
|
||||||
|
stmt.columns = rows.columns
|
||||||
|
} else {
|
||||||
|
rows.columns = stmt.columns
|
||||||
|
err = mc.readUntilEOF()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows, err
|
||||||
|
}
|
|
@ -0,0 +1,387 @@
|
||||||
|
!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
|
||||||
|
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
|
||||||
|
!_TAG_OUTPUT_MODE u-ctags /u-ctags or e-ctags/
|
||||||
|
!_TAG_PROGRAM_AUTHOR Universal Ctags Team //
|
||||||
|
!_TAG_PROGRAM_NAME Universal Ctags /Derived from Exuberant Ctags/
|
||||||
|
!_TAG_PROGRAM_URL https://ctags.io/ /official site/
|
||||||
|
!_TAG_PROGRAM_VERSION 0.0.0 /7fcdea3/
|
||||||
|
Begin connection.go /^func (mc *mysqlConn) Begin() (driver.Tx, error) {$/;" f
|
||||||
|
BenchmarkExec benchmark_test.go /^func BenchmarkExec(b *testing.B) {$/;" f
|
||||||
|
BenchmarkParseDSN utils_test.go /^func BenchmarkParseDSN(b *testing.B) {$/;" f
|
||||||
|
BenchmarkQuery benchmark_test.go /^func BenchmarkQuery(b *testing.B) {$/;" f
|
||||||
|
BenchmarkRoundtripBin benchmark_test.go /^func BenchmarkRoundtripBin(b *testing.B) {$/;" f
|
||||||
|
BenchmarkRoundtripTxt benchmark_test.go /^func BenchmarkRoundtripTxt(b *testing.B) {$/;" f
|
||||||
|
Binary driver_test.go /^func (t timeMode) Binary() bool {$/;" f
|
||||||
|
Close connection.go /^func (mc *mysqlConn) Close() (err error) {$/;" f
|
||||||
|
Close rows.go /^func (rows *mysqlRows) Close() error {$/;" f
|
||||||
|
Close rows.go /^func (rows emptyRows) Close() error {$/;" f
|
||||||
|
Close statement.go /^func (stmt *mysqlStmt) Close() error {$/;" f
|
||||||
|
Code errors.go /^ Code string$/;" m struct:MySQLWarning
|
||||||
|
Columns rows.go /^func (rows *mysqlRows) Columns() []string {$/;" f
|
||||||
|
Columns rows.go /^func (rows emptyRows) Columns() []string {$/;" f
|
||||||
|
Commit transaction.go /^func (tx *mysqlTx) Commit() (err error) {$/;" f
|
||||||
|
DBTest driver_test.go /^type DBTest struct {$/;" s
|
||||||
|
DeregisterLocalFile infile.go /^func DeregisterLocalFile(filePath string) {$/;" f
|
||||||
|
DeregisterReaderHandler infile.go /^func DeregisterReaderHandler(name string) {$/;" f
|
||||||
|
DeregisterTLSConfig utils.go /^func DeregisterTLSConfig(key string) {$/;" f
|
||||||
|
DialFunc driver.go /^type DialFunc func(addr string) (net.Conn, error)$/;" t
|
||||||
|
ErrBusyBuffer errors.go /^ ErrBusyBuffer = errors.New("Busy buffer")$/;" v
|
||||||
|
ErrInvalidConn errors.go /^ ErrInvalidConn = errors.New("Invalid Connection")$/;" v
|
||||||
|
ErrMalformPkt errors.go /^ ErrMalformPkt = errors.New("Malformed Packet")$/;" v
|
||||||
|
ErrNoTLS errors.go /^ ErrNoTLS = errors.New("TLS encryption requested but server does not support TLS")$/;" v
|
||||||
|
ErrOldPassword errors.go /^ ErrOldPassword = errors.New("This server only supports the insecure old password authentication/;" v
|
||||||
|
ErrOldProtocol errors.go /^ ErrOldProtocol = errors.New("MySQL-Server does not support required Protocol 41+")$/;" v
|
||||||
|
ErrPktSync errors.go /^ ErrPktSync = errors.New("Commands out of sync. You can't run this command now")$/;" v
|
||||||
|
ErrPktSyncMul errors.go /^ ErrPktSyncMul = errors.New("Commands out of sync. Did you run multiple statements at once?")$/;" v
|
||||||
|
ErrPktTooLarge errors.go /^ ErrPktTooLarge = errors.New("Packet for query is too large. You can change this value on the se/;" v
|
||||||
|
Error errors.go /^func (me *MySQLError) Error() string {$/;" f
|
||||||
|
Error errors.go /^func (mws MySQLWarnings) Error() string {$/;" f
|
||||||
|
Exec connection.go /^func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {$/;" f
|
||||||
|
Exec statement.go /^func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {$/;" f
|
||||||
|
LastInsertId result.go /^func (res *mysqlResult) LastInsertId() (int64, error) {$/;" f
|
||||||
|
Level errors.go /^ Level string$/;" m struct:MySQLWarning
|
||||||
|
Logger errors.go /^type Logger interface {$/;" i
|
||||||
|
Message errors.go /^ Message string$/;" m struct:MySQLError
|
||||||
|
Message errors.go /^ Message string$/;" m struct:MySQLWarning
|
||||||
|
MySQLDriver driver.go /^type MySQLDriver struct{}$/;" s
|
||||||
|
MySQLError errors.go /^type MySQLError struct {$/;" s
|
||||||
|
MySQLWarning errors.go /^type MySQLWarning struct {$/;" s
|
||||||
|
MySQLWarnings errors.go /^type MySQLWarnings []MySQLWarning$/;" t
|
||||||
|
Next rows.go /^func (rows *binaryRows) Next(dest []driver.Value) error {$/;" f
|
||||||
|
Next rows.go /^func (rows *textRows) Next(dest []driver.Value) error {$/;" f
|
||||||
|
Next rows.go /^func (rows emptyRows) Next(dest []driver.Value) error {$/;" f
|
||||||
|
NextByte utils.go /^func (r *myRnd) NextByte() byte {$/;" f
|
||||||
|
NullTime utils.go /^type NullTime struct {$/;" s
|
||||||
|
NumInput statement.go /^func (stmt *mysqlStmt) NumInput() int {$/;" f
|
||||||
|
Number errors.go /^ Number uint16$/;" m struct:MySQLError
|
||||||
|
Open driver.go /^func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {$/;" f
|
||||||
|
Prepare connection.go /^func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {$/;" f
|
||||||
|
Query connection.go /^func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {$/;" f
|
||||||
|
Query statement.go /^func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {$/;" f
|
||||||
|
RegisterDial driver.go /^func RegisterDial(net string, dial DialFunc) {$/;" f
|
||||||
|
RegisterLocalFile infile.go /^func RegisterLocalFile(filePath string) {$/;" f
|
||||||
|
RegisterReaderHandler infile.go /^func RegisterReaderHandler(name string, handler func() io.Reader) {$/;" f
|
||||||
|
RegisterTLSConfig utils.go /^func RegisterTLSConfig(key string, config *tls.Config) error {$/;" f
|
||||||
|
Rollback transaction.go /^func (tx *mysqlTx) Rollback() (err error) {$/;" f
|
||||||
|
RowsAffected result.go /^func (res *mysqlResult) RowsAffected() (int64, error) {$/;" f
|
||||||
|
Scan utils.go /^func (nt *NullTime) Scan(value interface{}) (err error) {$/;" f
|
||||||
|
SetLogger errors.go /^func SetLogger(logger Logger) error {$/;" f
|
||||||
|
String driver_test.go /^func (t timeMode) String() string {$/;" f
|
||||||
|
TB benchmark_test.go /^type TB testing.B$/;" t
|
||||||
|
TestCRUD driver_test.go /^func TestCRUD(t *testing.T) {$/;" f
|
||||||
|
TestCharset driver_test.go /^func TestCharset(t *testing.T) {$/;" f
|
||||||
|
TestCloseStmtBeforeRows driver_test.go /^func TestCloseStmtBeforeRows(t *testing.T) {$/;" f
|
||||||
|
TestCollation driver_test.go /^func TestCollation(t *testing.T) {$/;" f
|
||||||
|
TestConcurrent driver_test.go /^func TestConcurrent(t *testing.T) {$/;" f
|
||||||
|
TestCustomDial driver_test.go /^func TestCustomDial(t *testing.T) {$/;" f
|
||||||
|
TestDSNParser utils_test.go /^func TestDSNParser(t *testing.T) {$/;" f
|
||||||
|
TestDSNParserInvalid utils_test.go /^func TestDSNParserInvalid(t *testing.T) {$/;" f
|
||||||
|
TestDSNWithCustomTLS utils_test.go /^func TestDSNWithCustomTLS(t *testing.T) {$/;" f
|
||||||
|
TestDateTime driver_test.go /^func TestDateTime(t *testing.T) {$/;" f
|
||||||
|
TestEmptyQuery driver_test.go /^func TestEmptyQuery(t *testing.T) {$/;" f
|
||||||
|
TestErrorsSetLogger errors_test.go /^func TestErrorsSetLogger(t *testing.T) {$/;" f
|
||||||
|
TestErrorsStrictIgnoreNotes errors_test.go /^func TestErrorsStrictIgnoreNotes(t *testing.T) {$/;" f
|
||||||
|
TestFailingCharset driver_test.go /^func TestFailingCharset(t *testing.T) {$/;" f
|
||||||
|
TestFloat driver_test.go /^func TestFloat(t *testing.T) {$/;" f
|
||||||
|
TestFormatBinaryDateTime utils_test.go /^func TestFormatBinaryDateTime(t *testing.T) {$/;" f
|
||||||
|
TestFoundRows driver_test.go /^func TestFoundRows(t *testing.T) {$/;" f
|
||||||
|
TestInt driver_test.go /^func TestInt(t *testing.T) {$/;" f
|
||||||
|
TestLengthEncodedInteger utils_test.go /^func TestLengthEncodedInteger(t *testing.T) {$/;" f
|
||||||
|
TestLoadData driver_test.go /^func TestLoadData(t *testing.T) {$/;" f
|
||||||
|
TestLongData driver_test.go /^func TestLongData(t *testing.T) {$/;" f
|
||||||
|
TestNULL driver_test.go /^func TestNULL(t *testing.T) {$/;" f
|
||||||
|
TestOldPass utils_test.go /^func TestOldPass(t *testing.T) {$/;" f
|
||||||
|
TestPreparedManyCols driver_test.go /^func TestPreparedManyCols(t *testing.T) {$/;" f
|
||||||
|
TestRawBytesResultExceedsBuffer driver_test.go /^func TestRawBytesResultExceedsBuffer(t *testing.T) {$/;" f
|
||||||
|
TestReuseClosedConnection driver_test.go /^func TestReuseClosedConnection(t *testing.T) {$/;" f
|
||||||
|
TestRowsClose driver_test.go /^func TestRowsClose(t *testing.T) {$/;" f
|
||||||
|
TestScanNullTime utils_test.go /^func TestScanNullTime(t *testing.T) {$/;" f
|
||||||
|
TestStmtMultiRows driver_test.go /^func TestStmtMultiRows(t *testing.T) {$/;" f
|
||||||
|
TestStrict driver_test.go /^func TestStrict(t *testing.T) {$/;" f
|
||||||
|
TestString driver_test.go /^func TestString(t *testing.T) {$/;" f
|
||||||
|
TestTLS driver_test.go /^func TestTLS(t *testing.T) {$/;" f
|
||||||
|
TestTimestampMicros driver_test.go /^func TestTimestampMicros(t *testing.T) {$/;" f
|
||||||
|
TestTimezoneConversion driver_test.go /^func TestTimezoneConversion(t *testing.T) {$/;" f
|
||||||
|
Time utils.go /^ Time time.Time$/;" m struct:NullTime
|
||||||
|
Valid utils.go /^ Valid bool \/\/ Valid is true if Time is not NULL$/;" m struct:NullTime
|
||||||
|
Value utils.go /^func (nt NullTime) Value() (driver.Value, error) {$/;" f
|
||||||
|
addr connection.go /^ addr string$/;" m struct:config
|
||||||
|
addr driver_test.go /^ addr string$/;" v
|
||||||
|
affectedRows connection.go /^ affectedRows uint64$/;" m struct:mysqlConn
|
||||||
|
affectedRows result.go /^ affectedRows int64$/;" m struct:mysqlResult
|
||||||
|
allowAllFiles connection.go /^ allowAllFiles bool$/;" m struct:config
|
||||||
|
allowOldPasswords connection.go /^ allowOldPasswords bool$/;" m struct:config
|
||||||
|
appendLengthEncodedInteger utils.go /^func appendLengthEncodedInteger(b []byte, n uint64) []byte {$/;" f
|
||||||
|
available driver_test.go /^ available bool$/;" v
|
||||||
|
binaryRows rows.go /^type binaryRows struct {$/;" s
|
||||||
|
binaryString driver_test.go /^ binaryString timeMode = iota$/;" c
|
||||||
|
binaryTime driver_test.go /^ binaryTime$/;" c
|
||||||
|
buf buffer.go /^ buf []byte$/;" m struct:buffer
|
||||||
|
buf connection.go /^ buf buffer$/;" m struct:mysqlConn
|
||||||
|
buffer buffer.go /^type buffer struct {$/;" s
|
||||||
|
cfg connection.go /^ cfg *config$/;" m struct:mysqlConn
|
||||||
|
check benchmark_test.go /^func (tb *TB) check(err error) {$/;" f
|
||||||
|
checkDB benchmark_test.go /^func (tb *TB) checkDB(db *sql.DB, err error) *sql.DB {$/;" f
|
||||||
|
checkRows benchmark_test.go /^func (tb *TB) checkRows(rows *sql.Rows, err error) *sql.Rows {$/;" f
|
||||||
|
checkStmt benchmark_test.go /^func (tb *TB) checkStmt(stmt *sql.Stmt, err error) *sql.Stmt {$/;" f
|
||||||
|
clientCompress const.go /^ clientCompress$/;" c
|
||||||
|
clientConnectWithDB const.go /^ clientConnectWithDB$/;" c
|
||||||
|
clientFlag const.go /^type clientFlag uint32$/;" t
|
||||||
|
clientFoundRows connection.go /^ clientFoundRows bool$/;" m struct:config
|
||||||
|
clientFoundRows const.go /^ clientFoundRows$/;" c
|
||||||
|
clientIgnoreSIGPIPE const.go /^ clientIgnoreSIGPIPE$/;" c
|
||||||
|
clientIgnoreSpace const.go /^ clientIgnoreSpace$/;" c
|
||||||
|
clientInteractive const.go /^ clientInteractive$/;" c
|
||||||
|
clientLocalFiles const.go /^ clientLocalFiles$/;" c
|
||||||
|
clientLongFlag const.go /^ clientLongFlag$/;" c
|
||||||
|
clientLongPassword const.go /^ clientLongPassword clientFlag = 1 << iota$/;" c
|
||||||
|
clientMultiResults const.go /^ clientMultiResults$/;" c
|
||||||
|
clientMultiStatements const.go /^ clientMultiStatements$/;" c
|
||||||
|
clientNoSchema const.go /^ clientNoSchema$/;" c
|
||||||
|
clientODBC const.go /^ clientODBC$/;" c
|
||||||
|
clientProtocol41 const.go /^ clientProtocol41$/;" c
|
||||||
|
clientReserved const.go /^ clientReserved$/;" c
|
||||||
|
clientSSL const.go /^ clientSSL$/;" c
|
||||||
|
clientSecureConn const.go /^ clientSecureConn$/;" c
|
||||||
|
clientTransactions const.go /^ clientTransactions$/;" c
|
||||||
|
collation connection.go /^ collation uint8$/;" m struct:config
|
||||||
|
collations collations.go /^var collations = map[string]byte{$/;" v
|
||||||
|
columns rows.go /^ columns []mysqlField$/;" m struct:mysqlRows
|
||||||
|
columns statement.go /^ columns []mysqlField \/\/ cached from the first query$/;" m struct:mysqlStmt
|
||||||
|
columnsWithAlias connection.go /^ columnsWithAlias bool$/;" m struct:config
|
||||||
|
comBinlogDump const.go /^ comBinlogDump$/;" c
|
||||||
|
comChangeUser const.go /^ comChangeUser$/;" c
|
||||||
|
comConnect const.go /^ comConnect$/;" c
|
||||||
|
comConnectOut const.go /^ comConnectOut$/;" c
|
||||||
|
comCreateDB const.go /^ comCreateDB$/;" c
|
||||||
|
comDebug const.go /^ comDebug$/;" c
|
||||||
|
comDelayedInsert const.go /^ comDelayedInsert$/;" c
|
||||||
|
comDropDB const.go /^ comDropDB$/;" c
|
||||||
|
comFieldList const.go /^ comFieldList$/;" c
|
||||||
|
comInitDB const.go /^ comInitDB$/;" c
|
||||||
|
comPing const.go /^ comPing$/;" c
|
||||||
|
comProcessInfo const.go /^ comProcessInfo$/;" c
|
||||||
|
comProcessKill const.go /^ comProcessKill$/;" c
|
||||||
|
comQuery const.go /^ comQuery$/;" c
|
||||||
|
comQuit const.go /^ comQuit byte = iota + 1$/;" c
|
||||||
|
comRefresh const.go /^ comRefresh$/;" c
|
||||||
|
comRegisterSlave const.go /^ comRegisterSlave$/;" c
|
||||||
|
comSetOption const.go /^ comSetOption$/;" c
|
||||||
|
comShutdown const.go /^ comShutdown$/;" c
|
||||||
|
comStatistics const.go /^ comStatistics$/;" c
|
||||||
|
comStmtClose const.go /^ comStmtClose$/;" c
|
||||||
|
comStmtExecute const.go /^ comStmtExecute$/;" c
|
||||||
|
comStmtFetch const.go /^ comStmtFetch$/;" c
|
||||||
|
comStmtPrepare const.go /^ comStmtPrepare$/;" c
|
||||||
|
comStmtReset const.go /^ comStmtReset$/;" c
|
||||||
|
comStmtSendLongData const.go /^ comStmtSendLongData$/;" c
|
||||||
|
comTableDump const.go /^ comTableDump$/;" c
|
||||||
|
comTime const.go /^ comTime$/;" c
|
||||||
|
concurrencyLevel benchmark_test.go /^const concurrencyLevel = 10$/;" c
|
||||||
|
config connection.go /^type config struct {$/;" s
|
||||||
|
db driver_test.go /^ db *sql.DB$/;" m struct:DBTest
|
||||||
|
dbname connection.go /^ dbname string$/;" m struct:config
|
||||||
|
dbname driver_test.go /^ dbname string$/;" v
|
||||||
|
dbtype driver_test.go /^ dbtype string$/;" m struct:timeTests
|
||||||
|
decimals rows.go /^ decimals byte$/;" m struct:mysqlField
|
||||||
|
defaultBufSize buffer.go /^const defaultBufSize = 4096$/;" c
|
||||||
|
defaultCollation collations.go /^const defaultCollation byte = 33 \/\/ utf8_general_ci$/;" c
|
||||||
|
deferredClose infile.go /^func deferredClose(err *error, closer io.Closer) {$/;" f
|
||||||
|
dials driver.go /^var dials map[string]DialFunc$/;" v
|
||||||
|
dsn driver_test.go /^ dsn string$/;" v
|
||||||
|
emptyRows rows.go /^type emptyRows struct{}$/;" s
|
||||||
|
errInvalidDSNAddr utils.go /^ errInvalidDSNAddr = errors.New("Invalid DSN: Network Address not terminated (missing closi/;" v
|
||||||
|
errInvalidDSNNoSlash utils.go /^ errInvalidDSNNoSlash = errors.New("Invalid DSN: Missing the slash separating the database nam/;" v
|
||||||
|
errInvalidDSNUnescaped utils.go /^ errInvalidDSNUnescaped = errors.New("Invalid DSN: Did you forget to escape a param value?")$/;" v
|
||||||
|
errLog errors.go /^var errLog Logger = log.New(os.Stderr, "[MySQL] ", log.Ldate|log.Ltime|log.Lshortfile)$/;" v
|
||||||
|
exec connection.go /^func (mc *mysqlConn) exec(query string) error {$/;" f
|
||||||
|
fail driver_test.go /^func (dbt *DBTest) fail(method, query string, err error) {$/;" f
|
||||||
|
fieldFlag const.go /^type fieldFlag uint16$/;" t
|
||||||
|
fieldType rows.go /^ fieldType byte$/;" m struct:mysqlField
|
||||||
|
fieldTypeBLOB const.go /^ fieldTypeBLOB$/;" c
|
||||||
|
fieldTypeBit const.go /^ fieldTypeBit$/;" c
|
||||||
|
fieldTypeDate const.go /^ fieldTypeDate$/;" c
|
||||||
|
fieldTypeDateTime const.go /^ fieldTypeDateTime$/;" c
|
||||||
|
fieldTypeDecimal const.go /^ fieldTypeDecimal byte = iota$/;" c
|
||||||
|
fieldTypeDouble const.go /^ fieldTypeDouble$/;" c
|
||||||
|
fieldTypeEnum const.go /^ fieldTypeEnum$/;" c
|
||||||
|
fieldTypeFloat const.go /^ fieldTypeFloat$/;" c
|
||||||
|
fieldTypeGeometry const.go /^ fieldTypeGeometry$/;" c
|
||||||
|
fieldTypeInt24 const.go /^ fieldTypeInt24$/;" c
|
||||||
|
fieldTypeLong const.go /^ fieldTypeLong$/;" c
|
||||||
|
fieldTypeLongBLOB const.go /^ fieldTypeLongBLOB$/;" c
|
||||||
|
fieldTypeLongLong const.go /^ fieldTypeLongLong$/;" c
|
||||||
|
fieldTypeMediumBLOB const.go /^ fieldTypeMediumBLOB$/;" c
|
||||||
|
fieldTypeNULL const.go /^ fieldTypeNULL$/;" c
|
||||||
|
fieldTypeNewDate const.go /^ fieldTypeNewDate$/;" c
|
||||||
|
fieldTypeNewDecimal const.go /^ fieldTypeNewDecimal byte = iota + 0xf6$/;" c
|
||||||
|
fieldTypeSet const.go /^ fieldTypeSet$/;" c
|
||||||
|
fieldTypeShort const.go /^ fieldTypeShort$/;" c
|
||||||
|
fieldTypeString const.go /^ fieldTypeString$/;" c
|
||||||
|
fieldTypeTime const.go /^ fieldTypeTime$/;" c
|
||||||
|
fieldTypeTimestamp const.go /^ fieldTypeTimestamp$/;" c
|
||||||
|
fieldTypeTiny const.go /^ fieldTypeTiny$/;" c
|
||||||
|
fieldTypeTinyBLOB const.go /^ fieldTypeTinyBLOB$/;" c
|
||||||
|
fieldTypeVarChar const.go /^ fieldTypeVarChar$/;" c
|
||||||
|
fieldTypeVarString const.go /^ fieldTypeVarString$/;" c
|
||||||
|
fieldTypeYear const.go /^ fieldTypeYear$/;" c
|
||||||
|
fileRegister infile.go /^ fileRegister map[string]bool$/;" v
|
||||||
|
fill buffer.go /^func (b *buffer) fill(need int) error {$/;" f
|
||||||
|
flagAutoIncrement const.go /^ flagAutoIncrement$/;" c
|
||||||
|
flagBLOB const.go /^ flagBLOB$/;" c
|
||||||
|
flagBinary const.go /^ flagBinary$/;" c
|
||||||
|
flagEnum const.go /^ flagEnum$/;" c
|
||||||
|
flagMultipleKey const.go /^ flagMultipleKey$/;" c
|
||||||
|
flagNotNULL const.go /^ flagNotNULL fieldFlag = 1 << iota$/;" c
|
||||||
|
flagPriKey const.go /^ flagPriKey$/;" c
|
||||||
|
flagSet const.go /^ flagSet$/;" c
|
||||||
|
flagTimestamp const.go /^ flagTimestamp$/;" c
|
||||||
|
flagUniqueKey const.go /^ flagUniqueKey$/;" c
|
||||||
|
flagUnknown1 const.go /^ flagUnknown1$/;" c
|
||||||
|
flagUnknown2 const.go /^ flagUnknown2$/;" c
|
||||||
|
flagUnknown3 const.go /^ flagUnknown3$/;" c
|
||||||
|
flagUnknown4 const.go /^ flagUnknown4$/;" c
|
||||||
|
flagUnsigned const.go /^ flagUnsigned$/;" c
|
||||||
|
flagZeroFill const.go /^ flagZeroFill$/;" c
|
||||||
|
flags connection.go /^ flags clientFlag$/;" m struct:mysqlConn
|
||||||
|
flags rows.go /^ flags fieldFlag$/;" m struct:mysqlField
|
||||||
|
formatBinaryDateTime utils.go /^func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {$/;" f
|
||||||
|
genQuery driver_test.go /^func (t timeTest) genQuery(dbtype string, mode timeMode) string {$/;" f
|
||||||
|
getSystemVar connection.go /^func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {$/;" f
|
||||||
|
getWarnings errors.go /^func (mc *mysqlConn) getWarnings() (err error) {$/;" f
|
||||||
|
handleErrorPacket packets.go /^func (mc *mysqlConn) handleErrorPacket(data []byte) error {$/;" f
|
||||||
|
handleInFileRequest infile.go /^func (mc *mysqlConn) handleInFileRequest(name string) (err error) {$/;" f
|
||||||
|
handleOkPacket packets.go /^func (mc *mysqlConn) handleOkPacket(data []byte) error {$/;" f
|
||||||
|
handleParams connection.go /^func (mc *mysqlConn) handleParams() (err error) {$/;" f
|
||||||
|
iEOF const.go /^ iEOF byte = 0xfe$/;" c
|
||||||
|
iERR const.go /^ iERR byte = 0xff$/;" c
|
||||||
|
iLocalInFile const.go /^ iLocalInFile byte = 0xfb$/;" c
|
||||||
|
iOK const.go /^ iOK byte = 0x00$/;" c
|
||||||
|
id statement.go /^ id uint32$/;" m struct:mysqlStmt
|
||||||
|
idx buffer.go /^ idx int$/;" m struct:buffer
|
||||||
|
init appengine.go /^func init() {$/;" f
|
||||||
|
init driver.go /^func init() {$/;" f
|
||||||
|
init driver_test.go /^func init() {$/;" f
|
||||||
|
init utils.go /^func init() {$/;" f
|
||||||
|
initDB benchmark_test.go /^func initDB(b *testing.B, queries ...string) *sql.DB {$/;" f
|
||||||
|
initRoundtripBenchmarks benchmark_test.go /^func initRoundtripBenchmarks() ([]byte, int, int) {$/;" f
|
||||||
|
insertId connection.go /^ insertId uint64$/;" m struct:mysqlConn
|
||||||
|
insertId result.go /^ insertId int64$/;" m struct:mysqlResult
|
||||||
|
length buffer.go /^ length int$/;" m struct:buffer
|
||||||
|
loc connection.go /^ loc *time.Location$/;" m struct:config
|
||||||
|
maxPacketAllowed connection.go /^ maxPacketAllowed int$/;" m struct:mysqlConn
|
||||||
|
maxPacketSize const.go /^ maxPacketSize = 1<<24 - 1$/;" c
|
||||||
|
maxWriteSize connection.go /^ maxWriteSize int$/;" m struct:mysqlConn
|
||||||
|
mc rows.go /^ mc *mysqlConn$/;" m struct:mysqlRows
|
||||||
|
mc statement.go /^ mc *mysqlConn$/;" m struct:mysqlStmt
|
||||||
|
mc transaction.go /^ mc *mysqlConn$/;" m struct:mysqlTx
|
||||||
|
minProtocolVersion const.go /^ minProtocolVersion byte = 10$/;" c
|
||||||
|
mustExec driver_test.go /^func (dbt *DBTest) mustExec(query string, args ...interface{}) (res sql.Result) {$/;" f
|
||||||
|
mustQuery driver_test.go /^func (dbt *DBTest) mustQuery(query string, args ...interface{}) (rows *sql.Rows) {$/;" f
|
||||||
|
myRnd utils.go /^type myRnd struct {$/;" s
|
||||||
|
myRndMaxVal utils.go /^const myRndMaxVal = 0x3FFFFFFF$/;" c
|
||||||
|
mysql appengine.go /^package mysql$/;" p
|
||||||
|
mysql benchmark_test.go /^package mysql$/;" p
|
||||||
|
mysql buffer.go /^package mysql$/;" p
|
||||||
|
mysql collations.go /^package mysql$/;" p
|
||||||
|
mysql connection.go /^package mysql$/;" p
|
||||||
|
mysql const.go /^package mysql$/;" p
|
||||||
|
mysql driver.go /^package mysql$/;" p
|
||||||
|
mysql driver_test.go /^package mysql$/;" p
|
||||||
|
mysql errors.go /^package mysql$/;" p
|
||||||
|
mysql errors_test.go /^package mysql$/;" p
|
||||||
|
mysql infile.go /^package mysql$/;" p
|
||||||
|
mysql packets.go /^package mysql$/;" p
|
||||||
|
mysql result.go /^package mysql$/;" p
|
||||||
|
mysql rows.go /^package mysql$/;" p
|
||||||
|
mysql statement.go /^package mysql$/;" p
|
||||||
|
mysql transaction.go /^package mysql$/;" p
|
||||||
|
mysql utils.go /^package mysql$/;" p
|
||||||
|
mysql utils_test.go /^package mysql$/;" p
|
||||||
|
mysqlConn connection.go /^type mysqlConn struct {$/;" s
|
||||||
|
mysqlField rows.go /^type mysqlField struct {$/;" s
|
||||||
|
mysqlResult result.go /^type mysqlResult struct {$/;" s
|
||||||
|
mysqlRows rows.go /^type mysqlRows struct {$/;" s
|
||||||
|
mysqlStmt statement.go /^type mysqlStmt struct {$/;" s
|
||||||
|
mysqlTx transaction.go /^type mysqlTx struct {$/;" s
|
||||||
|
name rows.go /^ name string$/;" m struct:mysqlField
|
||||||
|
net connection.go /^ net string$/;" m struct:config
|
||||||
|
netAddr driver_test.go /^ netAddr string$/;" v
|
||||||
|
netConn connection.go /^ netConn net.Conn$/;" m struct:mysqlConn
|
||||||
|
newBuffer buffer.go /^func newBuffer(rd io.Reader) buffer {$/;" f
|
||||||
|
newMyRnd utils.go /^func newMyRnd(seed1, seed2 uint32) *myRnd {$/;" f
|
||||||
|
paramCount statement.go /^ paramCount int$/;" m struct:mysqlStmt
|
||||||
|
params connection.go /^ params map[string]string$/;" m struct:config
|
||||||
|
parseBinaryDateTime utils.go /^func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) {$/;" f
|
||||||
|
parseDSN utils.go /^func parseDSN(dsn string) (cfg *config, err error) {$/;" f
|
||||||
|
parseDSNParams utils.go /^func parseDSNParams(cfg *config, params string) (err error) {$/;" f
|
||||||
|
parseDateTime utils.go /^func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {$/;" f
|
||||||
|
parseTime connection.go /^ parseTime bool$/;" m struct:mysqlConn
|
||||||
|
pass driver_test.go /^ pass string$/;" v
|
||||||
|
passwd connection.go /^ passwd string$/;" m struct:config
|
||||||
|
prot driver_test.go /^ prot string$/;" v
|
||||||
|
pwHash utils.go /^func pwHash(password []byte) (result [2]uint32) {$/;" f
|
||||||
|
rd buffer.go /^ rd io.Reader$/;" m struct:buffer
|
||||||
|
readBool utils.go /^func readBool(input string) (value bool, valid bool) {$/;" f
|
||||||
|
readColumns packets.go /^func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {$/;" f
|
||||||
|
readInitPacket packets.go /^func (mc *mysqlConn) readInitPacket() ([]byte, error) {$/;" f
|
||||||
|
readLengthEncodedInteger utils.go /^func readLengthEncodedInteger(b []byte) (uint64, bool, int) {$/;" f
|
||||||
|
readLengthEncodedString utils.go /^func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {$/;" f
|
||||||
|
readNext buffer.go /^func (b *buffer) readNext(need int) ([]byte, error) {$/;" f
|
||||||
|
readPacket packets.go /^func (mc *mysqlConn) readPacket() ([]byte, error) {$/;" f
|
||||||
|
readPrepareResultPacket packets.go /^func (stmt *mysqlStmt) readPrepareResultPacket() (uint16, error) {$/;" f
|
||||||
|
readResultOK packets.go /^func (mc *mysqlConn) readResultOK() error {$/;" f
|
||||||
|
readResultSetHeaderPacket packets.go /^func (mc *mysqlConn) readResultSetHeaderPacket() (int, error) {$/;" f
|
||||||
|
readRow packets.go /^func (rows *binaryRows) readRow(dest []driver.Value) error {$/;" f
|
||||||
|
readRow packets.go /^func (rows *textRows) readRow(dest []driver.Value) error {$/;" f
|
||||||
|
readUntilEOF packets.go /^func (mc *mysqlConn) readUntilEOF() error {$/;" f
|
||||||
|
readerRegister infile.go /^ readerRegister map[string]func() io.Reader$/;" v
|
||||||
|
roundtripSample benchmark_test.go /^var roundtripSample []byte$/;" v
|
||||||
|
run driver_test.go /^func (t timeTest) run(dbt *DBTest, dbtype, tlayout string, mode timeMode) {$/;" f
|
||||||
|
runTests driver_test.go /^func runTests(t *testing.T, dsn string, tests ...func(dbt *DBTest)) {$/;" f
|
||||||
|
s driver_test.go /^ s string \/\/ leading "!": do not use t as value in queries$/;" m struct:timeTest
|
||||||
|
sDate driver_test.go /^ sDate = "2012-06-14"$/;" v
|
||||||
|
sDate0 driver_test.go /^ sDate0 = "0000-00-00"$/;" v
|
||||||
|
sDateTime driver_test.go /^ sDateTime = "2011-11-20 21:27:37"$/;" v
|
||||||
|
sDateTime0 driver_test.go /^ sDateTime0 = "0000-00-00 00:00:00"$/;" v
|
||||||
|
scrambleOldPassword utils.go /^func scrambleOldPassword(scramble, password []byte) []byte {$/;" f
|
||||||
|
scramblePassword utils.go /^func scramblePassword(scramble, password []byte) []byte {$/;" f
|
||||||
|
seed1 utils.go /^ seed1, seed2 uint32$/;" m struct:myRnd
|
||||||
|
seed2 utils.go /^ seed1, seed2 uint32$/;" m struct:myRnd
|
||||||
|
sequence connection.go /^ sequence uint8$/;" m struct:mysqlConn
|
||||||
|
skipLengthEncodedString utils.go /^func skipLengthEncodedString(b []byte) (int, error) {$/;" f
|
||||||
|
strict connection.go /^ strict bool$/;" m struct:mysqlConn
|
||||||
|
stringToInt utils.go /^func stringToInt(b []byte) int {$/;" f
|
||||||
|
t driver_test.go /^ t time.Time$/;" m struct:timeTest
|
||||||
|
tDate driver_test.go /^ tDate = time.Date(2012, 6, 14, 0, 0, 0, 0, time.UTC)$/;" v
|
||||||
|
tDate0 driver_test.go /^ tDate0 = time.Time{}$/;" v
|
||||||
|
tDateTime driver_test.go /^ tDateTime = time.Date(2011, 11, 20, 21, 27, 37, 0, time.UTC)$/;" v
|
||||||
|
tableName rows.go /^ tableName string$/;" m struct:mysqlField
|
||||||
|
takeBuffer buffer.go /^func (b *buffer) takeBuffer(length int) []byte {$/;" f
|
||||||
|
takeCompleteBuffer buffer.go /^func (b *buffer) takeCompleteBuffer() []byte {$/;" f
|
||||||
|
takeSmallBuffer buffer.go /^func (b *buffer) takeSmallBuffer(length int) []byte {$/;" f
|
||||||
|
testDSNs utils_test.go /^var testDSNs = []struct {$/;" v
|
||||||
|
tests driver_test.go /^ tests []timeTest$/;" m struct:timeTests
|
||||||
|
textRows rows.go /^type textRows struct {$/;" s
|
||||||
|
textString driver_test.go /^ textString$/;" c
|
||||||
|
timeFormat const.go /^ timeFormat = "2006-01-02 15:04:05.999999"$/;" c
|
||||||
|
timeMode driver_test.go /^type timeMode byte$/;" t
|
||||||
|
timeTest driver_test.go /^type timeTest struct {$/;" s
|
||||||
|
timeTests driver_test.go /^type timeTests struct {$/;" s
|
||||||
|
timeout connection.go /^ timeout time.Duration$/;" m struct:config
|
||||||
|
tlayout driver_test.go /^ tlayout string$/;" m struct:timeTests
|
||||||
|
tls connection.go /^ tls *tls.Config$/;" m struct:config
|
||||||
|
tlsConfigRegister utils.go /^ tlsConfigRegister map[string]*tls.Config \/\/ Register for custom tls.Configs$/;" v
|
||||||
|
uint64ToBytes utils.go /^func uint64ToBytes(n uint64) []byte {$/;" f
|
||||||
|
uint64ToString utils.go /^func uint64ToString(n uint64) []byte {$/;" f
|
||||||
|
user connection.go /^ user string$/;" m struct:config
|
||||||
|
user driver_test.go /^ user string$/;" v
|
||||||
|
writeAuthPacket packets.go /^func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {$/;" f
|
||||||
|
writeCommandLongData packets.go /^func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error {$/;" f
|
||||||
|
writeCommandPacket packets.go /^func (mc *mysqlConn) writeCommandPacket(command byte) error {$/;" f
|
||||||
|
writeCommandPacketStr packets.go /^func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {$/;" f
|
||||||
|
writeCommandPacketUint32 packets.go /^func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {$/;" f
|
||||||
|
writeExecutePacket packets.go /^func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {$/;" f
|
||||||
|
writeOldAuthPacket packets.go /^func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {$/;" f
|
||||||
|
writePacket packets.go /^func (mc *mysqlConn) writePacket(data []byte) error {$/;" f
|
||||||
|
zeroDateTime utils.go /^var zeroDateTime = []byte("0000-00-00 00:00:00.000000")$/;" v
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
type mysqlTx struct {
|
||||||
|
mc *mysqlConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *mysqlTx) Commit() (err error) {
|
||||||
|
if tx.mc == nil || tx.mc.netConn == nil {
|
||||||
|
return ErrInvalidConn
|
||||||
|
}
|
||||||
|
err = tx.mc.exec("COMMIT")
|
||||||
|
tx.mc = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *mysqlTx) Rollback() (err error) {
|
||||||
|
if tx.mc == nil || tx.mc.netConn == nil {
|
||||||
|
return ErrInvalidConn
|
||||||
|
}
|
||||||
|
err = tx.mc.exec("ROLLBACK")
|
||||||
|
tx.mc = nil
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,800 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/tls"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
|
||||||
|
|
||||||
|
errInvalidDSNUnescaped = errors.New("Invalid DSN: Did you forget to escape a param value?")
|
||||||
|
errInvalidDSNAddr = errors.New("Invalid DSN: Network Address not terminated (missing closing brace)")
|
||||||
|
errInvalidDSNNoSlash = errors.New("Invalid DSN: Missing the slash separating the database name")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tlsConfigRegister = make(map[string]*tls.Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
|
||||||
|
// Use the key as a value in the DSN where tls=value.
|
||||||
|
//
|
||||||
|
// rootCertPool := x509.NewCertPool()
|
||||||
|
// pem, err := ioutil.ReadFile("/path/ca-cert.pem")
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
// if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
|
||||||
|
// log.Fatal("Failed to append PEM.")
|
||||||
|
// }
|
||||||
|
// clientCert := make([]tls.Certificate, 0, 1)
|
||||||
|
// certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem")
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
// clientCert = append(clientCert, certs)
|
||||||
|
// mysql.RegisterTLSConfig("custom", &tls.Config{
|
||||||
|
// RootCAs: rootCertPool,
|
||||||
|
// Certificates: clientCert,
|
||||||
|
// })
|
||||||
|
// db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
|
||||||
|
//
|
||||||
|
func RegisterTLSConfig(key string, config *tls.Config) error {
|
||||||
|
if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" {
|
||||||
|
return fmt.Errorf("Key '%s' is reserved", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfigRegister[key] = config
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeregisterTLSConfig removes the tls.Config associated with key.
|
||||||
|
func DeregisterTLSConfig(key string) {
|
||||||
|
delete(tlsConfigRegister, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDSN parses the DSN string to a config
|
||||||
|
func parseDSN(dsn string) (cfg *config, err error) {
|
||||||
|
// New config with some default values
|
||||||
|
cfg = &config{
|
||||||
|
loc: time.UTC,
|
||||||
|
collation: defaultCollation,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use strings.IndexByte when we can depend on Go 1.2
|
||||||
|
|
||||||
|
// [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=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&...¶mN=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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default network if empty
|
||||||
|
if cfg.net == "" {
|
||||||
|
cfg.net = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default address if empty
|
||||||
|
if cfg.addr == "" {
|
||||||
|
switch cfg.net {
|
||||||
|
case "tcp":
|
||||||
|
cfg.addr = "127.0.0.1:3306"
|
||||||
|
case "unix":
|
||||||
|
cfg.addr = "/tmp/mysql.sock"
|
||||||
|
default:
|
||||||
|
return nil, errors.New("Default addr for network '" + cfg.net + "' unknown")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDSNParams parses the DSN "query string"
|
||||||
|
// Values must be url.QueryEscape'ed
|
||||||
|
func parseDSNParams(cfg *config, params string) (err error) {
|
||||||
|
for _, v := range strings.Split(params, "&") {
|
||||||
|
param := strings.SplitN(v, "=", 2)
|
||||||
|
if len(param) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// cfg params
|
||||||
|
switch value := param[1]; param[0] {
|
||||||
|
|
||||||
|
// Disable INFILE whitelist / enable all files
|
||||||
|
case "allowAllFiles":
|
||||||
|
var isBool bool
|
||||||
|
cfg.allowAllFiles, isBool = readBool(value)
|
||||||
|
if !isBool {
|
||||||
|
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use old authentication mode (pre MySQL 4.1)
|
||||||
|
case "allowOldPasswords":
|
||||||
|
var isBool bool
|
||||||
|
cfg.allowOldPasswords, isBool = readBool(value)
|
||||||
|
if !isBool {
|
||||||
|
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch "rowsAffected" mode
|
||||||
|
case "clientFoundRows":
|
||||||
|
var isBool bool
|
||||||
|
cfg.clientFoundRows, isBool = readBool(value)
|
||||||
|
if !isBool {
|
||||||
|
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collation
|
||||||
|
case "collation":
|
||||||
|
collation, ok := collations[value]
|
||||||
|
if !ok {
|
||||||
|
// Note possibility for false negatives:
|
||||||
|
// could be triggered although the collation is valid if the
|
||||||
|
// collations map does not contain entries the server supports.
|
||||||
|
err = errors.New("unknown collation")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfg.collation = collation
|
||||||
|
break
|
||||||
|
|
||||||
|
case "columnsWithAlias":
|
||||||
|
var isBool bool
|
||||||
|
cfg.columnsWithAlias, isBool = readBool(value)
|
||||||
|
if !isBool {
|
||||||
|
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time Location
|
||||||
|
case "loc":
|
||||||
|
if value, err = url.QueryUnescape(value); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfg.loc, err = time.LoadLocation(value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial Timeout
|
||||||
|
case "timeout":
|
||||||
|
cfg.timeout, err = time.ParseDuration(value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLS-Encryption
|
||||||
|
case "tls":
|
||||||
|
boolValue, isBool := readBool(value)
|
||||||
|
if isBool {
|
||||||
|
if boolValue {
|
||||||
|
cfg.tls = &tls.Config{}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if strings.ToLower(value) == "skip-verify" {
|
||||||
|
cfg.tls = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
} else if tlsConfig, ok := tlsConfigRegister[value]; ok {
|
||||||
|
if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
|
||||||
|
host, _, err := net.SplitHostPort(cfg.addr)
|
||||||
|
if err == nil {
|
||||||
|
tlsConfig.ServerName = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.tls = tlsConfig
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Invalid value / unknown config name: %s", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// lazy init
|
||||||
|
if cfg.params == nil {
|
||||||
|
cfg.params = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.params[param[0]], err = url.QueryUnescape(value); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the bool value of the input.
|
||||||
|
// The 2nd return value indicates if the input was a valid bool value
|
||||||
|
func readBool(input string) (value bool, valid bool) {
|
||||||
|
switch input {
|
||||||
|
case "1", "true", "TRUE", "True":
|
||||||
|
return true, true
|
||||||
|
case "0", "false", "FALSE", "False":
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a valid bool value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* Authentication *
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
// Encrypt password using 4.1+ method
|
||||||
|
func scramblePassword(scramble, password []byte) []byte {
|
||||||
|
if len(password) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stage1Hash = SHA1(password)
|
||||||
|
crypt := sha1.New()
|
||||||
|
crypt.Write(password)
|
||||||
|
stage1 := crypt.Sum(nil)
|
||||||
|
|
||||||
|
// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
|
||||||
|
// inner Hash
|
||||||
|
crypt.Reset()
|
||||||
|
crypt.Write(stage1)
|
||||||
|
hash := crypt.Sum(nil)
|
||||||
|
|
||||||
|
// outer Hash
|
||||||
|
crypt.Reset()
|
||||||
|
crypt.Write(scramble)
|
||||||
|
crypt.Write(hash)
|
||||||
|
scramble = crypt.Sum(nil)
|
||||||
|
|
||||||
|
// token = scrambleHash XOR stage1Hash
|
||||||
|
for i := range scramble {
|
||||||
|
scramble[i] ^= stage1[i]
|
||||||
|
}
|
||||||
|
return scramble
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt password using pre 4.1 (old password) method
|
||||||
|
// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
|
||||||
|
type myRnd struct {
|
||||||
|
seed1, seed2 uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
const myRndMaxVal = 0x3FFFFFFF
|
||||||
|
|
||||||
|
// Pseudo random number generator
|
||||||
|
func newMyRnd(seed1, seed2 uint32) *myRnd {
|
||||||
|
return &myRnd{
|
||||||
|
seed1: seed1 % myRndMaxVal,
|
||||||
|
seed2: seed2 % myRndMaxVal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tested to be equivalent to MariaDB's floating point variant
|
||||||
|
// http://play.golang.org/p/QHvhd4qved
|
||||||
|
// http://play.golang.org/p/RG0q4ElWDx
|
||||||
|
func (r *myRnd) NextByte() byte {
|
||||||
|
r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
|
||||||
|
r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
|
||||||
|
|
||||||
|
return byte(uint64(r.seed1) * 31 / myRndMaxVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate binary hash from byte string using insecure pre 4.1 method
|
||||||
|
func pwHash(password []byte) (result [2]uint32) {
|
||||||
|
var add uint32 = 7
|
||||||
|
var tmp uint32
|
||||||
|
|
||||||
|
result[0] = 1345345333
|
||||||
|
result[1] = 0x12345671
|
||||||
|
|
||||||
|
for _, c := range password {
|
||||||
|
// skip spaces and tabs in password
|
||||||
|
if c == ' ' || c == '\t' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp = uint32(c)
|
||||||
|
result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
|
||||||
|
result[1] += (result[1] << 8) ^ result[0]
|
||||||
|
add += tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove sign bit (1<<31)-1)
|
||||||
|
result[0] &= 0x7FFFFFFF
|
||||||
|
result[1] &= 0x7FFFFFFF
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt password using insecure pre 4.1 method
|
||||||
|
func scrambleOldPassword(scramble, password []byte) []byte {
|
||||||
|
if len(password) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
scramble = scramble[:8]
|
||||||
|
|
||||||
|
hashPw := pwHash(password)
|
||||||
|
hashSc := pwHash(scramble)
|
||||||
|
|
||||||
|
r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
|
||||||
|
|
||||||
|
var out [8]byte
|
||||||
|
for i := range out {
|
||||||
|
out[i] = r.NextByte() + 64
|
||||||
|
}
|
||||||
|
|
||||||
|
mask := r.NextByte()
|
||||||
|
for i := range out {
|
||||||
|
out[i] ^= mask
|
||||||
|
}
|
||||||
|
|
||||||
|
return out[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* Time related utils *
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
// NullTime represents a time.Time that may be NULL.
|
||||||
|
// NullTime implements the Scanner interface so
|
||||||
|
// it can be used as a scan destination:
|
||||||
|
//
|
||||||
|
// var nt NullTime
|
||||||
|
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
|
||||||
|
// ...
|
||||||
|
// if nt.Valid {
|
||||||
|
// // use nt.Time
|
||||||
|
// } else {
|
||||||
|
// // NULL value
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This NullTime implementation is not driver-specific
|
||||||
|
type NullTime struct {
|
||||||
|
Time time.Time
|
||||||
|
Valid bool // Valid is true if Time is not NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the Scanner interface.
|
||||||
|
// The value type must be time.Time or string / []byte (formatted time-string),
|
||||||
|
// otherwise Scan fails.
|
||||||
|
func (nt *NullTime) Scan(value interface{}) (err error) {
|
||||||
|
if value == nil {
|
||||||
|
nt.Time, nt.Valid = time.Time{}, false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case time.Time:
|
||||||
|
nt.Time, nt.Valid = v, true
|
||||||
|
return
|
||||||
|
case []byte:
|
||||||
|
nt.Time, err = parseDateTime(string(v), time.UTC)
|
||||||
|
nt.Valid = (err == nil)
|
||||||
|
return
|
||||||
|
case string:
|
||||||
|
nt.Time, err = parseDateTime(v, time.UTC)
|
||||||
|
nt.Valid = (err == nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nt.Valid = false
|
||||||
|
return fmt.Errorf("Can't convert %T to time.Time", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver Valuer interface.
|
||||||
|
func (nt NullTime) Value() (driver.Value, error) {
|
||||||
|
if !nt.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nt.Time, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
|
||||||
|
base := "0000-00-00 00:00:00.0000000"
|
||||||
|
switch len(str) {
|
||||||
|
case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
|
||||||
|
if str == base[:len(str)] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t, err = time.Parse(timeFormat[:len(str)], str)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("Invalid Time-String: %s", str)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust location
|
||||||
|
if err == nil && loc != time.UTC {
|
||||||
|
y, mo, d := t.Date()
|
||||||
|
h, mi, s := t.Clock()
|
||||||
|
t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) {
|
||||||
|
switch num {
|
||||||
|
case 0:
|
||||||
|
return time.Time{}, nil
|
||||||
|
case 4:
|
||||||
|
return time.Date(
|
||||||
|
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||||
|
time.Month(data[2]), // month
|
||||||
|
int(data[3]), // day
|
||||||
|
0, 0, 0, 0,
|
||||||
|
loc,
|
||||||
|
), nil
|
||||||
|
case 7:
|
||||||
|
return time.Date(
|
||||||
|
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||||
|
time.Month(data[2]), // month
|
||||||
|
int(data[3]), // day
|
||||||
|
int(data[4]), // hour
|
||||||
|
int(data[5]), // minutes
|
||||||
|
int(data[6]), // seconds
|
||||||
|
0,
|
||||||
|
loc,
|
||||||
|
), nil
|
||||||
|
case 11:
|
||||||
|
return time.Date(
|
||||||
|
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||||
|
time.Month(data[2]), // month
|
||||||
|
int(data[3]), // day
|
||||||
|
int(data[4]), // hour
|
||||||
|
int(data[5]), // minutes
|
||||||
|
int(data[6]), // seconds
|
||||||
|
int(binary.LittleEndian.Uint32(data[7:11]))*1000, // nanoseconds
|
||||||
|
loc,
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Invalid DATETIME-packet length %d", num)
|
||||||
|
}
|
||||||
|
|
||||||
|
// zeroDateTime is used in formatBinaryDateTime to avoid an allocation
|
||||||
|
// if the DATE or DATETIME has the zero value.
|
||||||
|
// It must never be changed.
|
||||||
|
// The current behavior depends on database/sql copying the result.
|
||||||
|
var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
|
||||||
|
|
||||||
|
func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {
|
||||||
|
// length expects the deterministic length of the zero value,
|
||||||
|
// negative time and 100+ hours are automatically added if needed
|
||||||
|
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
||||||
|
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
|
||||||
|
if len(src) == 0 {
|
||||||
|
if justTime {
|
||||||
|
return zeroDateTime[11 : 11+length], nil
|
||||||
|
}
|
||||||
|
return zeroDateTime[:length], nil
|
||||||
|
}
|
||||||
|
var dst []byte // return value
|
||||||
|
var pt, p1, p2, p3 byte // current digit pair
|
||||||
|
var zOffs byte // offset of value in zeroDateTime
|
||||||
|
if justTime {
|
||||||
|
switch length {
|
||||||
|
case
|
||||||
|
8, // time (can be up to 10 when negative and 100+ hours)
|
||||||
|
10, 11, 12, 13, 14, 15: // time with fractional seconds
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("illegal TIME length %d", length)
|
||||||
|
}
|
||||||
|
switch len(src) {
|
||||||
|
case 8, 12:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Invalid TIME-packet length %d", len(src))
|
||||||
|
}
|
||||||
|
// +2 to enable negative time and 100+ hours
|
||||||
|
dst = make([]byte, 0, length+2)
|
||||||
|
if src[0] == 1 {
|
||||||
|
dst = append(dst, '-')
|
||||||
|
}
|
||||||
|
if src[1] != 0 {
|
||||||
|
hour := uint16(src[1])*24 + uint16(src[5])
|
||||||
|
pt = byte(hour / 100)
|
||||||
|
p1 = byte(hour - 100*uint16(pt))
|
||||||
|
dst = append(dst, digits01[pt])
|
||||||
|
} else {
|
||||||
|
p1 = src[5]
|
||||||
|
}
|
||||||
|
zOffs = 11
|
||||||
|
src = src[6:]
|
||||||
|
} else {
|
||||||
|
switch length {
|
||||||
|
case 10, 19, 21, 22, 23, 24, 25, 26:
|
||||||
|
default:
|
||||||
|
t := "DATE"
|
||||||
|
if length > 10 {
|
||||||
|
t += "TIME"
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("illegal %s length %d", t, length)
|
||||||
|
}
|
||||||
|
switch len(src) {
|
||||||
|
case 4, 7, 11:
|
||||||
|
default:
|
||||||
|
t := "DATE"
|
||||||
|
if length > 10 {
|
||||||
|
t += "TIME"
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("illegal %s-packet length %d", t, len(src))
|
||||||
|
}
|
||||||
|
dst = make([]byte, 0, length)
|
||||||
|
// start with the date
|
||||||
|
year := binary.LittleEndian.Uint16(src[:2])
|
||||||
|
pt = byte(year / 100)
|
||||||
|
p1 = byte(year - 100*uint16(pt))
|
||||||
|
p2, p3 = src[2], src[3]
|
||||||
|
dst = append(dst,
|
||||||
|
digits10[pt], digits01[pt],
|
||||||
|
digits10[p1], digits01[p1], '-',
|
||||||
|
digits10[p2], digits01[p2], '-',
|
||||||
|
digits10[p3], digits01[p3],
|
||||||
|
)
|
||||||
|
if length == 10 {
|
||||||
|
return dst, nil
|
||||||
|
}
|
||||||
|
if len(src) == 4 {
|
||||||
|
return append(dst, zeroDateTime[10:length]...), nil
|
||||||
|
}
|
||||||
|
dst = append(dst, ' ')
|
||||||
|
p1 = src[4] // hour
|
||||||
|
src = src[5:]
|
||||||
|
}
|
||||||
|
// p1 is 2-digit hour, src is after hour
|
||||||
|
p2, p3 = src[0], src[1]
|
||||||
|
dst = append(dst,
|
||||||
|
digits10[p1], digits01[p1], ':',
|
||||||
|
digits10[p2], digits01[p2], ':',
|
||||||
|
digits10[p3], digits01[p3],
|
||||||
|
)
|
||||||
|
if length <= byte(len(dst)) {
|
||||||
|
return dst, nil
|
||||||
|
}
|
||||||
|
src = src[2:]
|
||||||
|
if len(src) == 0 {
|
||||||
|
return append(dst, zeroDateTime[19:zOffs+length]...), nil
|
||||||
|
}
|
||||||
|
microsecs := binary.LittleEndian.Uint32(src[:4])
|
||||||
|
p1 = byte(microsecs / 10000)
|
||||||
|
microsecs -= 10000 * uint32(p1)
|
||||||
|
p2 = byte(microsecs / 100)
|
||||||
|
microsecs -= 100 * uint32(p2)
|
||||||
|
p3 = byte(microsecs)
|
||||||
|
switch decimals := zOffs + length - 20; decimals {
|
||||||
|
default:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1], digits01[p1],
|
||||||
|
digits10[p2], digits01[p2],
|
||||||
|
digits10[p3], digits01[p3],
|
||||||
|
), nil
|
||||||
|
case 1:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1],
|
||||||
|
), nil
|
||||||
|
case 2:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1], digits01[p1],
|
||||||
|
), nil
|
||||||
|
case 3:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1], digits01[p1],
|
||||||
|
digits10[p2],
|
||||||
|
), nil
|
||||||
|
case 4:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1], digits01[p1],
|
||||||
|
digits10[p2], digits01[p2],
|
||||||
|
), nil
|
||||||
|
case 5:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1], digits01[p1],
|
||||||
|
digits10[p2], digits01[p2],
|
||||||
|
digits10[p3],
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* Convert from and to bytes *
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
func uint64ToBytes(n uint64) []byte {
|
||||||
|
return []byte{
|
||||||
|
byte(n),
|
||||||
|
byte(n >> 8),
|
||||||
|
byte(n >> 16),
|
||||||
|
byte(n >> 24),
|
||||||
|
byte(n >> 32),
|
||||||
|
byte(n >> 40),
|
||||||
|
byte(n >> 48),
|
||||||
|
byte(n >> 56),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func uint64ToString(n uint64) []byte {
|
||||||
|
var a [20]byte
|
||||||
|
i := 20
|
||||||
|
|
||||||
|
// U+0030 = 0
|
||||||
|
// ...
|
||||||
|
// U+0039 = 9
|
||||||
|
|
||||||
|
var q uint64
|
||||||
|
for n >= 10 {
|
||||||
|
i--
|
||||||
|
q = n / 10
|
||||||
|
a[i] = uint8(n-q*10) + 0x30
|
||||||
|
n = q
|
||||||
|
}
|
||||||
|
|
||||||
|
i--
|
||||||
|
a[i] = uint8(n) + 0x30
|
||||||
|
|
||||||
|
return a[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// treats string value as unsigned integer representation
|
||||||
|
func stringToInt(b []byte) int {
|
||||||
|
val := 0
|
||||||
|
for i := range b {
|
||||||
|
val *= 10
|
||||||
|
val += int(b[i] - 0x30)
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the string read as a bytes slice, wheter the value is NULL,
|
||||||
|
// the number of bytes read and an error, in case the string is longer than
|
||||||
|
// the input slice
|
||||||
|
func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
|
||||||
|
// Get length
|
||||||
|
num, isNull, n := readLengthEncodedInteger(b)
|
||||||
|
if num < 1 {
|
||||||
|
return b[n:n], isNull, n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n += int(num)
|
||||||
|
|
||||||
|
// Check data length
|
||||||
|
if len(b) >= n {
|
||||||
|
return b[n-int(num) : n], false, n, nil
|
||||||
|
}
|
||||||
|
return nil, false, n, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the number of bytes skipped and an error, in case the string is
|
||||||
|
// longer than the input slice
|
||||||
|
func skipLengthEncodedString(b []byte) (int, error) {
|
||||||
|
// Get length
|
||||||
|
num, _, n := readLengthEncodedInteger(b)
|
||||||
|
if num < 1 {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n += int(num)
|
||||||
|
|
||||||
|
// Check data length
|
||||||
|
if len(b) >= n {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
return n, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the number read, whether the value is NULL and the number of bytes read
|
||||||
|
func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
|
||||||
|
switch b[0] {
|
||||||
|
|
||||||
|
// 251: NULL
|
||||||
|
case 0xfb:
|
||||||
|
return 0, true, 1
|
||||||
|
|
||||||
|
// 252: value of following 2
|
||||||
|
case 0xfc:
|
||||||
|
return uint64(b[1]) | uint64(b[2])<<8, false, 3
|
||||||
|
|
||||||
|
// 253: value of following 3
|
||||||
|
case 0xfd:
|
||||||
|
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4
|
||||||
|
|
||||||
|
// 254: value of following 8
|
||||||
|
case 0xfe:
|
||||||
|
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 |
|
||||||
|
uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 |
|
||||||
|
uint64(b[7])<<48 | uint64(b[8])<<56,
|
||||||
|
false, 9
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0-250: value of first byte
|
||||||
|
return uint64(b[0]), false, 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes a uint64 value and appends it to the given bytes slice
|
||||||
|
func appendLengthEncodedInteger(b []byte, n uint64) []byte {
|
||||||
|
switch {
|
||||||
|
case n <= 250:
|
||||||
|
return append(b, byte(n))
|
||||||
|
|
||||||
|
case n <= 0xffff:
|
||||||
|
return append(b, 0xfc, byte(n), byte(n>>8))
|
||||||
|
|
||||||
|
case n <= 0xffffff:
|
||||||
|
return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16))
|
||||||
|
}
|
||||||
|
return append(b, 0xfe, byte(n), byte(n>>8), byte(n>>16), byte(n>>24),
|
||||||
|
byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56))
|
||||||
|
}
|
|
@ -0,0 +1,254 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testDSNs = []struct {
|
||||||
|
in string
|
||||||
|
out string
|
||||||
|
loc *time.Location
|
||||||
|
}{
|
||||||
|
{"username:password@protocol(address)/dbname?param=value", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
|
||||||
|
{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:true}", time.UTC},
|
||||||
|
{"user@unix(/path/to/socket)/dbname?charset=utf8", "&{user:user passwd: net:unix addr:/path/to/socket dbname:dbname params:map[charset:utf8] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
|
||||||
|
{"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
|
||||||
|
{"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8mb4,utf8] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
|
||||||
|
{"user:password@/dbname?loc=UTC&timeout=30s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci", "&{user:user passwd:password net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls:<nil> timeout:30000000000 collation:224 allowAllFiles:true allowOldPasswords:true clientFoundRows:true columnsWithAlias:false}", time.UTC},
|
||||||
|
{"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", "&{user:user passwd:p@ss(word) net:tcp addr:[de:ad:be:ef::ca:fe]:80 dbname:dbname params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.Local},
|
||||||
|
{"/dbname", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
|
||||||
|
{"@/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
|
||||||
|
{"/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
|
||||||
|
{"", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
|
||||||
|
{"user:p@/ssword@/", "&{user:user passwd:p@/ssword net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
|
||||||
|
{"unix/?arg=%2Fsome%2Fpath.ext", "&{user: passwd: net:unix addr:/tmp/mysql.sock dbname: params:map[arg:/some/path.ext] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDSNParser(t *testing.T) {
|
||||||
|
var cfg *config
|
||||||
|
var err error
|
||||||
|
var res string
|
||||||
|
|
||||||
|
for i, tst := range testDSNs {
|
||||||
|
cfg, err = parseDSN(tst.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// pointer not static
|
||||||
|
cfg.tls = nil
|
||||||
|
|
||||||
|
res = fmt.Sprintf("%+v", cfg)
|
||||||
|
if res != fmt.Sprintf(tst.out, tst.loc) {
|
||||||
|
t.Errorf("%d. parseDSN(%q) => %q, want %q", i, tst.in, res, fmt.Sprintf(tst.out, tst.loc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDSNParserInvalid(t *testing.T) {
|
||||||
|
var invalidDSNs = []string{
|
||||||
|
"@net(addr/", // no closing brace
|
||||||
|
"@tcp(/", // no closing brace
|
||||||
|
"tcp(/", // no closing brace
|
||||||
|
"(/", // no closing brace
|
||||||
|
"net(addr)//", // unescaped
|
||||||
|
"user:pass@tcp(1.2.3.4:3306)", // no trailing slash
|
||||||
|
//"/dbname?arg=/some/unescaped/path",
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tst := range invalidDSNs {
|
||||||
|
if _, err := parseDSN(tst); err == nil {
|
||||||
|
t.Errorf("invalid DSN #%d. (%s) didn't error!", i, tst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDSNWithCustomTLS(t *testing.T) {
|
||||||
|
baseDSN := "user:password@tcp(localhost:5555)/dbname?tls="
|
||||||
|
tlsCfg := tls.Config{}
|
||||||
|
|
||||||
|
RegisterTLSConfig("utils_test", &tlsCfg)
|
||||||
|
|
||||||
|
// Custom TLS is missing
|
||||||
|
tst := baseDSN + "invalid_tls"
|
||||||
|
cfg, err := parseDSN(tst)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Invalid custom TLS in DSN (%s) but did not error. Got config: %#v", tst, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
tst = baseDSN + "utils_test"
|
||||||
|
|
||||||
|
// Custom TLS with a server name
|
||||||
|
name := "foohost"
|
||||||
|
tlsCfg.ServerName = name
|
||||||
|
cfg, err = parseDSN(tst)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err.Error())
|
||||||
|
} else if cfg.tls.ServerName != name {
|
||||||
|
t.Errorf("Did not get the correct TLS ServerName (%s) parsing DSN (%s).", name, tst)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom TLS without a server name
|
||||||
|
name = "localhost"
|
||||||
|
tlsCfg.ServerName = ""
|
||||||
|
cfg, err = parseDSN(tst)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err.Error())
|
||||||
|
} else if cfg.tls.ServerName != name {
|
||||||
|
t.Errorf("Did not get the correct ServerName (%s) parsing DSN (%s).", name, tst)
|
||||||
|
}
|
||||||
|
|
||||||
|
DeregisterTLSConfig("utils_test")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkParseDSN(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, tst := range testDSNs {
|
||||||
|
if _, err := parseDSN(tst.in); err != nil {
|
||||||
|
b.Error(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanNullTime(t *testing.T) {
|
||||||
|
var scanTests = []struct {
|
||||||
|
in interface{}
|
||||||
|
error bool
|
||||||
|
valid bool
|
||||||
|
time time.Time
|
||||||
|
}{
|
||||||
|
{tDate, false, true, tDate},
|
||||||
|
{sDate, false, true, tDate},
|
||||||
|
{[]byte(sDate), false, true, tDate},
|
||||||
|
{tDateTime, false, true, tDateTime},
|
||||||
|
{sDateTime, false, true, tDateTime},
|
||||||
|
{[]byte(sDateTime), false, true, tDateTime},
|
||||||
|
{tDate0, false, true, tDate0},
|
||||||
|
{sDate0, false, true, tDate0},
|
||||||
|
{[]byte(sDate0), false, true, tDate0},
|
||||||
|
{sDateTime0, false, true, tDate0},
|
||||||
|
{[]byte(sDateTime0), false, true, tDate0},
|
||||||
|
{"", true, false, tDate0},
|
||||||
|
{"1234", true, false, tDate0},
|
||||||
|
{0, true, false, tDate0},
|
||||||
|
}
|
||||||
|
|
||||||
|
var nt = NullTime{}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for _, tst := range scanTests {
|
||||||
|
err = nt.Scan(tst.in)
|
||||||
|
if (err != nil) != tst.error {
|
||||||
|
t.Errorf("%v: expected error status %t, got %t", tst.in, tst.error, (err != nil))
|
||||||
|
}
|
||||||
|
if nt.Valid != tst.valid {
|
||||||
|
t.Errorf("%v: expected valid status %t, got %t", tst.in, tst.valid, nt.Valid)
|
||||||
|
}
|
||||||
|
if nt.Time != tst.time {
|
||||||
|
t.Errorf("%v: expected time %v, got %v", tst.in, tst.time, nt.Time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLengthEncodedInteger(t *testing.T) {
|
||||||
|
var integerTests = []struct {
|
||||||
|
num uint64
|
||||||
|
encoded []byte
|
||||||
|
}{
|
||||||
|
{0x0000000000000000, []byte{0x00}},
|
||||||
|
{0x0000000000000012, []byte{0x12}},
|
||||||
|
{0x00000000000000fa, []byte{0xfa}},
|
||||||
|
{0x0000000000000100, []byte{0xfc, 0x00, 0x01}},
|
||||||
|
{0x0000000000001234, []byte{0xfc, 0x34, 0x12}},
|
||||||
|
{0x000000000000ffff, []byte{0xfc, 0xff, 0xff}},
|
||||||
|
{0x0000000000010000, []byte{0xfd, 0x00, 0x00, 0x01}},
|
||||||
|
{0x0000000000123456, []byte{0xfd, 0x56, 0x34, 0x12}},
|
||||||
|
{0x0000000000ffffff, []byte{0xfd, 0xff, 0xff, 0xff}},
|
||||||
|
{0x0000000001000000, []byte{0xfe, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}},
|
||||||
|
{0x123456789abcdef0, []byte{0xfe, 0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12}},
|
||||||
|
{0xffffffffffffffff, []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tst := range integerTests {
|
||||||
|
num, isNull, numLen := readLengthEncodedInteger(tst.encoded)
|
||||||
|
if isNull {
|
||||||
|
t.Errorf("%x: expected %d, got NULL", tst.encoded, tst.num)
|
||||||
|
}
|
||||||
|
if num != tst.num {
|
||||||
|
t.Errorf("%x: expected %d, got %d", tst.encoded, tst.num, num)
|
||||||
|
}
|
||||||
|
if numLen != len(tst.encoded) {
|
||||||
|
t.Errorf("%x: expected size %d, got %d", tst.encoded, len(tst.encoded), numLen)
|
||||||
|
}
|
||||||
|
encoded := appendLengthEncodedInteger(nil, num)
|
||||||
|
if !bytes.Equal(encoded, tst.encoded) {
|
||||||
|
t.Errorf("%v: expected %x, got %x", num, tst.encoded, encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOldPass(t *testing.T) {
|
||||||
|
scramble := []byte{9, 8, 7, 6, 5, 4, 3, 2}
|
||||||
|
vectors := []struct {
|
||||||
|
pass string
|
||||||
|
out string
|
||||||
|
}{
|
||||||
|
{" pass", "47575c5a435b4251"},
|
||||||
|
{"pass ", "47575c5a435b4251"},
|
||||||
|
{"123\t456", "575c47505b5b5559"},
|
||||||
|
{"C0mpl!ca ted#PASS123", "5d5d554849584a45"},
|
||||||
|
}
|
||||||
|
for _, tuple := range vectors {
|
||||||
|
ours := scrambleOldPassword(scramble, []byte(tuple.pass))
|
||||||
|
if tuple.out != fmt.Sprintf("%x", ours) {
|
||||||
|
t.Errorf("Failed old password %q", tuple.pass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatBinaryDateTime(t *testing.T) {
|
||||||
|
rawDate := [11]byte{}
|
||||||
|
binary.LittleEndian.PutUint16(rawDate[:2], 1978) // years
|
||||||
|
rawDate[2] = 12 // months
|
||||||
|
rawDate[3] = 30 // days
|
||||||
|
rawDate[4] = 15 // hours
|
||||||
|
rawDate[5] = 46 // minutes
|
||||||
|
rawDate[6] = 23 // seconds
|
||||||
|
binary.LittleEndian.PutUint32(rawDate[7:], 987654) // microseconds
|
||||||
|
expect := func(expected string, inlen, outlen uint8) {
|
||||||
|
actual, _ := formatBinaryDateTime(rawDate[:inlen], outlen, false)
|
||||||
|
bytes, ok := actual.([]byte)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("formatBinaryDateTime must return []byte, was %T", actual)
|
||||||
|
}
|
||||||
|
if string(bytes) != expected {
|
||||||
|
t.Errorf(
|
||||||
|
"expected %q, got %q for length in %d, out %d",
|
||||||
|
bytes, actual, inlen, outlen,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect("0000-00-00", 0, 10)
|
||||||
|
expect("0000-00-00 00:00:00", 0, 19)
|
||||||
|
expect("1978-12-30", 4, 10)
|
||||||
|
expect("1978-12-30 15:46:23", 7, 19)
|
||||||
|
expect("1978-12-30 15:46:23.987654", 11, 26)
|
||||||
|
}
|
Loading…
Reference in New Issue