commit
166c6f3457
51
vendor/github.com/siddontang/go-mysql/Godeps/Godeps.json
generated
vendored
51
vendor/github.com/siddontang/go-mysql/Godeps/Godeps.json
generated
vendored
@ -1,51 +0,0 @@
|
||||
{
|
||||
"ImportPath": "github.com/siddontang/go-mysql",
|
||||
"GoVersion": "go1.5.2",
|
||||
"Packages": [
|
||||
"./..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/BurntSushi/toml",
|
||||
"Rev": "2ceedfee35ad3848e49308ab0c9a4f640cfb5fb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/go-sql-driver/mysql",
|
||||
"Comment": "v1.0.1",
|
||||
"Rev": "75bb76ec381d160c6b119bd8004a12979b0f2008"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jmoiron/sqlx",
|
||||
"Comment": "sqlx-v1.0-58-gac7070c",
|
||||
"Rev": "ac7070c8a115e759a26e524fd299efdac81b7e86"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/juju/errors",
|
||||
"Rev": "1b5e39b83d1835fa480e0c2ddefb040ee82d58b3"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/satori/go.uuid",
|
||||
"Rev": "a07c134f8289edd9ac6ee6f0aa5f3d345f4aaf27"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/siddontang/go/hack",
|
||||
"Rev": "530a23162549a31baa14dfa3b647a9eccee8878f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/siddontang/go/ioutil2",
|
||||
"Rev": "530a23162549a31baa14dfa3b647a9eccee8878f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/siddontang/go/log",
|
||||
"Rev": "530a23162549a31baa14dfa3b647a9eccee8878f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/siddontang/go/sync2",
|
||||
"Rev": "530a23162549a31baa14dfa3b647a9eccee8878f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/check.v1",
|
||||
"Rev": "64131543e7896d5bcc6bd5a76287eb75ea96c673"
|
||||
}
|
||||
]
|
||||
}
|
5
vendor/github.com/siddontang/go-mysql/Godeps/Readme
generated
vendored
5
vendor/github.com/siddontang/go-mysql/Godeps/Readme
generated
vendored
@ -1,5 +0,0 @@
|
||||
This directory tree is generated automatically by godep.
|
||||
|
||||
Please do not edit.
|
||||
|
||||
See https://github.com/tools/godep for more information.
|
2
vendor/github.com/siddontang/go-mysql/Godeps/_workspace/.gitignore
generated
vendored
2
vendor/github.com/siddontang/go-mysql/Godeps/_workspace/.gitignore
generated
vendored
@ -1,2 +0,0 @@
|
||||
/pkg
|
||||
/bin
|
@ -1 +0,0 @@
|
||||
README.md merge=ours
|
@ -1,8 +0,0 @@
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
Icon?
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
@ -1,4 +0,0 @@
|
||||
language: go
|
||||
|
||||
before_script:
|
||||
- mysql -e 'create database gotest;'
|
@ -1,373 +0,0 @@
|
||||
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.
|
@ -1,188 +0,0 @@
|
||||
# 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/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow")
|
||||
|
||||
**Current tagged Release:** June 03, 2013 (Version 1.0.1)
|
||||
|
||||
---------------------------------------
|
||||
* [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)
|
||||
* [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.0.3 or higher
|
||||
* MySQL (Version 4.1 or higher), MariaDB or Percona Server
|
||||
|
||||
---------------------------------------
|
||||
|
||||
## 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, e := sql.Open("mysql", "user:password@/dbname?charset=utf8")
|
||||
```
|
||||
|
||||
[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 of the databasename, all values are optional. So the minimal DSN is:
|
||||
```
|
||||
/dbname
|
||||
```
|
||||
|
||||
If you do not want to preselect a database, leave `dbname` empty:
|
||||
```
|
||||
/
|
||||
```
|
||||
|
||||
#### 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!***
|
||||
|
||||
Possible Parameters are:
|
||||
* `timeout`: **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).
|
||||
* `charset`: Sets the charset used for client-server interaction ("SET NAMES `value`"). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
|
||||
* `allowAllFiles`: `allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files. *Might be insecure!*
|
||||
* `parseTime`: `parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
|
||||
* `loc`: Sets the location for time.Time values (when using `parseTime=true`). The default is `UTC`. *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details.
|
||||
* `strict`: Enable strict mode. MySQL warnings are treated as errors.
|
||||
|
||||
All other parameters are interpreted as system variables:
|
||||
* `autocommit`: *"SET autocommit=`value`"*
|
||||
* `time_zone`: *"SET time_zone=`value`"*
|
||||
* `tx_isolation`: *"SET [tx_isolation](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation)=`value`"*
|
||||
* `param`: *"SET `param`=`value`"*
|
||||
|
||||
#### Examples
|
||||
```
|
||||
user@unix(/path/to/socket)/dbname
|
||||
```
|
||||
|
||||
```
|
||||
user:password@tcp(localhost:5555)/dbname?charset=utf8&autocommit=true
|
||||
```
|
||||
|
||||
```
|
||||
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?charset=utf8mb4,utf8
|
||||
```
|
||||
|
||||
```
|
||||
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).
|
||||
|
||||
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 also the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation")
|
||||
|
||||
### `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`.
|
||||
|
||||
|
||||
|
||||
## 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).
|
||||
|
||||
Code changes must be proposed via a Pull Request and must be reviewed. Only *LGTM*-ed (" *Looks good to me* ") code may be committed to the master branch.
|
||||
|
||||
---------------------------------------
|
||||
|
||||
## 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 as also commercial
|
||||
* You **needn't publish** the source code of your library as long the files licensed under the MPL 2.0 are **unchanged**
|
||||
* 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)
|
||||
|
||||
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)
|
@ -1,88 +0,0 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||
// http://www.julienschmidt.com
|
||||
//
|
||||
// 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
|
||||
|
||||
type buffer struct {
|
||||
buf []byte
|
||||
rd io.Reader
|
||||
idx int
|
||||
length int
|
||||
}
|
||||
|
||||
func newBuffer(rd io.Reader) *buffer {
|
||||
return &buffer{
|
||||
buf: make([]byte, defaultBufSize),
|
||||
rd: rd,
|
||||
}
|
||||
}
|
||||
|
||||
// fill reads into the buffer until at least _need_ bytes are in it
|
||||
func (b *buffer) fill(need int) (err error) {
|
||||
// move existing data to the beginning
|
||||
if b.length > 0 && b.idx > 0 {
|
||||
copy(b.buf[0:b.length], b.buf[b.idx:])
|
||||
}
|
||||
|
||||
// grow buffer if necessary
|
||||
if need > len(b.buf) {
|
||||
b.grow(need)
|
||||
}
|
||||
|
||||
b.idx = 0
|
||||
|
||||
var n int
|
||||
for b.length < need {
|
||||
n, err = b.rd.Read(b.buf[b.length:])
|
||||
b.length += n
|
||||
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
return // err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// grow the buffer to at least the given size
|
||||
// credit for this code snippet goes to Maxim Khitrov
|
||||
// https://groups.google.com/forum/#!topic/golang-nuts/ETbw1ECDgRs
|
||||
func (b *buffer) grow(size int) {
|
||||
// If append would be too expensive, alloc a new slice
|
||||
if size > 2*cap(b.buf) {
|
||||
newBuf := make([]byte, size)
|
||||
copy(newBuf, b.buf)
|
||||
b.buf = newBuf
|
||||
return
|
||||
}
|
||||
|
||||
for cap(b.buf) < size {
|
||||
b.buf = append(b.buf[:cap(b.buf)], 0)
|
||||
}
|
||||
b.buf = b.buf[:cap(b.buf)]
|
||||
}
|
||||
|
||||
// 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) (p []byte, err error) {
|
||||
if b.length < need {
|
||||
// refill
|
||||
err = b.fill(need) // err deferred
|
||||
}
|
||||
|
||||
p = b.buf[b.idx : b.idx+need]
|
||||
b.idx += need
|
||||
b.length -= need
|
||||
return
|
||||
}
|
@ -1,241 +0,0 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 Julien Schmidt. All rights reserved.
|
||||
// http://www.julienschmidt.com
|
||||
//
|
||||
// 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"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type mysqlConn struct {
|
||||
cfg *config
|
||||
flags clientFlag
|
||||
charset byte
|
||||
cipher []byte
|
||||
netConn net.Conn
|
||||
buf *buffer
|
||||
protocol uint8
|
||||
sequence uint8
|
||||
affectedRows uint64
|
||||
insertId uint64
|
||||
maxPacketAllowed int
|
||||
maxWriteSize int
|
||||
parseTime bool
|
||||
strict bool
|
||||
}
|
||||
|
||||
type config struct {
|
||||
user string
|
||||
passwd string
|
||||
net string
|
||||
addr string
|
||||
dbname string
|
||||
params map[string]string
|
||||
loc *time.Location
|
||||
}
|
||||
|
||||
// Handles parameters set in DSN
|
||||
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
|
||||
}
|
||||
|
||||
// handled elsewhere
|
||||
case "timeout", "allowAllFiles", "loc":
|
||||
continue
|
||||
|
||||
// time.Time parsing
|
||||
case "parseTime":
|
||||
mc.parseTime = readBool(val)
|
||||
|
||||
// Strict mode
|
||||
case "strict":
|
||||
mc.strict = readBool(val)
|
||||
|
||||
// TLS-Encryption
|
||||
case "tls":
|
||||
err = errors.New("TLS-Encryption not implemented yet")
|
||||
return
|
||||
|
||||
// Compression
|
||||
case "compress":
|
||||
err = errors.New("Compression not implemented yet")
|
||||
|
||||
// System Vars
|
||||
default:
|
||||
err = mc.exec("SET " + param + "=" + val + "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Begin() (driver.Tx, error) {
|
||||
err := mc.exec("START TRANSACTION")
|
||||
if err == nil {
|
||||
return &mysqlTx{mc}, err
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Close() (err error) {
|
||||
mc.writeCommandPacket(comQuit)
|
||||
mc.cfg = nil
|
||||
mc.buf = nil
|
||||
mc.netConn.Close()
|
||||
mc.netConn = nil
|
||||
return
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
|
||||
// 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 {
|
||||
stmt.params, err = stmt.mc.readColumns(stmt.paramCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if columnCount > 0 {
|
||||
err = stmt.mc.readUntilEOF()
|
||||
}
|
||||
}
|
||||
|
||||
return stmt, err
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
||||
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) (err error) {
|
||||
// Send command
|
||||
err = mc.writeCommandPacketStr(comQuery, query)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Read Result
|
||||
var resLen int
|
||||
resLen, err = mc.readResultSetHeaderPacket()
|
||||
if err == nil && resLen > 0 {
|
||||
err = mc.readUntilEOF()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = mc.readUntilEOF()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
||||
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 := &mysqlRows{mc, false, nil, false}
|
||||
|
||||
if resLen > 0 {
|
||||
// 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) (val []byte, err error) {
|
||||
// Send command
|
||||
err = mc.writeCommandPacketStr(comQuery, "SELECT @@"+name)
|
||||
if err == nil {
|
||||
// Read Result
|
||||
var resLen int
|
||||
resLen, err = mc.readResultSetHeaderPacket()
|
||||
if err == nil {
|
||||
rows := &mysqlRows{mc, false, nil, false}
|
||||
|
||||
if resLen > 0 {
|
||||
// Columns
|
||||
rows.columns, err = mc.readColumns(resLen)
|
||||
}
|
||||
|
||||
dest := make([]driver.Value, resLen)
|
||||
err = rows.readRow(dest)
|
||||
if err == nil {
|
||||
val = dest[0].([]byte)
|
||||
err = mc.readUntilEOF()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 Julien Schmidt. All rights reserved.
|
||||
// http://www.julienschmidt.com
|
||||
//
|
||||
// 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"
|
||||
)
|
||||
|
||||
// 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
|
||||
comRegiserSlave
|
||||
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
|
||||
)
|
@ -1,90 +0,0 @@
|
||||
// Copyright 2012 Julien Schmidt. All rights reserved.
|
||||
// http://www.julienschmidt.com
|
||||
//
|
||||
// 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
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type mysqlDriver struct{}
|
||||
|
||||
// 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 _, ok := mc.cfg.params["timeout"]; ok { // with timeout
|
||||
var timeout time.Duration
|
||||
timeout, err = time.ParseDuration(mc.cfg.params["timeout"])
|
||||
if err == nil {
|
||||
mc.netConn, err = net.DialTimeout(mc.cfg.net, mc.cfg.addr, timeout)
|
||||
}
|
||||
} else { // no timeout
|
||||
mc.netConn, err = net.Dial(mc.cfg.net, mc.cfg.addr)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mc.buf = newBuffer(mc.netConn)
|
||||
|
||||
// Reading Handshake Initialization Packet
|
||||
err = mc.readInitPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send Client Authentication Packet
|
||||
err = mc.writeAuthPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read Result Packet
|
||||
err = mc.readResultOK()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get max allowed packet size
|
||||
maxap, err := mc.getSystemVar("max_allowed_packet")
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mc, err
|
||||
}
|
||||
|
||||
func init() {
|
||||
sql.Register("mysql", &mysqlDriver{})
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,104 +0,0 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||
// http://www.julienschmidt.com
|
||||
//
|
||||
// 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"
|
||||
)
|
||||
|
||||
var (
|
||||
errMalformPkt = errors.New("Malformed Packet")
|
||||
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?")
|
||||
errOldPassword = errors.New("It seems like you are using old_passwords, which is unsupported. See https://github.com/go-sql-driver/mysql/wiki/old_passwords")
|
||||
errPktTooLarge = errors.New("Packet for query is too large. You can change this value on the server by adjusting the 'max_allowed_packet' variable.")
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// error type which represents a single MySQL warning
|
||||
type mysqlWarning struct {
|
||||
Level string
|
||||
Code string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) getWarnings() (err error) {
|
||||
rows, err := mc.Query("SHOW WARNINGS", []driver.Value{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var warnings = MySQLWarnings{}
|
||||
var values = make([]driver.Value, 3)
|
||||
|
||||
var warning mysqlWarning
|
||||
var raw []byte
|
||||
var ok bool
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||
// http://www.julienschmidt.com
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
fileRegister map[string]bool
|
||||
readerRegister map[string]func() io.Reader
|
||||
)
|
||||
|
||||
func init() {
|
||||
fileRegister = make(map[string]bool)
|
||||
readerRegister = make(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'
|
||||
func RegisterLocalFile(filepath string) {
|
||||
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.
|
||||
func RegisterReaderHandler(name string, handler 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 (mc *mysqlConn) handleInFileRequest(name string) (err error) {
|
||||
var rdr io.Reader
|
||||
data := make([]byte, 4+mc.maxWriteSize)
|
||||
|
||||
if strings.HasPrefix(name, "Reader::") { // io.Reader
|
||||
name = name[8:]
|
||||
handler, inMap := readerRegister[name]
|
||||
if handler != nil {
|
||||
rdr = handler()
|
||||
}
|
||||
if rdr == nil {
|
||||
if !inMap {
|
||||
err = fmt.Errorf("Reader '%s' is not registered", name)
|
||||
} else {
|
||||
err = fmt.Errorf("Reader '%s' is <nil>", name)
|
||||
}
|
||||
}
|
||||
} else { // File
|
||||
name = strings.Trim(name, `"`)
|
||||
if fileRegister[name] || mc.cfg.params[`allowAllFiles`] == `true` {
|
||||
rdr, err = os.Open(name)
|
||||
} else {
|
||||
err = fmt.Errorf("Local File '%s' is not registered. Use the DSN parameter 'allowAllFiles=true' to allow all files", name)
|
||||
}
|
||||
}
|
||||
|
||||
if rdc, ok := rdr.(io.ReadCloser); ok {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = rdc.Close()
|
||||
} else {
|
||||
rdc.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// send content packets
|
||||
var ioErr error
|
||||
if err == nil {
|
||||
var n int
|
||||
for err == nil && ioErr == nil {
|
||||
n, err = rdr.Read(data[4:])
|
||||
if n > 0 {
|
||||
data[0] = byte(n)
|
||||
data[1] = byte(n >> 8)
|
||||
data[2] = byte(n >> 16)
|
||||
data[3] = mc.sequence
|
||||
ioErr = mc.writePacket(data[:4+n])
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
if ioErr != nil {
|
||||
errLog.Print(ioErr.Error())
|
||||
return driver.ErrBadConn
|
||||
}
|
||||
}
|
||||
|
||||
// send empty packet (termination)
|
||||
ioErr = mc.writePacket([]byte{
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
mc.sequence,
|
||||
})
|
||||
if ioErr != nil {
|
||||
errLog.Print(ioErr.Error())
|
||||
return driver.ErrBadConn
|
||||
}
|
||||
|
||||
// read OK packet
|
||||
if err == nil {
|
||||
return mc.readResultOK()
|
||||
} else {
|
||||
mc.readPacket()
|
||||
}
|
||||
return err
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,23 +0,0 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 Julien Schmidt. All rights reserved.
|
||||
// http://www.julienschmidt.com
|
||||
//
|
||||
// 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
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 Julien Schmidt. All rights reserved.
|
||||
// http://www.julienschmidt.com
|
||||
//
|
||||
// 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"
|
||||
"io"
|
||||
)
|
||||
|
||||
type mysqlField struct {
|
||||
name string
|
||||
fieldType byte
|
||||
flags fieldFlag
|
||||
}
|
||||
|
||||
type mysqlRows struct {
|
||||
mc *mysqlConn
|
||||
binary bool
|
||||
columns []mysqlField
|
||||
eof bool
|
||||
}
|
||||
|
||||
func (rows *mysqlRows) Columns() (columns []string) {
|
||||
columns = make([]string, len(rows.columns))
|
||||
for i := range columns {
|
||||
columns[i] = rows.columns[i].name
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rows *mysqlRows) Close() (err error) {
|
||||
defer func() {
|
||||
rows.mc = nil
|
||||
}()
|
||||
|
||||
// Remove unread packets from stream
|
||||
if !rows.eof {
|
||||
if rows.mc == nil {
|
||||
return errors.New("Invalid Connection")
|
||||
}
|
||||
|
||||
err = rows.mc.readUntilEOF()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (rows *mysqlRows) Next(dest []driver.Value) error {
|
||||
if rows.eof {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
if rows.mc == nil {
|
||||
return errors.New("Invalid Connection")
|
||||
}
|
||||
|
||||
// Fetch next row from stream
|
||||
var err error
|
||||
if rows.binary {
|
||||
err = rows.readBinaryRow(dest)
|
||||
} else {
|
||||
err = rows.readRow(dest)
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
rows.eof = true
|
||||
}
|
||||
return err
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 Julien Schmidt. All rights reserved.
|
||||
// http://www.julienschmidt.com
|
||||
//
|
||||
// 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
|
||||
params []mysqlField
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) Close() (err error) {
|
||||
err = stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id)
|
||||
stmt.mc = nil
|
||||
return
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) NumInput() int {
|
||||
return stmt.paramCount
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||
stmt.mc.affectedRows = 0
|
||||
stmt.mc.insertId = 0
|
||||
|
||||
// Send command
|
||||
err := stmt.writeExecutePacket(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read Result
|
||||
var resLen int
|
||||
resLen, err = stmt.mc.readResultSetHeaderPacket()
|
||||
if err == nil {
|
||||
if resLen > 0 {
|
||||
// Columns
|
||||
err = stmt.mc.readUntilEOF()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Rows
|
||||
err = stmt.mc.readUntilEOF()
|
||||
}
|
||||
if err == nil {
|
||||
return &mysqlResult{
|
||||
affectedRows: int64(stmt.mc.affectedRows),
|
||||
insertId: int64(stmt.mc.insertId),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||
// Send command
|
||||
err := stmt.writeExecutePacket(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read Result
|
||||
var resLen int
|
||||
resLen, err = stmt.mc.readResultSetHeaderPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows := &mysqlRows{stmt.mc, true, nil, false}
|
||||
|
||||
if resLen > 0 {
|
||||
// Columns
|
||||
rows.columns, err = stmt.mc.readColumns(resLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return rows, err
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 Julien Schmidt. All rights reserved.
|
||||
// http://www.julienschmidt.com
|
||||
//
|
||||
// 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) {
|
||||
err = tx.mc.exec("COMMIT")
|
||||
tx.mc = nil
|
||||
return
|
||||
}
|
||||
|
||||
func (tx *mysqlTx) Rollback() (err error) {
|
||||
err = tx.mc.exec("ROLLBACK")
|
||||
tx.mc = nil
|
||||
return
|
||||
}
|
@ -1,428 +0,0 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 Julien Schmidt. All rights reserved.
|
||||
// http://www.julienschmidt.com
|
||||
//
|
||||
// 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"
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Logger
|
||||
var (
|
||||
errLog *log.Logger
|
||||
)
|
||||
|
||||
func init() {
|
||||
errLog = log.New(os.Stderr, "[MySQL] ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||
|
||||
dsnPattern = regexp.MustCompile(
|
||||
`^(?:(?P<user>.*?)(?::(?P<passwd>.*))?@)?` + // [user[:password]@]
|
||||
`(?:(?P<net>[^\(]*)(?:\((?P<addr>[^\)]*)\))?)?` + // [net[(addr)]]
|
||||
`\/(?P<dbname>.*?)` + // /dbname
|
||||
`(?:\?(?P<params>[^\?]*))?$`) // [?param1=value1¶mN=valueN]
|
||||
}
|
||||
|
||||
// Data Source Name Parser
|
||||
var dsnPattern *regexp.Regexp
|
||||
|
||||
func parseDSN(dsn string) (cfg *config, err error) {
|
||||
cfg = new(config)
|
||||
cfg.params = make(map[string]string)
|
||||
|
||||
matches := dsnPattern.FindStringSubmatch(dsn)
|
||||
names := dsnPattern.SubexpNames()
|
||||
|
||||
for i, match := range matches {
|
||||
switch names[i] {
|
||||
case "user":
|
||||
cfg.user = match
|
||||
case "passwd":
|
||||
cfg.passwd = match
|
||||
case "net":
|
||||
cfg.net = match
|
||||
case "addr":
|
||||
cfg.addr = match
|
||||
case "dbname":
|
||||
cfg.dbname = match
|
||||
case "params":
|
||||
for _, v := range strings.Split(match, "&") {
|
||||
param := strings.SplitN(v, "=", 2)
|
||||
if len(param) != 2 {
|
||||
continue
|
||||
}
|
||||
cfg.params[param[0]] = param[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set default network if empty
|
||||
if cfg.net == "" {
|
||||
cfg.net = "tcp"
|
||||
}
|
||||
|
||||
// Set default adress if empty
|
||||
if cfg.addr == "" {
|
||||
cfg.addr = "127.0.0.1:3306"
|
||||
}
|
||||
|
||||
cfg.loc, err = time.LoadLocation(cfg.params["loc"])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Encrypt password using 4.1+ method
|
||||
// http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#4.1_and_later
|
||||
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
|
||||
}
|
||||
|
||||
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
|
||||
switch len(str) {
|
||||
case 10: // YYYY-MM-DD
|
||||
if str == "0000-00-00" {
|
||||
return
|
||||
}
|
||||
t, err = time.Parse(timeFormat[:10], str)
|
||||
case 19: // YYYY-MM-DD HH:MM:SS
|
||||
if str == "0000-00-00 00:00:00" {
|
||||
return
|
||||
}
|
||||
t, err = time.Parse(timeFormat, str)
|
||||
default:
|
||||
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)
|
||||
}
|
||||
|
||||
func formatBinaryDate(num uint64, data []byte) (driver.Value, error) {
|
||||
switch num {
|
||||
case 0:
|
||||
return []byte("0000-00-00"), nil
|
||||
case 4:
|
||||
return []byte(fmt.Sprintf(
|
||||
"%04d-%02d-%02d",
|
||||
binary.LittleEndian.Uint16(data[:2]),
|
||||
data[2],
|
||||
data[3],
|
||||
)), nil
|
||||
}
|
||||
return nil, fmt.Errorf("Invalid DATE-packet length %d", num)
|
||||
}
|
||||
|
||||
func formatBinaryDateTime(num uint64, data []byte) (driver.Value, error) {
|
||||
switch num {
|
||||
case 0:
|
||||
return []byte("0000-00-00 00:00:00"), nil
|
||||
case 4:
|
||||
return []byte(fmt.Sprintf(
|
||||
"%04d-%02d-%02d 00:00:00",
|
||||
binary.LittleEndian.Uint16(data[:2]),
|
||||
data[2],
|
||||
data[3],
|
||||
)), nil
|
||||
case 7:
|
||||
return []byte(fmt.Sprintf(
|
||||
"%04d-%02d-%02d %02d:%02d:%02d",
|
||||
binary.LittleEndian.Uint16(data[:2]),
|
||||
data[2],
|
||||
data[3],
|
||||
data[4],
|
||||
data[5],
|
||||
data[6],
|
||||
)), nil
|
||||
case 11:
|
||||
return []byte(fmt.Sprintf(
|
||||
"%04d-%02d-%02d %02d:%02d:%02d.%06d",
|
||||
binary.LittleEndian.Uint16(data[:2]),
|
||||
data[2],
|
||||
data[3],
|
||||
data[4],
|
||||
data[5],
|
||||
data[6],
|
||||
binary.LittleEndian.Uint32(data[7:11]),
|
||||
)), nil
|
||||
}
|
||||
return nil, fmt.Errorf("Invalid DATETIME-packet length %d", num)
|
||||
}
|
||||
|
||||
func readBool(value string) bool {
|
||||
switch strings.ToLower(value) {
|
||||
case "true":
|
||||
return true
|
||||
case "1":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* 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
|
||||
}
|
||||
|
||||
func readLengthEnodedString(b []byte) ([]byte, bool, int, error) {
|
||||
// Get length
|
||||
num, isNull, n := readLengthEncodedInteger(b)
|
||||
if num < 1 {
|
||||
return nil, 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
|
||||
}
|
||||
|
||||
func skipLengthEnodedString(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
|
||||
}
|
||||
|
||||
func readLengthEncodedInteger(b []byte) (num uint64, isNull bool, n int) {
|
||||
switch b[0] {
|
||||
|
||||
// 251: NULL
|
||||
case 0xfb:
|
||||
n = 1
|
||||
isNull = true
|
||||
return
|
||||
|
||||
// 252: value of following 2
|
||||
case 0xfc:
|
||||
num = uint64(b[1]) | uint64(b[2])<<8
|
||||
n = 3
|
||||
return
|
||||
|
||||
// 253: value of following 3
|
||||
case 0xfd:
|
||||
num = uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16
|
||||
n = 4
|
||||
return
|
||||
|
||||
// 254: value of following 8
|
||||
case 0xfe:
|
||||
num = 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])<<54
|
||||
n = 9
|
||||
return
|
||||
}
|
||||
|
||||
// 0-250: value of first byte
|
||||
num = uint64(b[0])
|
||||
n = 1
|
||||
return
|
||||
}
|
||||
|
||||
func lengthEncodedIntegerToBytes(n uint64) []byte {
|
||||
switch {
|
||||
case n <= 250:
|
||||
return []byte{byte(n)}
|
||||
|
||||
case n <= 0xffff:
|
||||
return []byte{0xfc, byte(n), byte(n >> 8)}
|
||||
|
||||
case n <= 0xffffff:
|
||||
return []byte{0xfd, byte(n), byte(n >> 8), byte(n >> 16)}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||
// http://www.julienschmidt.com
|
||||
//
|
||||
// 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"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDSNParser(t *testing.T) {
|
||||
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}", 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}", time.UTC},
|
||||
{"user:password@tcp(localhost:5555)/dbname?charset=utf8", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8] loc:%p}", time.UTC},
|
||||
{"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8mb4,utf8] loc:%p}", time.UTC},
|
||||
{"user:password@/dbname?loc=UTC", "&{user:user passwd:password net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[loc:UTC] loc:%p}", 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:Local] loc:%p}", time.Local},
|
||||
{"/dbname", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p}", time.UTC},
|
||||
{"/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p}", time.UTC},
|
||||
{"user:p@/ssword@/", "&{user:user passwd:p@/ssword net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p}", time.UTC},
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
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 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 %b, got %b", tst.in, tst.error, (err != nil))
|
||||
}
|
||||
if nt.Valid != tst.valid {
|
||||
t.Errorf("%v: expected valid status %b, got %b", 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
@ -1,27 +0,0 @@
|
||||
package hack
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// no copy to change slice to string
|
||||
// use your own risk
|
||||
func String(b []byte) (s string) {
|
||||
pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
pstring := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
pstring.Data = pbytes.Data
|
||||
pstring.Len = pbytes.Len
|
||||
return
|
||||
}
|
||||
|
||||
// no copy to change string to slice
|
||||
// use your own risk
|
||||
func Slice(s string) (b []byte) {
|
||||
pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
pstring := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
pbytes.Data = pstring.Data
|
||||
pbytes.Len = pstring.Len
|
||||
pbytes.Cap = pstring.Len
|
||||
return
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package hack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
b := []byte("hello world")
|
||||
a := String(b)
|
||||
|
||||
if a != "hello world" {
|
||||
t.Fatal(a)
|
||||
}
|
||||
|
||||
b[0] = 'a'
|
||||
|
||||
if a != "aello world" {
|
||||
t.Fatal(a)
|
||||
}
|
||||
|
||||
b = append(b, "abc"...)
|
||||
if a != "aello world" {
|
||||
t.Fatal(a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestByte(t *testing.T) {
|
||||
a := "hello world"
|
||||
|
||||
b := Slice(a)
|
||||
|
||||
if !bytes.Equal(b, []byte("hello world")) {
|
||||
t.Fatal(string(b))
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
// Copyright 2012, Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ioutil2
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
// Write file to temp and atomically move when everything else succeeds.
|
||||
func WriteFileAtomic(filename string, data []byte, perm os.FileMode) error {
|
||||
dir, name := path.Split(filename)
|
||||
f, err := ioutil.TempFile(dir, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := f.Write(data)
|
||||
f.Close()
|
||||
if err == nil && n < len(data) {
|
||||
err = io.ErrShortWrite
|
||||
} else {
|
||||
err = os.Chmod(f.Name(), perm)
|
||||
}
|
||||
if err != nil {
|
||||
os.Remove(f.Name())
|
||||
return err
|
||||
}
|
||||
return os.Rename(f.Name(), filename)
|
||||
}
|
||||
|
||||
// Check file exists or not
|
||||
func FileExists(name string) bool {
|
||||
_, err := os.Stat(name)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
package ioutil2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
var ErrExceedLimit = errors.New("write exceed limit")
|
||||
|
||||
func NewSectionWriter(w io.WriterAt, off int64, n int64) *SectionWriter {
|
||||
return &SectionWriter{w, off, off, off + n}
|
||||
}
|
||||
|
||||
type SectionWriter struct {
|
||||
w io.WriterAt
|
||||
base int64
|
||||
off int64
|
||||
limit int64
|
||||
}
|
||||
|
||||
func (s *SectionWriter) Write(p []byte) (n int, err error) {
|
||||
if s.off >= s.limit {
|
||||
return 0, ErrExceedLimit
|
||||
}
|
||||
|
||||
if max := s.limit - s.off; int64(len(p)) > max {
|
||||
return 0, ErrExceedLimit
|
||||
}
|
||||
|
||||
n, err = s.w.WriteAt(p, s.off)
|
||||
s.off += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
var errWhence = errors.New("Seek: invalid whence")
|
||||
var errOffset = errors.New("Seek: invalid offset")
|
||||
|
||||
func (s *SectionWriter) Seek(offset int64, whence int) (int64, error) {
|
||||
switch whence {
|
||||
default:
|
||||
return 0, errWhence
|
||||
case 0:
|
||||
offset += s.base
|
||||
case 1:
|
||||
offset += s.off
|
||||
case 2:
|
||||
offset += s.limit
|
||||
}
|
||||
if offset < s.base {
|
||||
return 0, errOffset
|
||||
}
|
||||
s.off = offset
|
||||
return offset - s.base, nil
|
||||
}
|
||||
|
||||
func (s *SectionWriter) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
if off < 0 || off >= s.limit-s.base {
|
||||
return 0, errOffset
|
||||
}
|
||||
off += s.base
|
||||
if max := s.limit - off; int64(len(p)) > max {
|
||||
return 0, ErrExceedLimit
|
||||
}
|
||||
|
||||
return s.w.WriteAt(p, off)
|
||||
}
|
||||
|
||||
// Size returns the size of the section in bytes.
|
||||
func (s *SectionWriter) Size() int64 { return s.limit - s.base }
|
@ -1,56 +0,0 @@
|
||||
package ioutil2
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSectionWriter(t *testing.T) {
|
||||
f, err := ioutil.TempFile(".", "test_")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
n := f.Name()
|
||||
f.Close()
|
||||
os.Remove(n)
|
||||
}()
|
||||
|
||||
f.Truncate(3)
|
||||
|
||||
rw := NewSectionWriter(f, 0, 1)
|
||||
|
||||
_, err = rw.Write([]byte{'1'})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = rw.Write([]byte{'1'})
|
||||
if err == nil {
|
||||
t.Fatal("must err")
|
||||
}
|
||||
|
||||
rw = NewSectionWriter(f, 1, 2)
|
||||
|
||||
_, err = rw.Write([]byte{'2', '3', '4'})
|
||||
if err == nil {
|
||||
t.Fatal("must err")
|
||||
}
|
||||
|
||||
_, err = rw.Write([]byte{'2', '3'})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buf := make([]byte, 3)
|
||||
_, err = f.ReadAt(buf, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(buf) != "123" {
|
||||
t.Fatal(string(buf))
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
// log package supplies more advanced features than go orign log package.
|
||||
//
|
||||
// It supports log different level: trace, debug, info, warn, error, fatal.
|
||||
//
|
||||
// It also supports different log handlers which you can log to stdout, file, socket, etc...
|
||||
//
|
||||
// Use
|
||||
//
|
||||
// import "github.com/siddontang/go/log"
|
||||
//
|
||||
// //log with different level
|
||||
// log.Info("hello world")
|
||||
// log.Error("hello world")
|
||||
//
|
||||
// //create a logger with specified handler
|
||||
// h := NewStreamHandler(os.Stdout)
|
||||
// l := log.NewDefault(h)
|
||||
// l.Info("hello world")
|
||||
// l.Infof("%s %d", "hello", 123)
|
||||
//
|
||||
package log
|
@ -1,200 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
//FileHandler writes log to a file.
|
||||
type FileHandler struct {
|
||||
fd *os.File
|
||||
}
|
||||
|
||||
func NewFileHandler(fileName string, flag int) (*FileHandler, error) {
|
||||
dir := path.Dir(fileName)
|
||||
os.Mkdir(dir, 0777)
|
||||
|
||||
f, err := os.OpenFile(fileName, flag, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := new(FileHandler)
|
||||
|
||||
h.fd = f
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (h *FileHandler) Write(b []byte) (n int, err error) {
|
||||
return h.fd.Write(b)
|
||||
}
|
||||
|
||||
func (h *FileHandler) Close() error {
|
||||
return h.fd.Close()
|
||||
}
|
||||
|
||||
//RotatingFileHandler writes log a file, if file size exceeds maxBytes,
|
||||
//it will backup current file and open a new one.
|
||||
//
|
||||
//max backup file number is set by backupCount, it will delete oldest if backups too many.
|
||||
type RotatingFileHandler struct {
|
||||
fd *os.File
|
||||
|
||||
fileName string
|
||||
maxBytes int
|
||||
backupCount int
|
||||
}
|
||||
|
||||
func NewRotatingFileHandler(fileName string, maxBytes int, backupCount int) (*RotatingFileHandler, error) {
|
||||
dir := path.Dir(fileName)
|
||||
os.Mkdir(dir, 0777)
|
||||
|
||||
h := new(RotatingFileHandler)
|
||||
|
||||
if maxBytes <= 0 {
|
||||
return nil, fmt.Errorf("invalid max bytes")
|
||||
}
|
||||
|
||||
h.fileName = fileName
|
||||
h.maxBytes = maxBytes
|
||||
h.backupCount = backupCount
|
||||
|
||||
var err error
|
||||
h.fd, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (h *RotatingFileHandler) Write(p []byte) (n int, err error) {
|
||||
h.doRollover()
|
||||
return h.fd.Write(p)
|
||||
}
|
||||
|
||||
func (h *RotatingFileHandler) Close() error {
|
||||
if h.fd != nil {
|
||||
return h.fd.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *RotatingFileHandler) doRollover() {
|
||||
f, err := h.fd.Stat()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if h.maxBytes <= 0 {
|
||||
return
|
||||
} else if f.Size() < int64(h.maxBytes) {
|
||||
return
|
||||
}
|
||||
|
||||
if h.backupCount > 0 {
|
||||
h.fd.Close()
|
||||
|
||||
for i := h.backupCount - 1; i > 0; i-- {
|
||||
sfn := fmt.Sprintf("%s.%d", h.fileName, i)
|
||||
dfn := fmt.Sprintf("%s.%d", h.fileName, i+1)
|
||||
|
||||
os.Rename(sfn, dfn)
|
||||
}
|
||||
|
||||
dfn := fmt.Sprintf("%s.1", h.fileName)
|
||||
os.Rename(h.fileName, dfn)
|
||||
|
||||
h.fd, _ = os.OpenFile(h.fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
}
|
||||
}
|
||||
|
||||
//TimeRotatingFileHandler writes log to a file,
|
||||
//it will backup current and open a new one, with a period time you sepecified.
|
||||
//
|
||||
//refer: http://docs.python.org/2/library/logging.handlers.html.
|
||||
//same like python TimedRotatingFileHandler.
|
||||
type TimeRotatingFileHandler struct {
|
||||
fd *os.File
|
||||
|
||||
baseName string
|
||||
interval int64
|
||||
suffix string
|
||||
rolloverAt int64
|
||||
}
|
||||
|
||||
const (
|
||||
WhenSecond = iota
|
||||
WhenMinute
|
||||
WhenHour
|
||||
WhenDay
|
||||
)
|
||||
|
||||
func NewTimeRotatingFileHandler(baseName string, when int8, interval int) (*TimeRotatingFileHandler, error) {
|
||||
dir := path.Dir(baseName)
|
||||
os.Mkdir(dir, 0777)
|
||||
|
||||
h := new(TimeRotatingFileHandler)
|
||||
|
||||
h.baseName = baseName
|
||||
|
||||
switch when {
|
||||
case WhenSecond:
|
||||
h.interval = 1
|
||||
h.suffix = "2006-01-02_15-04-05"
|
||||
case WhenMinute:
|
||||
h.interval = 60
|
||||
h.suffix = "2006-01-02_15-04"
|
||||
case WhenHour:
|
||||
h.interval = 3600
|
||||
h.suffix = "2006-01-02_15"
|
||||
case WhenDay:
|
||||
h.interval = 3600 * 24
|
||||
h.suffix = "2006-01-02"
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid when_rotate: %d", when)
|
||||
}
|
||||
|
||||
h.interval = h.interval * int64(interval)
|
||||
|
||||
var err error
|
||||
h.fd, err = os.OpenFile(h.baseName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fInfo, _ := h.fd.Stat()
|
||||
h.rolloverAt = fInfo.ModTime().Unix() + h.interval
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (h *TimeRotatingFileHandler) doRollover() {
|
||||
//refer http://hg.python.org/cpython/file/2.7/Lib/logging/handlers.py
|
||||
now := time.Now()
|
||||
|
||||
if h.rolloverAt <= now.Unix() {
|
||||
fName := h.baseName + now.Format(h.suffix)
|
||||
h.fd.Close()
|
||||
e := os.Rename(h.baseName, fName)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
|
||||
h.fd, _ = os.OpenFile(h.baseName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
|
||||
h.rolloverAt = time.Now().Unix() + h.interval
|
||||
}
|
||||
}
|
||||
|
||||
func (h *TimeRotatingFileHandler) Write(b []byte) (n int, err error) {
|
||||
h.doRollover()
|
||||
return h.fd.Write(b)
|
||||
}
|
||||
|
||||
func (h *TimeRotatingFileHandler) Close() error {
|
||||
return h.fd.Close()
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
//Handler writes logs to somewhere
|
||||
type Handler interface {
|
||||
Write(p []byte) (n int, err error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
//StreamHandler writes logs to a specified io Writer, maybe stdout, stderr, etc...
|
||||
type StreamHandler struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func NewStreamHandler(w io.Writer) (*StreamHandler, error) {
|
||||
h := new(StreamHandler)
|
||||
|
||||
h.w = w
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (h *StreamHandler) Write(b []byte) (n int, err error) {
|
||||
return h.w.Write(b)
|
||||
}
|
||||
|
||||
func (h *StreamHandler) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
//NullHandler does nothing, it discards anything.
|
||||
type NullHandler struct {
|
||||
}
|
||||
|
||||
func NewNullHandler() (*NullHandler, error) {
|
||||
return new(NullHandler), nil
|
||||
}
|
||||
|
||||
func (h *NullHandler) Write(b []byte) (n int, err error) {
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (h *NullHandler) Close() {
|
||||
|
||||
}
|
@ -1,341 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
//log level, from low to high, more high means more serious
|
||||
const (
|
||||
LevelTrace = iota
|
||||
LevelDebug
|
||||
LevelInfo
|
||||
LevelWarn
|
||||
LevelError
|
||||
LevelFatal
|
||||
)
|
||||
|
||||
const (
|
||||
Ltime = 1 << iota //time format "2006/01/02 15:04:05"
|
||||
Lfile //file.go:123
|
||||
Llevel //[Trace|Debug|Info...]
|
||||
)
|
||||
|
||||
var LevelName [6]string = [6]string{"Trace", "Debug", "Info", "Warn", "Error", "Fatal"}
|
||||
|
||||
const TimeFormat = "2006/01/02 15:04:05"
|
||||
|
||||
const maxBufPoolSize = 16
|
||||
|
||||
type atomicInt32 int32
|
||||
|
||||
func (i *atomicInt32) Set(n int) {
|
||||
atomic.StoreInt32((*int32)(i), int32(n))
|
||||
}
|
||||
|
||||
func (i *atomicInt32) Get() int {
|
||||
return int(atomic.LoadInt32((*int32)(i)))
|
||||
}
|
||||
|
||||
type Logger struct {
|
||||
level atomicInt32
|
||||
flag int
|
||||
|
||||
hMutex sync.Mutex
|
||||
handler Handler
|
||||
|
||||
quit chan struct{}
|
||||
msg chan []byte
|
||||
|
||||
bufMutex sync.Mutex
|
||||
bufs [][]byte
|
||||
|
||||
wg sync.WaitGroup
|
||||
|
||||
closed atomicInt32
|
||||
}
|
||||
|
||||
//new a logger with specified handler and flag
|
||||
func New(handler Handler, flag int) *Logger {
|
||||
var l = new(Logger)
|
||||
|
||||
l.level.Set(LevelInfo)
|
||||
l.handler = handler
|
||||
|
||||
l.flag = flag
|
||||
|
||||
l.quit = make(chan struct{})
|
||||
l.closed.Set(0)
|
||||
|
||||
l.msg = make(chan []byte, 1024)
|
||||
|
||||
l.bufs = make([][]byte, 0, 16)
|
||||
|
||||
l.wg.Add(1)
|
||||
go l.run()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
//new a default logger with specified handler and flag: Ltime|Lfile|Llevel
|
||||
func NewDefault(handler Handler) *Logger {
|
||||
return New(handler, Ltime|Lfile|Llevel)
|
||||
}
|
||||
|
||||
func newStdHandler() *StreamHandler {
|
||||
h, _ := NewStreamHandler(os.Stdout)
|
||||
return h
|
||||
}
|
||||
|
||||
var std = NewDefault(newStdHandler())
|
||||
|
||||
func (l *Logger) run() {
|
||||
defer l.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case msg := <-l.msg:
|
||||
l.hMutex.Lock()
|
||||
l.handler.Write(msg)
|
||||
l.hMutex.Unlock()
|
||||
l.putBuf(msg)
|
||||
case <-l.quit:
|
||||
//we must log all msg
|
||||
if len(l.msg) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) popBuf() []byte {
|
||||
l.bufMutex.Lock()
|
||||
var buf []byte
|
||||
if len(l.bufs) == 0 {
|
||||
buf = make([]byte, 0, 1024)
|
||||
} else {
|
||||
buf = l.bufs[len(l.bufs)-1]
|
||||
l.bufs = l.bufs[0 : len(l.bufs)-1]
|
||||
}
|
||||
l.bufMutex.Unlock()
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func (l *Logger) putBuf(buf []byte) {
|
||||
l.bufMutex.Lock()
|
||||
if len(l.bufs) < maxBufPoolSize {
|
||||
buf = buf[0:0]
|
||||
l.bufs = append(l.bufs, buf)
|
||||
}
|
||||
l.bufMutex.Unlock()
|
||||
}
|
||||
|
||||
func (l *Logger) Close() {
|
||||
if l.closed.Get() == 1 {
|
||||
return
|
||||
}
|
||||
l.closed.Set(1)
|
||||
|
||||
close(l.quit)
|
||||
|
||||
l.wg.Wait()
|
||||
|
||||
l.quit = nil
|
||||
|
||||
l.handler.Close()
|
||||
}
|
||||
|
||||
//set log level, any log level less than it will not log
|
||||
func (l *Logger) SetLevel(level int) {
|
||||
l.level.Set(level)
|
||||
}
|
||||
|
||||
func (l *Logger) SetHandler(h Handler) {
|
||||
if l.closed.Get() == 1 {
|
||||
return
|
||||
}
|
||||
|
||||
l.hMutex.Lock()
|
||||
if l.handler != nil {
|
||||
l.handler.Close()
|
||||
}
|
||||
l.handler = h
|
||||
l.hMutex.Unlock()
|
||||
}
|
||||
|
||||
func (l *Logger) Output(callDepth int, level int, s string) {
|
||||
if l.closed.Get() == 1 {
|
||||
// closed
|
||||
return
|
||||
}
|
||||
|
||||
if l.level.Get() > level {
|
||||
// higher level can be logged
|
||||
return
|
||||
}
|
||||
|
||||
buf := l.popBuf()
|
||||
|
||||
if l.flag&Ltime > 0 {
|
||||
now := time.Now().Format(TimeFormat)
|
||||
buf = append(buf, '[')
|
||||
buf = append(buf, now...)
|
||||
buf = append(buf, "] "...)
|
||||
}
|
||||
|
||||
if l.flag&Lfile > 0 {
|
||||
_, file, line, ok := runtime.Caller(callDepth)
|
||||
if !ok {
|
||||
file = "???"
|
||||
line = 0
|
||||
} else {
|
||||
for i := len(file) - 1; i > 0; i-- {
|
||||
if file[i] == '/' {
|
||||
file = file[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf = append(buf, file...)
|
||||
buf = append(buf, ':')
|
||||
|
||||
buf = strconv.AppendInt(buf, int64(line), 10)
|
||||
buf = append(buf, ' ')
|
||||
}
|
||||
|
||||
if l.flag&Llevel > 0 {
|
||||
buf = append(buf, '[')
|
||||
buf = append(buf, LevelName[level]...)
|
||||
buf = append(buf, "] "...)
|
||||
}
|
||||
|
||||
buf = append(buf, s...)
|
||||
|
||||
if s[len(s)-1] != '\n' {
|
||||
buf = append(buf, '\n')
|
||||
}
|
||||
|
||||
l.msg <- buf
|
||||
}
|
||||
|
||||
//log with Trace level
|
||||
func (l *Logger) Trace(v ...interface{}) {
|
||||
l.Output(2, LevelTrace, fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
//log with Debug level
|
||||
func (l *Logger) Debug(v ...interface{}) {
|
||||
l.Output(2, LevelDebug, fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
//log with info level
|
||||
func (l *Logger) Info(v ...interface{}) {
|
||||
l.Output(2, LevelInfo, fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
//log with warn level
|
||||
func (l *Logger) Warn(v ...interface{}) {
|
||||
l.Output(2, LevelWarn, fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
//log with error level
|
||||
func (l *Logger) Error(v ...interface{}) {
|
||||
l.Output(2, LevelError, fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
//log with fatal level
|
||||
func (l *Logger) Fatal(v ...interface{}) {
|
||||
l.Output(2, LevelFatal, fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
//log with Trace level
|
||||
func (l *Logger) Tracef(format string, v ...interface{}) {
|
||||
l.Output(2, LevelTrace, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
//log with Debug level
|
||||
func (l *Logger) Debugf(format string, v ...interface{}) {
|
||||
l.Output(2, LevelDebug, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
//log with info level
|
||||
func (l *Logger) Infof(format string, v ...interface{}) {
|
||||
l.Output(2, LevelInfo, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
//log with warn level
|
||||
func (l *Logger) Warnf(format string, v ...interface{}) {
|
||||
l.Output(2, LevelWarn, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
//log with error level
|
||||
func (l *Logger) Errorf(format string, v ...interface{}) {
|
||||
l.Output(2, LevelError, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
//log with fatal level
|
||||
func (l *Logger) Fatalf(format string, v ...interface{}) {
|
||||
l.Output(2, LevelFatal, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func SetLevel(level int) {
|
||||
std.SetLevel(level)
|
||||
}
|
||||
|
||||
func SetHandler(h Handler) {
|
||||
std.SetHandler(h)
|
||||
}
|
||||
|
||||
func Trace(v ...interface{}) {
|
||||
std.Output(2, LevelTrace, fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func Debug(v ...interface{}) {
|
||||
std.Output(2, LevelDebug, fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func Info(v ...interface{}) {
|
||||
std.Output(2, LevelInfo, fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func Warn(v ...interface{}) {
|
||||
std.Output(2, LevelWarn, fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func Error(v ...interface{}) {
|
||||
std.Output(2, LevelError, fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func Fatal(v ...interface{}) {
|
||||
std.Output(2, LevelFatal, fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func Tracef(format string, v ...interface{}) {
|
||||
std.Output(2, LevelTrace, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Debugf(format string, v ...interface{}) {
|
||||
std.Output(2, LevelDebug, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Infof(format string, v ...interface{}) {
|
||||
std.Output(2, LevelInfo, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Warnf(format string, v ...interface{}) {
|
||||
std.Output(2, LevelWarn, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Errorf(format string, v ...interface{}) {
|
||||
std.Output(2, LevelError, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Fatalf(format string, v ...interface{}) {
|
||||
std.Output(2, LevelFatal, fmt.Sprintf(format, v...))
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStdStreamLog(t *testing.T) {
|
||||
h, _ := NewStreamHandler(os.Stdout)
|
||||
s := NewDefault(h)
|
||||
s.Info("hello world")
|
||||
|
||||
s.Close()
|
||||
|
||||
s.Info("can not log")
|
||||
|
||||
Info("hello world")
|
||||
|
||||
SetHandler(os.Stderr)
|
||||
|
||||
Infof("%s %d", "Hello", 123)
|
||||
|
||||
SetLevel(LevelError)
|
||||
|
||||
Infof("%s %d", "Hello", 123)
|
||||
Fatalf("%s %d", "Hello", 123)
|
||||
}
|
||||
|
||||
func TestRotatingFileLog(t *testing.T) {
|
||||
path := "./test_log"
|
||||
os.RemoveAll(path)
|
||||
|
||||
os.Mkdir(path, 0777)
|
||||
fileName := path + "/test"
|
||||
|
||||
h, err := NewRotatingFileHandler(fileName, 10, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buf := make([]byte, 10)
|
||||
|
||||
h.Write(buf)
|
||||
|
||||
h.Write(buf)
|
||||
|
||||
if _, err := os.Stat(fileName + ".1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(fileName + ".2"); err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
h.Write(buf)
|
||||
if _, err := os.Stat(fileName + ".2"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
h.Close()
|
||||
|
||||
os.RemoveAll(path)
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
//SocketHandler writes log to a connectionl.
|
||||
//Network protocol is simple: log length + log | log length + log. log length is uint32, bigendian.
|
||||
//you must implement your own log server, maybe you can use logd instead simply.
|
||||
type SocketHandler struct {
|
||||
c net.Conn
|
||||
protocol string
|
||||
addr string
|
||||
}
|
||||
|
||||
func NewSocketHandler(protocol string, addr string) (*SocketHandler, error) {
|
||||
s := new(SocketHandler)
|
||||
|
||||
s.protocol = protocol
|
||||
s.addr = addr
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (h *SocketHandler) Write(p []byte) (n int, err error) {
|
||||
if err = h.connect(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf := make([]byte, len(p)+4)
|
||||
|
||||
binary.BigEndian.PutUint32(buf, uint32(len(p)))
|
||||
|
||||
copy(buf[4:], p)
|
||||
|
||||
n, err = h.c.Write(buf)
|
||||
if err != nil {
|
||||
h.c.Close()
|
||||
h.c = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h *SocketHandler) Close() error {
|
||||
if h.c != nil {
|
||||
h.c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *SocketHandler) connect() error {
|
||||
if h.c != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
h.c, err = net.DialTimeout(h.protocol, h.addr, 20*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
// Copyright 2013, Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sync2
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AtomicInt32 int32
|
||||
|
||||
func (i *AtomicInt32) Add(n int32) int32 {
|
||||
return atomic.AddInt32((*int32)(i), n)
|
||||
}
|
||||
|
||||
func (i *AtomicInt32) Set(n int32) {
|
||||
atomic.StoreInt32((*int32)(i), n)
|
||||
}
|
||||
|
||||
func (i *AtomicInt32) Get() int32 {
|
||||
return atomic.LoadInt32((*int32)(i))
|
||||
}
|
||||
|
||||
func (i *AtomicInt32) CompareAndSwap(oldval, newval int32) (swapped bool) {
|
||||
return atomic.CompareAndSwapInt32((*int32)(i), oldval, newval)
|
||||
}
|
||||
|
||||
type AtomicUint32 uint32
|
||||
|
||||
func (i *AtomicUint32) Add(n uint32) uint32 {
|
||||
return atomic.AddUint32((*uint32)(i), n)
|
||||
}
|
||||
|
||||
func (i *AtomicUint32) Set(n uint32) {
|
||||
atomic.StoreUint32((*uint32)(i), n)
|
||||
}
|
||||
|
||||
func (i *AtomicUint32) Get() uint32 {
|
||||
return atomic.LoadUint32((*uint32)(i))
|
||||
}
|
||||
|
||||
func (i *AtomicUint32) CompareAndSwap(oldval, newval uint32) (swapped bool) {
|
||||
return atomic.CompareAndSwapUint32((*uint32)(i), oldval, newval)
|
||||
}
|
||||
|
||||
type AtomicInt64 int64
|
||||
|
||||
func (i *AtomicInt64) Add(n int64) int64 {
|
||||
return atomic.AddInt64((*int64)(i), n)
|
||||
}
|
||||
|
||||
func (i *AtomicInt64) Set(n int64) {
|
||||
atomic.StoreInt64((*int64)(i), n)
|
||||
}
|
||||
|
||||
func (i *AtomicInt64) Get() int64 {
|
||||
return atomic.LoadInt64((*int64)(i))
|
||||
}
|
||||
|
||||
func (i *AtomicInt64) CompareAndSwap(oldval, newval int64) (swapped bool) {
|
||||
return atomic.CompareAndSwapInt64((*int64)(i), oldval, newval)
|
||||
}
|
||||
|
||||
type AtomicUint64 uint64
|
||||
|
||||
func (i *AtomicUint64) Add(n uint64) uint64 {
|
||||
return atomic.AddUint64((*uint64)(i), n)
|
||||
}
|
||||
|
||||
func (i *AtomicUint64) Set(n uint64) {
|
||||
atomic.StoreUint64((*uint64)(i), n)
|
||||
}
|
||||
|
||||
func (i *AtomicUint64) Get() uint64 {
|
||||
return atomic.LoadUint64((*uint64)(i))
|
||||
}
|
||||
|
||||
func (i *AtomicUint64) CompareAndSwap(oldval, newval uint64) (swapped bool) {
|
||||
return atomic.CompareAndSwapUint64((*uint64)(i), oldval, newval)
|
||||
}
|
||||
|
||||
type AtomicDuration int64
|
||||
|
||||
func (d *AtomicDuration) Add(duration time.Duration) time.Duration {
|
||||
return time.Duration(atomic.AddInt64((*int64)(d), int64(duration)))
|
||||
}
|
||||
|
||||
func (d *AtomicDuration) Set(duration time.Duration) {
|
||||
atomic.StoreInt64((*int64)(d), int64(duration))
|
||||
}
|
||||
|
||||
func (d *AtomicDuration) Get() time.Duration {
|
||||
return time.Duration(atomic.LoadInt64((*int64)(d)))
|
||||
}
|
||||
|
||||
func (d *AtomicDuration) CompareAndSwap(oldval, newval time.Duration) (swapped bool) {
|
||||
return atomic.CompareAndSwapInt64((*int64)(d), int64(oldval), int64(newval))
|
||||
}
|
||||
|
||||
// AtomicString gives you atomic-style APIs for string, but
|
||||
// it's only a convenience wrapper that uses a mutex. So, it's
|
||||
// not as efficient as the rest of the atomic types.
|
||||
type AtomicString struct {
|
||||
mu sync.Mutex
|
||||
str string
|
||||
}
|
||||
|
||||
func (s *AtomicString) Set(str string) {
|
||||
s.mu.Lock()
|
||||
s.str = str
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *AtomicString) Get() string {
|
||||
s.mu.Lock()
|
||||
str := s.str
|
||||
s.mu.Unlock()
|
||||
return str
|
||||
}
|
||||
|
||||
func (s *AtomicString) CompareAndSwap(oldval, newval string) (swqpped bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.str == oldval {
|
||||
s.str = newval
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type AtomicBool int32
|
||||
|
||||
func (b *AtomicBool) Set(v bool) {
|
||||
if v {
|
||||
atomic.StoreInt32((*int32)(b), 1)
|
||||
} else {
|
||||
atomic.StoreInt32((*int32)(b), 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *AtomicBool) Get() bool {
|
||||
return atomic.LoadInt32((*int32)(b)) == 1
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
// Copyright 2013, Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sync2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAtomicString(t *testing.T) {
|
||||
var s AtomicString
|
||||
if s.Get() != "" {
|
||||
t.Errorf("want empty, got %s", s.Get())
|
||||
}
|
||||
s.Set("a")
|
||||
if s.Get() != "a" {
|
||||
t.Errorf("want a, got %s", s.Get())
|
||||
}
|
||||
if s.CompareAndSwap("b", "c") {
|
||||
t.Errorf("want false, got true")
|
||||
}
|
||||
if s.Get() != "a" {
|
||||
t.Errorf("want a, got %s", s.Get())
|
||||
}
|
||||
if !s.CompareAndSwap("a", "c") {
|
||||
t.Errorf("want true, got false")
|
||||
}
|
||||
if s.Get() != "c" {
|
||||
t.Errorf("want c, got %s", s.Get())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAtomicBool(t *testing.T) {
|
||||
var b AtomicBool
|
||||
if b.Get() != false {
|
||||
t.Fatal("must false")
|
||||
}
|
||||
|
||||
b.Set(true)
|
||||
|
||||
if b.Get() != true {
|
||||
t.Fatal("must true")
|
||||
}
|
||||
|
||||
b.Set(false)
|
||||
|
||||
if b.Get() != false {
|
||||
t.Fatal("must false")
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user