Browse Source

个人博客评价,浏览次数的脚本

tangs 6 years ago
parent
commit
320d27420d
73 changed files with 10278 additions and 0 deletions
  1. 1 0
      .gitignore
  2. 11 0
      .idea/misc.xml
  3. 6 0
      .idea/vcs.xml
  4. 3 0
      config.conf
  5. 4 0
      install.sh
  6. 8 0
      src/github.com/go-sql-driver/mysql/.gitignore
  7. 12 0
      src/github.com/go-sql-driver/mysql/.travis.yml
  8. 52 0
      src/github.com/go-sql-driver/mysql/AUTHORS
  9. 103 0
      src/github.com/go-sql-driver/mysql/CHANGELOG.md
  10. 23 0
      src/github.com/go-sql-driver/mysql/CONTRIBUTING.md
  11. 21 0
      src/github.com/go-sql-driver/mysql/ISSUE_TEMPLATE.md
  12. 373 0
      src/github.com/go-sql-driver/mysql/LICENSE
  13. 9 0
      src/github.com/go-sql-driver/mysql/PULL_REQUEST_TEMPLATE.md
  14. 420 0
      src/github.com/go-sql-driver/mysql/README.md
  15. 19 0
      src/github.com/go-sql-driver/mysql/appengine.go
  16. 246 0
      src/github.com/go-sql-driver/mysql/benchmark_test.go
  17. 147 0
      src/github.com/go-sql-driver/mysql/buffer.go
  18. 250 0
      src/github.com/go-sql-driver/mysql/collations.go
  19. 372 0
      src/github.com/go-sql-driver/mysql/connection.go
  20. 163 0
      src/github.com/go-sql-driver/mysql/const.go
  21. 167 0
      src/github.com/go-sql-driver/mysql/driver.go
  22. 1857 0
      src/github.com/go-sql-driver/mysql/driver_test.go
  23. 513 0
      src/github.com/go-sql-driver/mysql/dsn.go
  24. 207 0
      src/github.com/go-sql-driver/mysql/dsn_test.go
  25. 131 0
      src/github.com/go-sql-driver/mysql/errors.go
  26. 42 0
      src/github.com/go-sql-driver/mysql/errors_test.go
  27. 181 0
      src/github.com/go-sql-driver/mysql/infile.go
  28. 1243 0
      src/github.com/go-sql-driver/mysql/packets.go
  29. 22 0
      src/github.com/go-sql-driver/mysql/result.go
  30. 112 0
      src/github.com/go-sql-driver/mysql/rows.go
  31. 150 0
      src/github.com/go-sql-driver/mysql/statement.go
  32. 31 0
      src/github.com/go-sql-driver/mysql/transaction.go
  33. 740 0
      src/github.com/go-sql-driver/mysql/utils.go
  34. 197 0
      src/github.com/go-sql-driver/mysql/utils_test.go
  35. 3 0
      src/github.com/tangs-drm/go-tool/README.md
  36. 46 0
      src/github.com/tangs-drm/go-tool/config/README.md
  37. 17 0
      src/github.com/tangs-drm/go-tool/config/config.conf
  38. 102 0
      src/github.com/tangs-drm/go-tool/config/config.go
  39. 96 0
      src/github.com/tangs-drm/go-tool/config/config_test.go
  40. 6 0
      src/github.com/tangs-drm/go-tool/config/errconfigType.type
  41. 12 0
      src/github.com/tangs-drm/go-tool/config/testconfig.conf
  42. 3 0
      src/github.com/tangs-drm/go-tool/dbm/README.md
  43. 110 0
      src/github.com/tangs-drm/go-tool/dbm/mysql.go
  44. 177 0
      src/github.com/tangs-drm/go-tool/dbm/mysql_test.go
  45. 49 0
      src/github.com/tangs-drm/go-tool/http/README.md
  46. 99 0
      src/github.com/tangs-drm/go-tool/http/http.go
  47. 168 0
      src/github.com/tangs-drm/go-tool/http/http_test.go
  48. 42 0
      src/github.com/tangs-drm/go-tool/http/response.go
  49. 2 0
      src/github.com/tangs-drm/go-tool/log/.gitignore
  50. 37 0
      src/github.com/tangs-drm/go-tool/log/README.md
  51. 132 0
      src/github.com/tangs-drm/go-tool/log/log.go
  52. 34 0
      src/github.com/tangs-drm/go-tool/log/log_test.go
  53. 31 0
      src/github.com/tangs-drm/go-tool/util/README.md
  54. 10 0
      src/github.com/tangs-drm/go-tool/util/error.go
  55. 22 0
      src/github.com/tangs-drm/go-tool/util/error_test.go
  56. 236 0
      src/github.com/tangs-drm/go-tool/util/map.go
  57. 375 0
      src/github.com/tangs-drm/go-tool/util/map_test.go
  58. 65 0
      src/github.com/tangs-drm/go-tool/util/request.go
  59. 131 0
      src/github.com/tangs-drm/go-tool/util/request_test.go
  60. 19 0
      src/github.com/tangs-drm/go-tool/util/time.go
  61. 15 0
      src/github.com/tangs-drm/go-tool/util/time_test.go
  62. 83 0
      src/github.com/tangs-drm/go-tool/util/value.go
  63. 152 0
      src/github.com/tangs-drm/go-tool/util/value_test.go
  64. 1 0
      src/gopkg.in/mgo.v2
  65. 51 0
      src/main.go
  66. 7 0
      src/response/message.go
  67. 1 0
      src/view/README.md
  68. 7 0
      src/view/viewapi/api.go
  69. 39 0
      src/view/viewapi/viewapi.go
  70. 17 0
      src/view/viewdb/db.go
  71. 13 0
      src/view/viewdb/items.go
  72. 32 0
      src/view/viewdb/record.go
  73. 0 0
      src/www/index.html

+ 1 - 0
.gitignore

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

+ 11 - 0
.idea/misc.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GOROOT" path="E:\Go" />
+  <component name="GoLibraries">
+    <option name="urls">
+      <list>
+        <option value="file://$PROJECT_DIR$" />
+      </list>
+    </option>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 3 - 0
config.conf

@@ -0,0 +1,3 @@
+db_url=127.0.0.1:27017
+db_name=blogScript
+listen_port=:9600

+ 4 - 0
install.sh

@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+echo "start install blogScript"
+
+echo "install blogScript success"

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

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

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

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

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

@@ -0,0 +1,52 @@
+# This is the official list of Go-MySQL-Driver authors for copyright purposes.
+
+# If you are submitting a patch, please add your name or the name of the
+# organization which holds the copyright to this list in alphabetical order.
+
+# Names should be added to this file as
+#	Name <email address>
+# The email address is not required for organizations.
+# Please keep the list sorted.
+
+
+# Individual Persons
+
+Aaron Hopkins <go-sql-driver at die.net>
+Arne Hormann <arnehormann at gmail.com>
+Carlos Nieto <jose.carlos at menteslibres.net>
+Chris Moos <chris at tech9computers.com>
+Daniel Nichter <nil at codenode.com>
+Daniël van Eeden <git at myname.nl>
+DisposaBoy <disposaboy at dby.me>
+Frederick Mayle <frederickmayle at gmail.com>
+Gustavo Kristic <gkristic at gmail.com>
+Hanno Braun <mail at hannobraun.com>
+Henri Yandell <flamefew at gmail.com>
+Hirotaka Yamamoto <ymmt2005 at gmail.com>
+INADA Naoki <songofacandy at gmail.com>
+James Harr <james.harr at gmail.com>
+Jian Zhen <zhenjl at gmail.com>
+Joshua Prunier <joshua.prunier at gmail.com>
+Julien Lefevre <julien.lefevr at gmail.com>
+Julien Schmidt <go-sql-driver at julienschmidt.com>
+Kamil Dziedzic <kamil at klecza.pl>
+Kevin Malachowski <kevin at chowski.com>
+Leonardo YongUk Kim <dalinaum at gmail.com>
+Luca Looz <luca.looz92 at gmail.com>
+Lucas Liu <extrafliu at gmail.com>
+Luke Scott <luke at webconnex.com>
+Michael Woolnough <michael.woolnough at gmail.com>
+Nicola Peduzzi <thenikso at gmail.com>
+Paul Bonser <misterpib at gmail.com>
+Runrioter Wung <runrioter at gmail.com>
+Soroush Pour <me at soroushjp.com>
+Stan Putrya <root.vagner at gmail.com>
+Stanley Gunawan <gunawan.stanley at gmail.com>
+Xiaobing Jiang <s7v7nislands at gmail.com>
+Xiuming Chen <cc at cxm.cc>
+
+# Organizations
+
+Barracuda Networks, Inc.
+Google Inc.
+Stripe Inc.

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

@@ -0,0 +1,250 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+const defaultCollation = "utf8_general_ci"
+
+// A list of available collations mapped to the internal ID.
+// To update this map use the following MySQL query:
+//     SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS
+var collations = map[string]byte{
+	"big5_chinese_ci":          1,
+	"latin2_czech_cs":          2,
+	"dec8_swedish_ci":          3,
+	"cp850_general_ci":         4,
+	"latin1_german1_ci":        5,
+	"hp8_english_ci":           6,
+	"koi8r_general_ci":         7,
+	"latin1_swedish_ci":        8,
+	"latin2_general_ci":        9,
+	"swe7_swedish_ci":          10,
+	"ascii_general_ci":         11,
+	"ujis_japanese_ci":         12,
+	"sjis_japanese_ci":         13,
+	"cp1251_bulgarian_ci":      14,
+	"latin1_danish_ci":         15,
+	"hebrew_general_ci":        16,
+	"tis620_thai_ci":           18,
+	"euckr_korean_ci":          19,
+	"latin7_estonian_cs":       20,
+	"latin2_hungarian_ci":      21,
+	"koi8u_general_ci":         22,
+	"cp1251_ukrainian_ci":      23,
+	"gb2312_chinese_ci":        24,
+	"greek_general_ci":         25,
+	"cp1250_general_ci":        26,
+	"latin2_croatian_ci":       27,
+	"gbk_chinese_ci":           28,
+	"cp1257_lithuanian_ci":     29,
+	"latin5_turkish_ci":        30,
+	"latin1_german2_ci":        31,
+	"armscii8_general_ci":      32,
+	"utf8_general_ci":          33,
+	"cp1250_czech_cs":          34,
+	"ucs2_general_ci":          35,
+	"cp866_general_ci":         36,
+	"keybcs2_general_ci":       37,
+	"macce_general_ci":         38,
+	"macroman_general_ci":      39,
+	"cp852_general_ci":         40,
+	"latin7_general_ci":        41,
+	"latin7_general_cs":        42,
+	"macce_bin":                43,
+	"cp1250_croatian_ci":       44,
+	"utf8mb4_general_ci":       45,
+	"utf8mb4_bin":              46,
+	"latin1_bin":               47,
+	"latin1_general_ci":        48,
+	"latin1_general_cs":        49,
+	"cp1251_bin":               50,
+	"cp1251_general_ci":        51,
+	"cp1251_general_cs":        52,
+	"macroman_bin":             53,
+	"utf16_general_ci":         54,
+	"utf16_bin":                55,
+	"utf16le_general_ci":       56,
+	"cp1256_general_ci":        57,
+	"cp1257_bin":               58,
+	"cp1257_general_ci":        59,
+	"utf32_general_ci":         60,
+	"utf32_bin":                61,
+	"utf16le_bin":              62,
+	"binary":                   63,
+	"armscii8_bin":             64,
+	"ascii_bin":                65,
+	"cp1250_bin":               66,
+	"cp1256_bin":               67,
+	"cp866_bin":                68,
+	"dec8_bin":                 69,
+	"greek_bin":                70,
+	"hebrew_bin":               71,
+	"hp8_bin":                  72,
+	"keybcs2_bin":              73,
+	"koi8r_bin":                74,
+	"koi8u_bin":                75,
+	"latin2_bin":               77,
+	"latin5_bin":               78,
+	"latin7_bin":               79,
+	"cp850_bin":                80,
+	"cp852_bin":                81,
+	"swe7_bin":                 82,
+	"utf8_bin":                 83,
+	"big5_bin":                 84,
+	"euckr_bin":                85,
+	"gb2312_bin":               86,
+	"gbk_bin":                  87,
+	"sjis_bin":                 88,
+	"tis620_bin":               89,
+	"ucs2_bin":                 90,
+	"ujis_bin":                 91,
+	"geostd8_general_ci":       92,
+	"geostd8_bin":              93,
+	"latin1_spanish_ci":        94,
+	"cp932_japanese_ci":        95,
+	"cp932_bin":                96,
+	"eucjpms_japanese_ci":      97,
+	"eucjpms_bin":              98,
+	"cp1250_polish_ci":         99,
+	"utf16_unicode_ci":         101,
+	"utf16_icelandic_ci":       102,
+	"utf16_latvian_ci":         103,
+	"utf16_romanian_ci":        104,
+	"utf16_slovenian_ci":       105,
+	"utf16_polish_ci":          106,
+	"utf16_estonian_ci":        107,
+	"utf16_spanish_ci":         108,
+	"utf16_swedish_ci":         109,
+	"utf16_turkish_ci":         110,
+	"utf16_czech_ci":           111,
+	"utf16_danish_ci":          112,
+	"utf16_lithuanian_ci":      113,
+	"utf16_slovak_ci":          114,
+	"utf16_spanish2_ci":        115,
+	"utf16_roman_ci":           116,
+	"utf16_persian_ci":         117,
+	"utf16_esperanto_ci":       118,
+	"utf16_hungarian_ci":       119,
+	"utf16_sinhala_ci":         120,
+	"utf16_german2_ci":         121,
+	"utf16_croatian_ci":        122,
+	"utf16_unicode_520_ci":     123,
+	"utf16_vietnamese_ci":      124,
+	"ucs2_unicode_ci":          128,
+	"ucs2_icelandic_ci":        129,
+	"ucs2_latvian_ci":          130,
+	"ucs2_romanian_ci":         131,
+	"ucs2_slovenian_ci":        132,
+	"ucs2_polish_ci":           133,
+	"ucs2_estonian_ci":         134,
+	"ucs2_spanish_ci":          135,
+	"ucs2_swedish_ci":          136,
+	"ucs2_turkish_ci":          137,
+	"ucs2_czech_ci":            138,
+	"ucs2_danish_ci":           139,
+	"ucs2_lithuanian_ci":       140,
+	"ucs2_slovak_ci":           141,
+	"ucs2_spanish2_ci":         142,
+	"ucs2_roman_ci":            143,
+	"ucs2_persian_ci":          144,
+	"ucs2_esperanto_ci":        145,
+	"ucs2_hungarian_ci":        146,
+	"ucs2_sinhala_ci":          147,
+	"ucs2_german2_ci":          148,
+	"ucs2_croatian_ci":         149,
+	"ucs2_unicode_520_ci":      150,
+	"ucs2_vietnamese_ci":       151,
+	"ucs2_general_mysql500_ci": 159,
+	"utf32_unicode_ci":         160,
+	"utf32_icelandic_ci":       161,
+	"utf32_latvian_ci":         162,
+	"utf32_romanian_ci":        163,
+	"utf32_slovenian_ci":       164,
+	"utf32_polish_ci":          165,
+	"utf32_estonian_ci":        166,
+	"utf32_spanish_ci":         167,
+	"utf32_swedish_ci":         168,
+	"utf32_turkish_ci":         169,
+	"utf32_czech_ci":           170,
+	"utf32_danish_ci":          171,
+	"utf32_lithuanian_ci":      172,
+	"utf32_slovak_ci":          173,
+	"utf32_spanish2_ci":        174,
+	"utf32_roman_ci":           175,
+	"utf32_persian_ci":         176,
+	"utf32_esperanto_ci":       177,
+	"utf32_hungarian_ci":       178,
+	"utf32_sinhala_ci":         179,
+	"utf32_german2_ci":         180,
+	"utf32_croatian_ci":        181,
+	"utf32_unicode_520_ci":     182,
+	"utf32_vietnamese_ci":      183,
+	"utf8_unicode_ci":          192,
+	"utf8_icelandic_ci":        193,
+	"utf8_latvian_ci":          194,
+	"utf8_romanian_ci":         195,
+	"utf8_slovenian_ci":        196,
+	"utf8_polish_ci":           197,
+	"utf8_estonian_ci":         198,
+	"utf8_spanish_ci":          199,
+	"utf8_swedish_ci":          200,
+	"utf8_turkish_ci":          201,
+	"utf8_czech_ci":            202,
+	"utf8_danish_ci":           203,
+	"utf8_lithuanian_ci":       204,
+	"utf8_slovak_ci":           205,
+	"utf8_spanish2_ci":         206,
+	"utf8_roman_ci":            207,
+	"utf8_persian_ci":          208,
+	"utf8_esperanto_ci":        209,
+	"utf8_hungarian_ci":        210,
+	"utf8_sinhala_ci":          211,
+	"utf8_german2_ci":          212,
+	"utf8_croatian_ci":         213,
+	"utf8_unicode_520_ci":      214,
+	"utf8_vietnamese_ci":       215,
+	"utf8_general_mysql500_ci": 223,
+	"utf8mb4_unicode_ci":       224,
+	"utf8mb4_icelandic_ci":     225,
+	"utf8mb4_latvian_ci":       226,
+	"utf8mb4_romanian_ci":      227,
+	"utf8mb4_slovenian_ci":     228,
+	"utf8mb4_polish_ci":        229,
+	"utf8mb4_estonian_ci":      230,
+	"utf8mb4_spanish_ci":       231,
+	"utf8mb4_swedish_ci":       232,
+	"utf8mb4_turkish_ci":       233,
+	"utf8mb4_czech_ci":         234,
+	"utf8mb4_danish_ci":        235,
+	"utf8mb4_lithuanian_ci":    236,
+	"utf8mb4_slovak_ci":        237,
+	"utf8mb4_spanish2_ci":      238,
+	"utf8mb4_roman_ci":         239,
+	"utf8mb4_persian_ci":       240,
+	"utf8mb4_esperanto_ci":     241,
+	"utf8mb4_hungarian_ci":     242,
+	"utf8mb4_sinhala_ci":       243,
+	"utf8mb4_german2_ci":       244,
+	"utf8mb4_croatian_ci":      245,
+	"utf8mb4_unicode_520_ci":   246,
+	"utf8mb4_vietnamese_ci":    247,
+}
+
+// A blacklist of collations which is unsafe to interpolate parameters.
+// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
+var unsafeCollations = map[string]bool{
+	"big5_chinese_ci":   true,
+	"sjis_japanese_ci":  true,
+	"gbk_chinese_ci":    true,
+	"big5_bin":          true,
+	"gb2312_bin":        true,
+	"gbk_bin":           true,
+	"sjis_bin":          true,
+	"cp932_japanese_ci": true,
+	"cp932_bin":         true,
+}

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

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

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

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

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

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

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


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

@@ -0,0 +1,513 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2016 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+import (
+	"bytes"
+	"crypto/tls"
+	"errors"
+	"fmt"
+	"net"
+	"net/url"
+	"strings"
+	"time"
+)
+
+var (
+	errInvalidDSNUnescaped       = errors.New("invalid DSN: did you forget to escape a param value?")
+	errInvalidDSNAddr            = errors.New("invalid DSN: network address not terminated (missing closing brace)")
+	errInvalidDSNNoSlash         = errors.New("invalid DSN: missing the slash separating the database name")
+	errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations")
+)
+
+// Config is a configuration parsed from a DSN string
+type Config struct {
+	User         string            // Username
+	Passwd       string            // Password (requires User)
+	Net          string            // Network type
+	Addr         string            // Network address (requires Net)
+	DBName       string            // Database name
+	Params       map[string]string // Connection parameters
+	Collation    string            // Connection collation
+	Loc          *time.Location    // Location for time.Time values
+	TLSConfig    string            // TLS configuration name
+	tls          *tls.Config       // TLS configuration
+	Timeout      time.Duration     // Dial timeout
+	ReadTimeout  time.Duration     // I/O read timeout
+	WriteTimeout time.Duration     // I/O write timeout
+
+	AllowAllFiles           bool // Allow all files to be used with LOAD DATA LOCAL INFILE
+	AllowCleartextPasswords bool // Allows the cleartext client side plugin
+	AllowOldPasswords       bool // Allows the old insecure password method
+	ClientFoundRows         bool // Return number of matching rows instead of rows changed
+	ColumnsWithAlias        bool // Prepend table alias to column names
+	InterpolateParams       bool // Interpolate placeholders into query string
+	MultiStatements         bool // Allow multiple statements in one query
+	ParseTime               bool // Parse time values to time.Time
+	Strict                  bool // Return warnings as errors
+}
+
+// FormatDSN formats the given Config into a DSN string which can be passed to
+// the driver.
+func (cfg *Config) FormatDSN() string {
+	var buf bytes.Buffer
+
+	// [username[:password]@]
+	if len(cfg.User) > 0 {
+		buf.WriteString(cfg.User)
+		if len(cfg.Passwd) > 0 {
+			buf.WriteByte(':')
+			buf.WriteString(cfg.Passwd)
+		}
+		buf.WriteByte('@')
+	}
+
+	// [protocol[(address)]]
+	if len(cfg.Net) > 0 {
+		buf.WriteString(cfg.Net)
+		if len(cfg.Addr) > 0 {
+			buf.WriteByte('(')
+			buf.WriteString(cfg.Addr)
+			buf.WriteByte(')')
+		}
+	}
+
+	// /dbname
+	buf.WriteByte('/')
+	buf.WriteString(cfg.DBName)
+
+	// [?param1=value1&...&paramN=valueN]
+	hasParam := false
+
+	if cfg.AllowAllFiles {
+		hasParam = true
+		buf.WriteString("?allowAllFiles=true")
+	}
+
+	if cfg.AllowCleartextPasswords {
+		if hasParam {
+			buf.WriteString("&allowCleartextPasswords=true")
+		} else {
+			hasParam = true
+			buf.WriteString("?allowCleartextPasswords=true")
+		}
+	}
+
+	if cfg.AllowOldPasswords {
+		if hasParam {
+			buf.WriteString("&allowOldPasswords=true")
+		} else {
+			hasParam = true
+			buf.WriteString("?allowOldPasswords=true")
+		}
+	}
+
+	if cfg.ClientFoundRows {
+		if hasParam {
+			buf.WriteString("&clientFoundRows=true")
+		} else {
+			hasParam = true
+			buf.WriteString("?clientFoundRows=true")
+		}
+	}
+
+	if col := cfg.Collation; col != defaultCollation && len(col) > 0 {
+		if hasParam {
+			buf.WriteString("&collation=")
+		} else {
+			hasParam = true
+			buf.WriteString("?collation=")
+		}
+		buf.WriteString(col)
+	}
+
+	if cfg.ColumnsWithAlias {
+		if hasParam {
+			buf.WriteString("&columnsWithAlias=true")
+		} else {
+			hasParam = true
+			buf.WriteString("?columnsWithAlias=true")
+		}
+	}
+
+	if cfg.InterpolateParams {
+		if hasParam {
+			buf.WriteString("&interpolateParams=true")
+		} else {
+			hasParam = true
+			buf.WriteString("?interpolateParams=true")
+		}
+	}
+
+	if cfg.Loc != time.UTC && cfg.Loc != nil {
+		if hasParam {
+			buf.WriteString("&loc=")
+		} else {
+			hasParam = true
+			buf.WriteString("?loc=")
+		}
+		buf.WriteString(url.QueryEscape(cfg.Loc.String()))
+	}
+
+	if cfg.MultiStatements {
+		if hasParam {
+			buf.WriteString("&multiStatements=true")
+		} else {
+			hasParam = true
+			buf.WriteString("?multiStatements=true")
+		}
+	}
+
+	if cfg.ParseTime {
+		if hasParam {
+			buf.WriteString("&parseTime=true")
+		} else {
+			hasParam = true
+			buf.WriteString("?parseTime=true")
+		}
+	}
+
+	if cfg.ReadTimeout > 0 {
+		if hasParam {
+			buf.WriteString("&readTimeout=")
+		} else {
+			hasParam = true
+			buf.WriteString("?readTimeout=")
+		}
+		buf.WriteString(cfg.ReadTimeout.String())
+	}
+
+	if cfg.Strict {
+		if hasParam {
+			buf.WriteString("&strict=true")
+		} else {
+			hasParam = true
+			buf.WriteString("?strict=true")
+		}
+	}
+
+	if cfg.Timeout > 0 {
+		if hasParam {
+			buf.WriteString("&timeout=")
+		} else {
+			hasParam = true
+			buf.WriteString("?timeout=")
+		}
+		buf.WriteString(cfg.Timeout.String())
+	}
+
+	if len(cfg.TLSConfig) > 0 {
+		if hasParam {
+			buf.WriteString("&tls=")
+		} else {
+			hasParam = true
+			buf.WriteString("?tls=")
+		}
+		buf.WriteString(url.QueryEscape(cfg.TLSConfig))
+	}
+
+	if cfg.WriteTimeout > 0 {
+		if hasParam {
+			buf.WriteString("&writeTimeout=")
+		} else {
+			hasParam = true
+			buf.WriteString("?writeTimeout=")
+		}
+		buf.WriteString(cfg.WriteTimeout.String())
+	}
+
+	// other params
+	if cfg.Params != nil {
+		for param, value := range cfg.Params {
+			if hasParam {
+				buf.WriteByte('&')
+			} else {
+				hasParam = true
+				buf.WriteByte('?')
+			}
+
+			buf.WriteString(param)
+			buf.WriteByte('=')
+			buf.WriteString(url.QueryEscape(value))
+		}
+	}
+
+	return buf.String()
+}
+
+// ParseDSN parses the DSN string to a Config
+func ParseDSN(dsn string) (cfg *Config, err error) {
+	// New config with some default values
+	cfg = &Config{
+		Loc:       time.UTC,
+		Collation: defaultCollation,
+	}
+
+	// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
+	// Find the last '/' (since the password or the net addr might contain a '/')
+	foundSlash := false
+	for i := len(dsn) - 1; i >= 0; i-- {
+		if dsn[i] == '/' {
+			foundSlash = true
+			var j, k int
+
+			// left part is empty if i <= 0
+			if i > 0 {
+				// [username[:password]@][protocol[(address)]]
+				// Find the last '@' in dsn[:i]
+				for j = i; j >= 0; j-- {
+					if dsn[j] == '@' {
+						// username[:password]
+						// Find the first ':' in dsn[:j]
+						for k = 0; k < j; k++ {
+							if dsn[k] == ':' {
+								cfg.Passwd = dsn[k+1 : j]
+								break
+							}
+						}
+						cfg.User = dsn[:k]
+
+						break
+					}
+				}
+
+				// [protocol[(address)]]
+				// Find the first '(' in dsn[j+1:i]
+				for k = j + 1; k < i; k++ {
+					if dsn[k] == '(' {
+						// dsn[i-1] must be == ')' if an address is specified
+						if dsn[i-1] != ')' {
+							if strings.ContainsRune(dsn[k+1:i], ')') {
+								return nil, errInvalidDSNUnescaped
+							}
+							return nil, errInvalidDSNAddr
+						}
+						cfg.Addr = dsn[k+1 : i-1]
+						break
+					}
+				}
+				cfg.Net = dsn[j+1 : k]
+			}
+
+			// dbname[?param1=value1&...&paramN=valueN]
+			// Find the first '?' in dsn[i+1:]
+			for j = i + 1; j < len(dsn); j++ {
+				if dsn[j] == '?' {
+					if err = parseDSNParams(cfg, dsn[j+1:]); err != nil {
+						return
+					}
+					break
+				}
+			}
+			cfg.DBName = dsn[i+1 : j]
+
+			break
+		}
+	}
+
+	if !foundSlash && len(dsn) > 0 {
+		return nil, errInvalidDSNNoSlash
+	}
+
+	if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
+		return nil, errInvalidDSNUnsafeCollation
+	}
+
+	// Set default network if empty
+	if cfg.Net == "" {
+		cfg.Net = "tcp"
+	}
+
+	// Set default address if empty
+	if cfg.Addr == "" {
+		switch cfg.Net {
+		case "tcp":
+			cfg.Addr = "127.0.0.1:3306"
+		case "unix":
+			cfg.Addr = "/tmp/mysql.sock"
+		default:
+			return nil, errors.New("default addr for network '" + cfg.Net + "' unknown")
+		}
+
+	}
+
+	return
+}
+
+// parseDSNParams parses the DSN "query string"
+// Values must be url.QueryEscape'ed
+func parseDSNParams(cfg *Config, params string) (err error) {
+	for _, v := range strings.Split(params, "&") {
+		param := strings.SplitN(v, "=", 2)
+		if len(param) != 2 {
+			continue
+		}
+
+		// cfg params
+		switch value := param[1]; param[0] {
+
+		// Disable INFILE whitelist / enable all files
+		case "allowAllFiles":
+			var isBool bool
+			cfg.AllowAllFiles, isBool = readBool(value)
+			if !isBool {
+				return errors.New("invalid bool value: " + value)
+			}
+
+		// Use cleartext authentication mode (MySQL 5.5.10+)
+		case "allowCleartextPasswords":
+			var isBool bool
+			cfg.AllowCleartextPasswords, isBool = readBool(value)
+			if !isBool {
+				return errors.New("invalid bool value: " + value)
+			}
+
+		// Use old authentication mode (pre MySQL 4.1)
+		case "allowOldPasswords":
+			var isBool bool
+			cfg.AllowOldPasswords, isBool = readBool(value)
+			if !isBool {
+				return errors.New("invalid bool value: " + value)
+			}
+
+		// Switch "rowsAffected" mode
+		case "clientFoundRows":
+			var isBool bool
+			cfg.ClientFoundRows, isBool = readBool(value)
+			if !isBool {
+				return errors.New("invalid bool value: " + value)
+			}
+
+		// Collation
+		case "collation":
+			cfg.Collation = value
+			break
+
+		case "columnsWithAlias":
+			var isBool bool
+			cfg.ColumnsWithAlias, isBool = readBool(value)
+			if !isBool {
+				return errors.New("invalid bool value: " + value)
+			}
+
+		// Compression
+		case "compress":
+			return errors.New("compression not implemented yet")
+
+		// Enable client side placeholder substitution
+		case "interpolateParams":
+			var isBool bool
+			cfg.InterpolateParams, isBool = readBool(value)
+			if !isBool {
+				return errors.New("invalid bool value: " + value)
+			}
+
+		// Time Location
+		case "loc":
+			if value, err = url.QueryUnescape(value); err != nil {
+				return
+			}
+			cfg.Loc, err = time.LoadLocation(value)
+			if err != nil {
+				return
+			}
+
+		// multiple statements in one query
+		case "multiStatements":
+			var isBool bool
+			cfg.MultiStatements, isBool = readBool(value)
+			if !isBool {
+				return errors.New("invalid bool value: " + value)
+			}
+
+		// time.Time parsing
+		case "parseTime":
+			var isBool bool
+			cfg.ParseTime, isBool = readBool(value)
+			if !isBool {
+				return errors.New("invalid bool value: " + value)
+			}
+
+		// I/O read Timeout
+		case "readTimeout":
+			cfg.ReadTimeout, err = time.ParseDuration(value)
+			if err != nil {
+				return
+			}
+
+		// Strict mode
+		case "strict":
+			var isBool bool
+			cfg.Strict, isBool = readBool(value)
+			if !isBool {
+				return errors.New("invalid bool value: " + value)
+			}
+
+		// Dial Timeout
+		case "timeout":
+			cfg.Timeout, err = time.ParseDuration(value)
+			if err != nil {
+				return
+			}
+
+		// TLS-Encryption
+		case "tls":
+			boolValue, isBool := readBool(value)
+			if isBool {
+				if boolValue {
+					cfg.TLSConfig = "true"
+					cfg.tls = &tls.Config{}
+				} else {
+					cfg.TLSConfig = "false"
+				}
+			} else if vl := strings.ToLower(value); vl == "skip-verify" {
+				cfg.TLSConfig = vl
+				cfg.tls = &tls.Config{InsecureSkipVerify: true}
+			} else {
+				name, err := url.QueryUnescape(value)
+				if err != nil {
+					return fmt.Errorf("invalid value for TLS config name: %v", err)
+				}
+
+				if tlsConfig, ok := tlsConfigRegister[name]; ok {
+					if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
+						host, _, err := net.SplitHostPort(cfg.Addr)
+						if err == nil {
+							tlsConfig.ServerName = host
+						}
+					}
+
+					cfg.TLSConfig = name
+					cfg.tls = tlsConfig
+				} else {
+					return errors.New("invalid value / unknown config name: " + name)
+				}
+			}
+
+		// I/O write Timeout
+		case "writeTimeout":
+			cfg.WriteTimeout, err = time.ParseDuration(value)
+			if err != nil {
+				return
+			}
+
+		default:
+			// lazy init
+			if cfg.Params == nil {
+				cfg.Params = make(map[string]string)
+			}
+
+			if cfg.Params[param[0]], err = url.QueryUnescape(value); err != nil {
+				return
+			}
+		}
+	}
+
+	return
+}

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


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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

+ 3 - 0
src/github.com/tangs-drm/go-tool/README.md

@@ -0,0 +1,3 @@
+### 关于golang的开发工具包
+
+提供golang的开发工具,包含日志,数据库,定时器,并发测试工具,map操作等等

+ 46 - 0
src/github.com/tangs-drm/go-tool/config/README.md

@@ -0,0 +1,46 @@
+### 读取配置
+
+* 一个读取自定义配置文件的工具.目前支持.conf格式
+
+
+### 不定时更新
+
+ 1. 目前只支持通过文件路径读取配置文件,之后将支持通过url读取配置文件
+
+ 2. 之后将支持配置文件中引入配置文件.
+
+ 3. 之后将支持配置项重写,即一个配置文件引入多个配置文件,支持配置项优先级
+
+
+---
+有配置文件config.conf,内容如下
+```
+# this is a config test file
+
+int=1
+int32=2
+int64=3
+float32=1.1
+float64=2.2
+string=halo
+
+judge=judge #判断这个
+
+ # 过滤这个
+
+## 过滤掉这个
+
+
+ val = hey###### val = hey
+```
+
+读取配置文件config.conf
+```
+
+    confPath := "config.conf"
+    conf := NewConfig()
+    err = conf.Config(confPath)
+    if err != nil {
+        return
+    }
+```

+ 17 - 0
src/github.com/tangs-drm/go-tool/config/config.conf

@@ -0,0 +1,17 @@
+# this is a config test file
+
+int=1
+int32=2
+int64=3
+float32=1.1
+float64=2.2
+string=halo
+
+judge=judge #判断这个
+
+ # 过滤这个
+
+## 过滤掉这个
+
+
+ val = hey###### val = hey

+ 102 - 0
src/github.com/tangs-drm/go-tool/config/config.go

@@ -0,0 +1,102 @@
+package config
+
+import (
+	"github.com/tangs-drm/go-tool/util"
+	"strings"
+	"os"
+	"bufio"
+	"io"
+	"fmt"
+)
+
+type Config struct {
+	util.Map
+}
+
+
+// 配置文件的注释字符.如果遇到#,则忽略此行
+const CONFIG_COMMENT = "#"
+// 配置文件的配置项和值的连接符,如url=127.0.0.1
+const CONFIG_EQUAL = "="
+// 配置文件支持的文件后缀
+const (
+	CONFIG_CONF = ".conf"
+)
+
+func NewConfig() *Config {
+	return &Config{
+		Map: util.Map{},
+	}
+}
+
+func checkExt(filename string) bool {
+	return strings.HasSuffix(filename, CONFIG_CONF)
+}
+
+func (cf *Config) Config(filename string) error {
+	if !checkExt(filename) {
+		return util.Error("file type must be %v, invalid file name(%v)", CONFIG_CONF, filename)
+	}
+	return cf.ParseFile(filename)
+}
+
+func (cf *Config) ParseFile(filename string) error {
+	f, err := os.Open(filename)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	rd := bufio.NewReader(f)
+	var eof bool
+	for {
+		if eof {
+			break
+		}
+		line, err := rd.ReadString('\n')
+		if err != nil {
+			if err.Error() != io.EOF.Error() {
+				return err
+			}
+			eof = true
+		}
+		line = strings.TrimSpace(line)
+
+		// 如果是空行,跳过
+		if len(line) < 1 {
+			continue
+		}
+
+		// 判断是否开头有注释符号
+		if strings.HasPrefix(line, CONFIG_COMMENT) {
+			continue
+		}
+
+		// 处理掉一行中的注释部分
+		index := strings.Index(line, CONFIG_COMMENT)
+		if index > -1 {
+			line = line[0:index]
+		}
+		line = strings.TrimSpace(line)
+
+		vals := strings.SplitN(line, CONFIG_EQUAL, 2)
+		if len(vals) < 1 {
+			continue
+		}
+
+		config_key := strings.TrimSpace(vals[0])
+		config_val := ""
+		if len(vals) == 2 {
+			config_val = strings.TrimSpace(vals[1])
+		}
+
+		cf.Map[config_key] = config_val
+	}
+	return nil
+}
+
+func (cf *Config) Print() {
+	for k, v := range cf.Map {
+		fmt.Println(k, "---->", v)
+	}
+}

+ 96 - 0
src/github.com/tangs-drm/go-tool/config/config_test.go

@@ -0,0 +1,96 @@
+package config
+
+import (
+	"testing"
+	"os"
+	"fmt"
+)
+
+func TestT(t *testing.T) {
+
+	path, err := os.Getwd()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	confPath := path + "/invalid.ts"
+	conf := NewConfig()
+	err = conf.Config(confPath)
+	if err == nil {
+		t.Error(err)
+		return
+	}
+
+	confPath = path + "/errconfigType.type"
+	err = conf.Config(confPath)
+	if err == nil {
+		t.Error(err)
+		return
+	}
+
+	confPath = path + "/testconfig.conf"
+	err = conf.Config(confPath)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	conf.Print()
+
+	if len(conf.Map) != 4 {
+		t.Error(len(conf.Map))
+		return
+	}
+
+	fmt.Println("----------------------")
+	confPath = path + "/config.conf"
+	conf = NewConfig()
+	err = conf.Config(confPath)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	conf.Print()
+
+	if len(conf.Map) != 8 {
+		t.Error(len(conf.Map))
+		return
+	}
+
+	if "halo" != conf.String("string") {
+		t.Error(conf.String("key"))
+		return
+	}
+	if 1 != conf.Int("int") {
+		t.Error(conf.Int("int"))
+		return
+	}
+	if 2 != conf.Int32("int32") {
+		t.Error(conf.Int32("int32"))
+		return
+	}
+	if 3 != conf.Int64("int64") {
+		t.Error(conf.Int64("int64"))
+		return
+	}
+	if 1.1 != conf.Float32("float32") {
+		t.Error(conf.Float32("float32"))
+		return
+	}
+	fmt.Println("----------")
+	if 2.2 != conf.Float64("float64") {
+		t.Error(conf.Float64("float64"))
+		return
+	}
+	if "judge" != conf.String("judge") {
+		t.Error(conf.String("judge"))
+		return
+	}
+	if "hey" != conf.String("val") {
+		t.Error(conf.String("val"))
+		return
+	}
+	fmt.Println("测试配置文件结束")
+}

+ 6 - 0
src/github.com/tangs-drm/go-tool/config/errconfigType.type

@@ -0,0 +1,6 @@
+#
+
+#
+
+
+halo=halo

+ 12 - 0
src/github.com/tangs-drm/go-tool/config/testconfig.conf

@@ -0,0 +1,12 @@
+# halo
+
+key=val
+
+testKey = testVal
+
+ #
+
+ testKey2 = testVal
+
+int=1
+

+ 3 - 0
src/github.com/tangs-drm/go-tool/dbm/README.md

@@ -0,0 +1,3 @@
+### 封装的数据库小工具
+
+支持的数据库有mysql, mongodb

+ 110 - 0
src/github.com/tangs-drm/go-tool/dbm/mysql.go

@@ -0,0 +1,110 @@
+package dbm
+
+import (
+	"database/sql"
+	"github.com/tangs-drm/go-tool/util"
+	"strings"
+	_ "github.com/go-sql-driver/mysql"
+)
+
+type DB struct  {
+	*sql.DB
+}
+
+var Db *DB
+
+// NewDB根据name新建一个数据库的对象
+func NewDB(name string, url string) (*DB, error) {
+	if len(name) < 1 {
+		return nil, util.Error("name is invalid: %v", name)
+	}
+	db, err := sql.Open("mysql", url)
+	if err != nil {
+		return nil, err
+	}
+	err = db.Ping()
+	if err != nil {
+		return nil, err
+	}
+	var db_ = &DB{DB: db}
+	DefaultDBManger.Register(name, db_)
+	return db_, nil
+}
+
+// Default使用默认的数据库对象
+func Default(url string) (*DB, error) {
+	var err error
+	Db, err =  NewDB("default", url)
+	if err != nil {
+		return nil, err
+	}
+	return Db, nil
+}
+
+var DefaultDBManger = NewDbManager()
+
+type DbManager struct {
+	DbMap map[string]*DB
+}
+
+// NewDbManager 新建一个数据库对象管理者
+func NewDbManager() *DbManager {
+	return &DbManager{
+		DbMap:map[string]*DB{},
+	}
+}
+
+// Register 通过name注册数据库对象
+func (dm *DbManager) Register(name string, db *DB) {
+	dm.DbMap[name] = db
+}
+
+// Db 根据name返回对应的数据库对象,name为空则返回默认的数据库对象
+func (dm *DbManager) D(name... string) *DB {
+	if len(name) < 1 {
+		return dm.DbMap["default"]
+	}
+	nm := name[0]
+	return dm.DbMap[nm]
+}
+
+func D(name... string) *DB {
+	if len(name) < 1 {
+		return DefaultDBManger.DbMap["default"]
+	}
+	nm := name[0]
+	return DefaultDBManger.DbMap[nm]
+}
+
+// NewDBWithManager 新建数据库对象,并注册到指定的数据库管理者里
+func NewDBWithManager(manager *DbManager, name string, url string) (*DB, error) {
+	if len(name) < 1 {
+		return nil, util.Error("name is invalid: %v", name)
+	}
+	db, err := sql.Open("mysql", url)
+	if err != nil {
+		return nil, err
+	}
+	var db_ = &DB{DB: db}
+	manager.Register(name, db_)
+	return db_, nil
+}
+
+// ExecScripts 处理了多条sql命令的执行
+// 参数1: 需要执行的sql语句
+// 参数2: 如果发生错误是否继续执行下去, false: 不继续执行, true: 继续执行
+func (db *DB) ExecScripts(scripts string, Continue bool) error {
+	commands := strings.Split(scripts, ";")
+	if len(commands) < 1 {
+		return util.Error("scripts get command empty")
+	}
+
+	for _, command := range commands {
+		command = strings.TrimSpace(command)
+		_, err := db.Exec(command)
+		if err != nil && !Continue {
+			return err
+		}
+	}
+	return nil
+}

+ 177 - 0
src/github.com/tangs-drm/go-tool/dbm/mysql_test.go

@@ -0,0 +1,177 @@
+package dbm
+
+import (
+	"testing"
+	"github.com/tangs-drm/go-tool/log"
+	"github.com/tangs-drm/go-tool/util"
+	"fmt"
+)
+
+func TestMysql(t *testing.T) {
+	log.Debug("TestMysql --- START")
+	var err error
+	var db *DB
+	db, err = Default("testUser:123@tcp(localhost:3306)/testdb?charset=utf8")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	err = db.Ping()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	res, err := db.Exec("drop table if exists test;")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	fmt.Println(res)
+	log.Debug("res1 -- %v", util.S2Json(res))
+
+	res, err = db.Exec("create table test(id int, val varchar(16));")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	fmt.Println(res)
+	log.Debug("res2 -- %v", util.S2Json(res))
+
+	// 手动注册数据库
+	db, err = NewDB("", "")
+	if err == nil {
+		t.Error(err)
+		return
+	}
+
+	db = DefaultDBManger.D()
+	if db == nil {
+		t.Error(err)
+		return
+	}
+
+	db = DefaultDBManger.D("default")
+	if db == nil {
+		t.Error(nil)
+		return
+	}
+
+	db, err = NewDBWithManager(DefaultDBManger, "", "testUser:123@tcp(localhost:3306)/testdb?charset=utf8")
+	if err == nil {
+		t.Error(err)
+		return
+	}
+
+	db, err = NewDBWithManager(DefaultDBManger, "halo", "testUser:123@tcp(localhost:3306)/testdb?charset=utf8")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	err = db.Ping()
+	if err != nil {
+
+		t.Error(err)
+		return
+	}
+
+	// 测试执行多条sql语句
+	var sqlString string = `/*==============================================================*/
+/* DBMS name:      MySQL 5.0                                    */
+/* Created on:     2017/8/6 14:47:07                            */
+/*==============================================================*/
+
+
+DROP INDEX LASTTIME ON SESSION;
+
+DROP INDEX TIME ON SESSION;
+
+DROP INDEX UID ON SESSION;
+
+DROP INDEX ID ON SESSION;
+
+DROP TABLE IF EXISTS SESSION;
+
+/*==============================================================*/
+/* Table: SESSION                                               */
+/*==============================================================*/
+CREATE TABLE SESSION
+(
+   ID                   VARCHAR(64) NOT NULL,
+   UID                  VARCHAR(64),
+   TIME                 INT,
+   LASTTIME             INT,
+   PRIMARY KEY (ID)
+);
+
+/*==============================================================*/
+/* Index: ID                                                    */
+/*==============================================================*/
+CREATE INDEX ID ON SESSION
+(
+   ID
+);
+
+/*==============================================================*/
+/* Index: UID                                                   */
+/*==============================================================*/
+CREATE INDEX UID ON SESSION
+(
+   UID
+);
+
+/*==============================================================*/
+/* Index: TIME                                                  */
+/*==============================================================*/
+CREATE INDEX TIME ON SESSION
+(
+   TIME
+);
+
+/*==============================================================*/
+/* Index: LASTTIME                                              */
+/*==============================================================*/
+CREATE INDEX LASTTIME ON SESSION
+(
+   LASTTIME
+);
+
+`
+	db = D()
+	if db == nil {
+		t.Error(err)
+		return
+	}
+
+	err = db.ExecScripts(sqlString, false)
+	if err == nil {
+		t.Error(err)
+		return
+	}
+
+	err = db.ExecScripts(sqlString, true)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	// check db
+	rows, err := db.Query("desc SESSION;")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	defer rows.Close()
+
+	var count int = 0
+	for rows.Next() {
+		count ++
+	}
+	if count != 4 {
+		t.Error(count)
+		return
+	}
+
+	log.Debug("TestMysql --- END")
+}

+ 49 - 0
src/github.com/tangs-drm/go-tool/http/README.md

@@ -0,0 +1,49 @@
+### http 封装
+
+#### 说明
+* 该package封装了一个简单的http请求包
+
+* 该package保留了golang原本的http的使用风格,并在原本的代码风格上进行扩展
+
+#### 不定时更新
+
+1. 增加http请求路由的正则表达式
+
+2. 增加http请求的参数方便获取,类似于Map操作一样,可以使用形如Map.String()
+
+#### 用法,用例如下
+```golang
+package main
+
+import (
+	thttp "github.com/tangs-drm/go-tool/http"
+	"net/http"
+)
+
+func HaloHandleFunc(w http.ResponseWriter, r *http.Request) {
+	w.Write([]byte("halo world"))
+}
+
+func main() {
+	mux := thttp.NewServerMux()
+
+	// 普通的http路由
+	mux.HandleFunc("/halo", HaloHandleFunc)
+
+	// 添加过滤器
+	var filter thttp.FilterFunc = func(w http.ResponseWriter, r *http.Request) int {
+		path := r.URL.Path
+		if path != "/usr/halo" {
+			w.Write([]byte("wrong path"))
+			return thttp.REQUEST_RETURN
+		}
+		return thttp.REQUEST_CONTINUE
+	}
+	mux.FilterFunc("/usr/halo", filter)
+	mux.HandleFunc("/usr/halo", HaloHandleFunc)
+
+	http.ListenAndServe(":8900", mux)
+
+}
+
+```

+ 99 - 0
src/github.com/tangs-drm/go-tool/http/http.go

@@ -0,0 +1,99 @@
+package http
+
+import (
+	"net/http"
+	"strings"
+)
+
+const (
+	// 过滤器错误码
+	FILTER_ERROR = 1000
+)
+
+// http请求 继续往下执行标记码
+const (
+	REQUEST_CONTINUE int = 0 // 请求操作继续执行
+	REQUEST_RETURN   int = 1 // 请求操作停机执行
+)
+
+type Mux struct {
+	Handle map[string]http.Handler // 请求路由
+	Filter map[string]FilterFunc   // 过滤器
+}
+
+// ServeHTTP 实现了http的ServeHTTP接口,以实现http的封装
+func (mux *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	path := r.URL.Path
+	// 根据请求的路由获取执行的handler
+	handler := mux.deliverHandler(path)
+	if handler == nil {
+		http.NotFound(w, r)
+		return
+	}
+	// 获取过滤器,如果有则执行
+	filter := mux.deliverFilter(path)
+	var filter_code int
+	if filter != nil {
+		filter_code = filter(w, r)
+		if REQUEST_RETURN == filter_code {
+			return
+		}
+	}
+	// 执行handler
+	handler.ServeHTTP(w, r)
+}
+
+func NewServerMux() *Mux {
+	return &Mux{
+		Handle: map[string]http.Handler{},
+		Filter: map[string]FilterFunc{},
+	}
+}
+
+// DefaultHandle 定义默认的server mux 对象
+var DefaultHandle = NewServerMux()
+
+// HandleFunc 设置路由和对应的处理handler
+func HandleFunc(pre string, handler func(http.ResponseWriter, *http.Request)) {
+	DefaultHandle.Handle[pre] = http.HandlerFunc(handler)
+}
+
+// HandleFunc 设置路由和对应的处理handler
+func (mux *Mux) HandleFunc(pre string, handler func(http.ResponseWriter, *http.Request)) {
+	mux.Handle[pre] = http.HandlerFunc(handler)
+}
+
+// FilterFunc 定义过滤器
+// 参数一:过滤时所需要的参数
+// 参数二:用于扩展所需的参数,或者或者返回值
+type FilterFunc func(http.ResponseWriter, *http.Request) int
+
+// Filter设置过滤器
+func Filter(prefix string, filter FilterFunc) {
+	DefaultHandle.Filter[prefix] = filter
+}
+
+// FilterFunc 设置过滤器
+func (mux *Mux) FilterFunc(prefix string, filter FilterFunc) {
+	mux.Filter[prefix] = filter
+}
+
+// deliverHandler 根据请求进来的路由找到对应的handler, 找不到则返回nil
+func (mux *Mux) deliverHandler(path string) http.Handler {
+	for key, handler := range mux.Handle {
+		if path == key {
+			return handler
+		}
+	}
+	return nil
+}
+
+// deliverFilter 根据请求进来的路由找到对应的过滤器,找不到则返回nil
+func (mux *Mux) deliverFilter(path string) FilterFunc {
+	for key, filter := range mux.Filter {
+		if strings.HasPrefix(path, key) {
+			return filter
+		}
+	}
+	return nil
+}

+ 168 - 0
src/github.com/tangs-drm/go-tool/http/http_test.go

@@ -0,0 +1,168 @@
+package http
+
+import (
+	"testing"
+	"net/http"
+	"github.com/tangs-drm/go-tool/util"
+	"fmt"
+)
+
+func HandleHalo(w http.ResponseWriter, r *http.Request) {
+	msg := util.Map{"code": 0, "msg": "halo"}
+	w.Write([]byte(util.S2Json(msg)))
+}
+
+func HandleString(w http.ResponseWriter, r *http.Request) {
+	var err error
+	_, err = w.Write([]byte("string"))
+	fmt.Println("HandleString come in", err)
+	return
+}
+
+// TestHttp 测试http请求是否正常
+func TestHttp(t *testing.T) {
+	fmt.Println("TestHttp测试 Start")
+	mux := NewServerMux()
+	go func() {
+
+		// 定义路由
+		mux.HandleFunc("/halo", HandleHalo)
+		mux.HandleFunc("/string", HandleString)
+		http.ListenAndServe(":8901", mux)
+	}()
+
+	var resMap util.Map
+	var resString string
+
+	var err error
+
+	//resString, err = util.HTTPGetString("%v", "http://127.0.0.1:8901/halo")
+	//if err != nil {
+	//	t.Error(err)
+	//	return
+	//}
+	//fmt.Println(resString)
+	//return
+
+	// 请求测试
+	resMap, err = util.HTTPGetMap("%v", "http://127.0.0.1:8901/halo")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if resMap.Int("code") != 0 {
+		t.Error(err)
+		return
+	}
+	if resMap.String("msg") != "halo" {
+		t.Error(err)
+		return
+	}
+
+	resString, err = util.HTTPGetString("%v", "http://127.0.0.1:8901/string")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if resString != "string" {
+		t.Error(resString)
+		return
+	}
+
+	// 添加过滤器
+	var argFilter FilterFunc = func(w http.ResponseWriter, r *http.Request) int {
+		var filter string
+		filter = r.FormValue("filter")
+		fmt.Printf("filter -- %v|\n", filter)
+		if filter != "halo" {
+			w.Write([]byte("invalid"))
+			return 1
+		}
+		return 0
+	}
+	mux.FilterFunc("/usr/api",  argFilter)
+
+	// 添加过滤器,访问其他路由
+	resString, err = util.HTTPGetString("%v", "http://127.0.0.1:8901/string")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if resString != "string" {
+		t.Error(resString)
+		return
+	}
+
+	// 添加路由,访问不受过滤
+	mux.HandleFunc("/usr/ap/halo", HandleString)
+	resString, err = util.HTTPGetString("%v", "http://127.0.0.1:8901/usr/ap/halo")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if resString != "string" {
+		t.Error(resString)
+		return
+	}
+
+	// 添加路由,受过滤器影响,返回invalid
+	mux.HandleFunc("/usr/api/halo", HandleString)
+	resString, err = util.HTTPGetString("%v", "http://127.0.0.1:8901/usr/api/halo")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if resString != "invalid" {
+		t.Error(resString)
+		return
+	}
+
+	//添加路由,通过过滤器
+	mux.HandleFunc("/usr/api/halo", HandleString)
+	resString, err = util.HTTPGetString("%v", "http://127.0.0.1:8901/usr/api/halo?filter=halo")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if resString != "string" {
+		t.Error(resString)
+		return
+	}
+
+	//添加路由,不通过过滤器
+	mux.HandleFunc("/usr/api/halo", HandleString)
+	resString, err = util.HTTPGetString("%v", "http://127.0.0.1:8901/usr/api/halo?filter=halo+++")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if resString != "invalid" {
+		t.Error(resString)
+		return
+	}
+
+	// 添加路由, 通过路由获取map
+	var pubFilter FilterFunc = func(w http.ResponseWriter, r *http.Request) int {
+		if r.URL.Path != "/pub/api/map" {
+			return REQUEST_RETURN
+		}
+		return REQUEST_CONTINUE
+	}
+	mux.FilterFunc("/pub/", pubFilter)
+	mux.HandleFunc("/pub/api/map", HandleHalo)
+	resMap, err = util.HTTPGetMap("%v", "http://127.0.0.1:8901/halo")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if resMap.Int("code") != 0 {
+		t.Error(resMap.Int("code"))
+		return
+	}
+	if resMap.String("msg") != "halo" {
+		t.Error(resMap.String("msg"))
+		return
+	}
+
+	fmt.Println("TestHttp测试 Start")
+}

+ 42 - 0
src/github.com/tangs-drm/go-tool/http/response.go

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

+ 2 - 0
src/github.com/tangs-drm/go-tool/log/.gitignore

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

+ 37 - 0
src/github.com/tangs-drm/go-tool/log/README.md

@@ -0,0 +1,37 @@
+# log
+
+创建一个简单的日志对象
+
+用法如下:
+
+```golang
+package main
+
+import (
+	"github.com/tangs/log"
+	"os"
+	"fmt"
+)
+
+func main() {
+	// 直接使用对象打印日志
+	log.Debug("halo")
+
+	// 手动设置打印日志到文件
+	f, err := os.Create("log.log")
+	if err != nil {
+		log.Error("%v", err)
+		return
+	}
+	log.Redirect(f)
+	log.Debug("%v", "halo, log!")
+
+	// 重定向日志到文件
+	err = log.RedirectFile("log/log.log")
+	if err != nil {
+		fmt.Println("log ", err.Error())
+		return
+	}
+	log.Debug("%v", "halo, RedirectFile")
+}
+```

+ 132 - 0
src/github.com/tangs-drm/go-tool/log/log.go

@@ -0,0 +1,132 @@
+/*
+	log 包,日志等级分为四个等级,DEBUG, INFO, WARNING, ERROR, 和通常的日志包套路一样,日志打印可以设置等级
+	低于设置等级的日志会被过滤.
+*/
+package log
+
+import (
+	"log"
+	"os"
+	"fmt"
+	"io"
+	"path/filepath"
+)
+
+type logLevel int
+
+type Logger struct {
+	Level logLevel
+	Log *log.Logger
+}
+
+const (
+	DEBUG logLevel = iota
+	INFO             = 2
+	WARNING          = 3
+	ERROR            = 4
+)
+
+// 包内私有对象
+var logger = NewLogger()
+
+// NewLogger返回一个新log对象
+func NewLogger() *Logger {
+	return &Logger{
+		Log: log.New(os.Stdout, "",  log.LstdFlags|log.Lshortfile),
+	}
+}
+
+// SetLevel设置日子的打印等级
+func (l *Logger) SetLevel(level int) {
+	l.Level = logLevel(level)
+}
+
+// Redirect日志重定向,可以指定到控制台,文件等
+func Redirect(writer io.Writer) {
+	logger.Log.SetOutput(writer)
+}
+
+// RedirectFile日志重定向到文件,可以创建多层文件路径,如 /var/log/server/log/log.log
+func RedirectFile(file string) error {
+	var err error
+	fp := filepath.Dir(file)
+	err = os.MkdirAll(fp, os.ModePerm)
+	if err != nil {
+		return err
+	}
+	f, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_APPEND|os.O_SYNC, 0755)
+	if err != nil {
+		return err
+	}
+	Redirect(f)
+	return nil
+}
+
+// Debug 信息
+func Debug(format string, args... interface{}) {
+	if DEBUG < logger.Level {
+		return
+	}
+	logger.Log.Output(2,  fmt.Sprintf("[D] " + format, args...))
+}
+
+// Info 信息
+func Info(format string, args... interface{})  {
+	if INFO < logger.Level {
+		return
+	}
+	logger.Log.Output(2, fmt.Sprintf("[I] " + format, args...))
+}
+
+// Warning 信息
+func Warning(format string, args... interface{})  {
+	if WARNING < logger.Level {
+		return
+	}
+	logger.Log.Output(2, fmt.Sprintf("[W] " + format, args...))
+}
+
+// Error 信息
+func Error(format string, args... interface{})  {
+	if ERROR < logger.Level {
+		return
+	}
+	logger.Log.Output(2, fmt.Sprintf("[E] " + format, args...))
+}
+
+// Debug 信息
+func (l *Logger) Debug(format string, args... interface{}) {
+	if DEBUG < logger.Level {
+		return
+	}
+	logger.Log.Output(2,  fmt.Sprintf("[D] " + format, args...))
+}
+
+// Info 信息
+func (l *Logger) Info(format string, args... interface{})  {
+	if INFO < logger.Level {
+		return
+	}
+	logger.Log.Output(2, fmt.Sprintf("[I] " + format, args...))
+}
+
+// Warning 信息
+func (l *Logger) Warning(format string, args... interface{})  {
+	if WARNING < logger.Level {
+		return
+	}
+	logger.Log.Output(2, fmt.Sprintf("[W] " + format, args...))
+}
+
+// Error 信息
+func (l *Logger) Error(format string, args... interface{})  {
+	if ERROR < logger.Level {
+		return
+	}
+	logger.Log.Output(2, fmt.Sprintf("[E] " + format, args...))
+}
+
+// 自定义callpath的 Debug信息,常用于测试
+func LogD_(callpath int, format string, args... interface{}) {
+	logger.Log.Output(callpath, fmt.Sprintf("[D] " + format, args...))
+}

+ 34 - 0
src/github.com/tangs-drm/go-tool/log/log_test.go

@@ -0,0 +1,34 @@
+package log
+
+import (
+	"testing"
+	"fmt"
+)
+
+func slog() {
+	if true {
+		LogD_(3, "slog %v", "Debug")
+	}
+}
+
+func TestLog(t *testing.T) {
+	Debug("%v", "Debug")
+	Info("%v", "Info")
+	Warning("%v", "Warning")
+	Error("%v", "Error")
+
+	logger.Debug("logger %v", "Debug")
+	logger.Info("logger %v", "Info")
+	logger.Warning("logger %v", "Warning")
+	logger.Error("logger %v", "Error")
+
+	LogD_(-2, "-2 %v", "Debug")
+	LogD_(-1, "-1 %v", "Debug")
+	LogD_(0, "0 %v", "Debug")
+	LogD_(1, "1 %v", "Debug")
+	LogD_(2, "2 %v", "Debug")
+
+	slog()
+
+	fmt.Println("测试日志完毕")
+}

+ 31 - 0
src/github.com/tangs-drm/go-tool/util/README.md

@@ -0,0 +1,31 @@
+### golang-tool
+
+该包包含了golang使用使用过程中的小工具函数
+
+
+#### map
+
+定义:
+
+```golang
+type Map map[string]interface{}
+```
+
+可以通过该对象转换value,见如下:
+```
+    m = Map{
+        "string": "string",
+        "int": "1",
+        "int32": "2",
+        "int64": "3",
+        "float32": "1.1",
+        "float64": 2.2,
+        "Map": Map{"key": "val"},
+    }
+
+    m.Int("int) // 1 (type int)
+    m.Int32("int64") // 3 (type int32)
+    m.String("float64") // 2.2 (type string)
+```
+
+具体详情可见测试map_test.go

+ 10 - 0
src/github.com/tangs-drm/go-tool/util/error.go

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

+ 22 - 0
src/github.com/tangs-drm/go-tool/util/error_test.go

@@ -0,0 +1,22 @@
+package util
+
+import "testing"
+
+func TestError(t *testing.T) {
+	var err error
+	if nil != err {
+		t.Error(err)
+		return
+	}
+
+	err = Error("%v", "halo")
+	if err == nil {
+		t.Error(err)
+		return
+	}
+
+	if err.Error() != "halo" {
+		t.Error(err)
+		return
+	}
+}

+ 236 - 0
src/github.com/tangs-drm/go-tool/util/map.go

@@ -0,0 +1,236 @@
+package util
+
+import (
+	"encoding/json"
+	"reflect"
+	"strconv"
+	"fmt"
+)
+
+// Map
+type Map map[string]interface{}
+
+// Exist judge if the key exist in map
+func (m Map) Exist(key string) bool {
+	if _, ok := m[key]; !ok {
+		return false
+	}
+	return true
+}
+
+// String return the value type string by key
+func (m Map) String(key string) string {
+	var val string
+	var ok bool
+	if val, ok = m[key].(string); ok {
+		return val
+	}
+
+	res, _ := TransType(m[key], reflect.String)
+	if val, ok = res.(string); !ok {
+		return ""
+	}
+	return val
+}
+
+// Int return the value type int by key
+func (m Map) Int(key string) int {
+	var val int
+	var ok bool
+	if val, ok = m[key].(int); ok {
+		return val
+	}
+	res, _ := TransType(m[key], reflect.Int)
+	if val, ok = res.(int); !ok {
+		return 0
+	}
+	return val
+}
+
+// Int return the value type int32 by key
+func (m Map) Int32(key string) int32 {
+	var val int32
+	var ok bool
+	if val, ok = m[key].(int32); ok {
+		return val
+	}
+	res, _ := TransType(m[key], reflect.Int32)
+	if val, ok = res.(int32); !ok {
+		return 0
+	}
+	return val
+}
+
+// Int return the value type int64 by key
+func (m Map) Int64(key string) int64 {
+	var val int64
+	var ok bool
+	if val, ok = m[key].(int64); ok {
+		return val
+	}
+	res, _ := TransType(m[key], reflect.Int64)
+	if val, ok = res.(int64); !ok {
+		return 0
+	}
+	return val
+}
+
+// Int return the value type float32 by key
+func (m Map) Float32(key string) float32 {
+	var val float32
+	var ok bool
+	if val, ok = m[key].(float32); ok {
+		return val
+	}
+	res, _ := TransType(m[key], reflect.Float32)
+	if val, ok = res.(float32); !ok {
+		return 0
+	}
+	return val
+}
+
+// Int return the value type float32 by key
+func (m Map) Float64(key string) float64 {
+	var val float64
+	var ok bool
+	if val, ok = m[key].(float64); ok {
+		return val
+	}
+	res, _ := TransType(m[key], reflect.Float64)
+	if val, ok = res.(float64); !ok {
+		return 0
+	}
+	return val
+}
+
+// Int return the value type Map by key
+func (m Map) Map(key string) Map {
+	var val Map
+	var ok bool
+	if val, ok = m[key].(Map); !ok {
+		return nil
+	}
+	return val
+}
+
+// S2Json trans data to json, e.g struct, map and so on.
+func S2Json(data interface{}) string {
+	bys, _ := json.Marshal(data)
+	return string(bys)
+}
+
+// Json2S trans json to object
+func Json2S(src string, dest interface{}) error {
+	return json.Unmarshal([]byte(src), dest)
+}
+
+func TransType(val interface{}, descType reflect.Kind) (interface{}, error) {
+	if val == nil {
+		return nil, Error("val is nil")
+	}
+	typ := reflect.TypeOf(val).Kind()
+	switch typ {
+	case reflect.Int:
+		res, _ := val.(int)
+		switch descType {
+		case reflect.Int:
+			return int(res), nil
+		case reflect.Int32:
+			return int32(res), nil
+		case reflect.Int64:
+			return int64(res), nil
+		case reflect.Float32:
+			return float32(res), nil
+		case reflect.Float64:
+			return float64(res), nil
+		case reflect.String:
+			return fmt.Sprintf("%v", res), nil
+		}
+	case reflect.Int32:
+		res, _ := val.(int32)
+		switch descType {
+		case reflect.Int:
+			return int(res), nil
+		case reflect.Int32:
+			return int32(res), nil
+		case reflect.Int64:
+			return int64(res), nil
+		case reflect.Float32:
+			return float32(res), nil
+		case reflect.Float64:
+			return float64(res), nil
+		case reflect.String:
+			return fmt.Sprintf("%v", res), nil
+		}
+	case reflect.Int64:
+		res, _ := val.(int64)
+		switch descType {
+		case reflect.Int:
+			return int(res), nil
+		case reflect.Int32:
+			return int32(res), nil
+		case reflect.Int64:
+			return int64(res), nil
+		case reflect.Float32:
+			return float32(res), nil
+		case reflect.Float64:
+			return float64(res), nil
+		case reflect.String:
+			return fmt.Sprintf("%v", res), nil
+		}
+	case reflect.Float32:
+		res, _ := val.(float32)
+		switch descType {
+		case reflect.Int:
+			return int(res), nil
+		case reflect.Int32:
+			return int32(res), nil
+		case reflect.Int64:
+			return int64(res), nil
+		case reflect.Float32:
+			return float32(res), nil
+		case reflect.Float64:
+			return float64(res), nil
+		case reflect.String:
+			return fmt.Sprintf("%v", res), nil
+		}
+	case reflect.Float64:
+		res, _ := val.(float64)
+		switch descType {
+		case reflect.Int:
+			return int(res), nil
+		case reflect.Int32:
+			return int32(res), nil
+		case reflect.Int64:
+			return int64(res), nil
+		case reflect.Float32:
+			return float32(res), nil
+		case reflect.Float64:
+			return float64(res), nil
+		case reflect.String:
+			return fmt.Sprintf("%v", res), nil
+		}
+	case reflect.String:
+		res, _ := val.(string)
+		switch descType {
+		case reflect.Int:
+			s_res, _ := strconv.Atoi(res)
+			return s_res, nil
+		case reflect.Int32:
+			s_res, _ := strconv.Atoi(res)
+			return int32(s_res), nil
+		case reflect.Int64:
+			s_res, _ := strconv.Atoi(res)
+			return int64(s_res), nil
+		case reflect.Float32:
+			s_res, _ := strconv.ParseFloat(res, 32)
+			return float32(s_res), nil
+		case reflect.Float64:
+			s_res, _ := strconv.ParseFloat(res, 64)
+			return float64(s_res), nil
+		case reflect.String:
+			return fmt.Sprintf("%v", res), nil
+		}
+	}
+	return nil, Error("invalid value type(%v)", typ)
+}

+ 375 - 0
src/github.com/tangs-drm/go-tool/util/map_test.go

@@ -0,0 +1,375 @@
+package util
+
+import (
+	"testing"
+	"math"
+)
+
+func TestMap(t *testing.T) {
+	m := Map{}
+
+	// 检查语法错误
+	m.String("halo")
+	m.Int("halo")
+	m.Int32("halo")
+	m.Int64("halo")
+	m.Float32("halo")
+	m.Float64("halo")
+	m.Map("halo")
+
+	// 检查值
+	m = Map{
+		"string": "string",
+		"int": 1,
+		"int32": int32(2),
+		"int64": int64(3),
+		"float32": float32(1.1),
+		"float64": float64(2.2),
+		"Map": Map{"key": "val"},
+	}
+	if "string" != m.String("string") {
+		t.Error(m.String("string"))
+		return
+	}
+	if 1 != m.Int("int") {
+		t.Error(m.Int("int"))
+		return
+	}
+	if 2 != m.Int32("int32") {
+		t.Error(m.Int32("int32"))
+		return
+	}
+	if 3 != m.Int64("int64") {
+		t.Error(m.Int64("int64"))
+		return
+	}
+	if 1.1 != m.Float32("float32") {
+		t.Error(m.Float32("float32"))
+		return
+	}
+	if 2.2 != m.Float64("float64") {
+		t.Error("float64")
+		return
+	}
+	if nil == m.Map("Map") {
+		t.Error(m.Map("Map"))
+		return
+	}
+	mm := m.Map("Map")
+	if "val" != mm.String("key") {
+		t.Error(mm.String("key"))
+		return
+	}
+	if true != m.Exist("string") {
+		t.Error(m.Exist("string"))
+	}
+	if false != m.Exist("halo") {
+		t.Error(m.Exist("halo"))
+	}
+
+	check(t, m)
+
+	m = Map{
+		"string": "string",
+		"int": "1",
+		"int32": "2",
+		"int64": "3",
+		"float32": "1.1",
+		"float64": "2.2",
+		"Map": Map{"key": "val"},
+	}
+
+	// int
+	if 1 != m.Int("int") {
+		t.Error(m.Int("int"))
+		return
+	}
+	if 2 != m.Int("int32") {
+		t.Error(m.Int("int32"))
+		return
+	}
+	if 3 != m.Int("int64") {
+		t.Error(m.Int("int64"))
+		return
+	}
+	if 0 != m.Int("float32") {
+		t.Error(m.Int("float32"))
+		return
+	}
+	if 0 != m.Int("float64") {
+		t.Error(m.Int("float64"))
+		return
+	}
+	if 0 != m.Int("string") {
+		t.Error(m.Int("string"))
+		return
+	}
+
+	// int32
+	if 1 != m.Int32("int") {
+		t.Error(m.Int32("int"))
+		return
+	}
+	if 2 != m.Int32("int32") {
+		t.Error(m.Int32("int32"))
+		return
+	}
+	if 3 != m.Int32("int64") {
+		t.Error(m.Int32("int64"))
+		return
+	}
+	if 0 != m.Int32("float32") {
+		t.Error(m.Int32("float32"))
+		return
+	}
+	if 0 != m.Int32("float64") {
+		t.Error(m.Int32("float64"))
+		return
+	}
+	if 0 != m.Int32("string") {
+		t.Error(m.Int32("string"))
+		return
+	}
+
+	// int64
+	if 1 != m.Int64("int") {
+		t.Error(m.Int64("int"))
+		return
+	}
+	if 2 != m.Int64("int32") {
+		t.Error(m.Int64("int32"))
+		return
+	}
+	if 3 != m.Int64("int64") {
+		t.Error(m.Int64("int64"))
+		return
+	}
+	if 0 != m.Int64("float32") {
+		t.Error(m.Int64("float32"))
+		return
+	}
+	if 0 != m.Int64("float64") {
+		t.Error(m.Int64("float64"))
+		return
+	}
+	if 0 != m.Int64("string") {
+		t.Error(m.Int64("string"))
+		return
+	}
+
+	// float32
+	if 1 != m.Float32("int") {
+		t.Error(m.Float32("int"))
+		return
+	}
+	if 2 != m.Float32("int32") {
+		t.Error(m.Float32("int32"))
+		return
+	}
+	if 3 != m.Float32("int64") {
+		t.Error(m.Float32("int64"))
+		return
+	}
+	if 1.1 != m.Float32("float32") {
+		t.Error(m.Float32("float32"))
+		return
+	}
+	if 2.2 != m.Float32("float64") {
+		t.Error(m.Float32("float64"))
+		return
+	}
+	if 0 != m.Float32("string") {
+		t.Error(m.Float32("string"))
+		return
+	}
+
+	// float64
+	if 1 != m.Float64("int") {
+		t.Error(m.Float64("int"))
+		return
+	}
+	if 2 != m.Float64("int32") {
+		t.Error(m.Float64("int32"))
+		return
+	}
+	if 3 != m.Float64("int64") {
+		t.Error(m.Float64("int64"))
+		return
+	}
+	//if math.Abs(float64(1.1 - m.Float64("float32"))) > 0.0001 {
+	//	t.Error(m.Float64("float32"))
+	//	return
+	//}
+	if 1.1 != m.Float64("float32") {
+		t.Error(m.Float32("float32"))
+		return
+	}
+	if 2.2 != m.Float64("float64") {
+		t.Error(m.Float64("float64"))
+		return
+	}
+	if 0 != m.Float64("string") {
+		t.Error(m.Float64("string"))
+		return
+	}
+
+	// string
+	if "1" != m.String("int") {
+		t.Error(m.String("int"))
+		return
+	}
+	if "2" != m.String("int32") {
+		t.Error(m.String("int32"))
+		return
+	}
+	if "3" != m.String("int64") {
+		t.Error(m.String("int64"))
+		return
+	}
+	if "1.1" != m.String("float32") {
+		t.Error(m.String("float32"))
+		return
+	}
+	if "2.2" != m.String("float64") {
+		t.Error(m.String("float64"))
+		return
+	}
+	if "string" != m.String("string") {
+		t.Error(m.String("string"))
+		return
+	}
+}
+
+func check(t *testing.T, m Map) {
+	// int
+	if 2 != m.Int("int32") {
+		t.Error(m.Int("int32"))
+		return
+	}
+	if 3 != m.Int("int64") {
+		t.Error(m.Int("int64"))
+		return
+	}
+	if 1 != m.Int("float32") {
+		t.Error(m.Int("float32"))
+		return
+	}
+	if 2 != m.Int("float64") {
+		t.Error(m.Int("float64"))
+		return
+	}
+	if 0 != m.Int("string") {
+		t.Error(m.Int("string"))
+		return
+	}
+
+	// int32
+	if 1 != m.Int32("int") {
+		t.Error(m.Int32("int"))
+		return
+	}
+	if 3 != m.Int32("int64") {
+		t.Error(m.Int32("int64"))
+		return
+	}
+	if 1 != m.Int32("float32") {
+		t.Error(m.Int32("float32"))
+		return
+	}
+	if 2 != m.Int32("float64") {
+		t.Error(m.Int32("float64"))
+		return
+	}
+	if 0 != m.Int32("string") {
+		t.Error(m.Int32("string"))
+		return
+	}
+
+	// int64
+	if 1 != m.Int64("int") {
+		t.Error(m.Int64("int"))
+		return
+	}
+	if 2 != m.Int64("int32") {
+		t.Error(m.Int64("int32"))
+		return
+	}
+	if 1 != m.Int64("float32") {
+		t.Error(m.Int64("float32"))
+		return
+	}
+	if 2 != m.Int64("float64") {
+		t.Error(m.Int64("float64"))
+		return
+	}
+	if 0 != m.Int64("string") {
+		t.Error(m.Int64("string"))
+		return
+	}
+
+	// float32
+	if 1 != m.Float32("int") {
+		t.Error(m.Float32("int"))
+		return
+	}
+	if 2 != m.Float32("int32") {
+		t.Error(m.Float32("int32"))
+		return
+	}
+	if 3 != m.Float32("int64") {
+		t.Error(m.Float32("int64"))
+		return
+	}
+	if 2.2 != m.Float32("float64") {
+		t.Error(m.Float32("float64"))
+		return
+	}
+	if 0 != m.Float32("string") {
+		t.Error(m.Float32("string"))
+		return
+	}
+
+	// float64
+	if 1 != m.Float64("int") {
+		t.Error(m.Float64("int"))
+		return
+	}
+	if 2 != m.Float64("int32") {
+		t.Error(m.Float64("int32"))
+		return
+	}
+	if 3 != m.Float64("int64") {
+		t.Error(m.Float64("int64"))
+		return
+	}
+	if math.Abs(float64(1.1 - m.Float64("float32"))) > 0.0001 {
+		t.Error(m.Float64("float32"))
+		return
+	}
+	if 0 != m.Float64("string") {
+		t.Error(m.Float64("string"))
+		return
+	}
+
+	// string
+	if "1" != m.String("int") {
+		t.Error(m.String("int"))
+		return
+	}
+	if "2" != m.String("int32") {
+		t.Error(m.String("int32"))
+		return
+	}
+	if "3" != m.String("int64") {
+		t.Error(m.String("int64"))
+		return
+	}
+	if "1.1" != m.String("float32") {
+		t.Error(m.String("float32"))
+		return
+	}
+	if "2.2" != m.String("float64") {
+		t.Error(m.String("float64"))
+		return
+	}
+}

+ 65 - 0
src/github.com/tangs-drm/go-tool/util/request.go

@@ -0,0 +1,65 @@
+/*
+	goalng http request tool.
+	it contain func to request with method GET and POST.
+*/
+package util
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+)
+
+// httpRequest 发起一个http请求
+// method 请求的方法, body 请求体,headers 设置的请求头, format, args两个合在一起拼接成一个url
+func httpRequest(method string, body io.Reader, headers map[string]string, format string, args ...interface{}) ([]byte, error) {
+	var client = http.Client{}
+	var url = fmt.Sprintf(format, args...)
+	req, err := http.NewRequest(method, url, body)
+	if err != nil {
+		return nil, err
+	}
+	for k, v := range headers {
+		req.Header.Set(k, v)
+	}
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+
+	return ioutil.ReadAll(resp.Body)
+}
+
+// HTTPGetString 开启了一个GET请求,获得字符串的返回值
+func HTTPGetString(format string, args ...interface{}) (string, error) {
+	bys, err := httpRequest("GET", nil, nil, format, args...)
+	return string(bys), err
+}
+
+// HTTPGetMap 开启了一个GET请求,获得Map对象的返回值
+func HTTPGetMap(format string, args ...interface{}) (Map, error) {
+	bys, err := httpRequest("GET", nil, nil, format, args...)
+	if err != nil {
+		return nil, err
+	}
+	var result Map
+	err = json.Unmarshal(bys, &result)
+	return result, err
+}
+
+// HTTPPostString 开启了一个Post请求,获得字符串的返回值
+func HTTPPostString(url string, headers map[string]string, body io.Reader) (string, error) {
+	bys, err := httpRequest("POST", body, headers, "%v", url)
+	return string(bys), err
+}
+
+// HTTPPostMap 开启了一个Post请求,获得Map对象的返回值
+func HTTPPostMap(url string, headers map[string]string, body io.Reader) (Map, error) {
+	bys, err := httpRequest("POST", body, headers, "%v", url)
+	var result Map
+	err = json.Unmarshal(bys, &result)
+	return result, err
+}

+ 131 - 0
src/github.com/tangs-drm/go-tool/util/request_test.go

@@ -0,0 +1,131 @@
+package util
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+)
+
+func HandleTestFunc(w http.ResponseWriter, r *http.Request) {
+	msg := Map{
+		"msg": "message",
+	}
+	fmt.Println("HandleTestFunc come in")
+	w.Write([]byte(S2Json(msg)))
+}
+
+func HandleTestFuncString(w http.ResponseWriter, r *http.Request) {
+	w.Write([]byte("string"))
+	fmt.Println("HandleTestFuncString come in")
+}
+
+func TestRequest(t *testing.T) {
+	fmt.Println("TestRequest测试开始")
+	// 启动server
+	go func() {
+		http.HandleFunc("/message", HandleTestFunc)
+		http.HandleFunc("/string", HandleTestFuncString)
+		http.ListenAndServe(":8900", nil)
+	}()
+
+	var resString string
+	var resMap Map
+	var checkString string
+	var checkMap Map
+	var err error
+	var headers = map[string]string{}
+	// 测试HTTPGetString
+	resString, err = HTTPGetString("%v", "http://127.0.0.1:8900/message")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	checkMap = Map{"msg": "message"}
+	checkString = S2Json(checkMap)
+	if resString != checkString {
+		t.Error(resString)
+		return
+	}
+
+	// 测试HTTPGetMap
+	resMap, err = HTTPGetMap("%v", "http://127.0.0.1:8900/message")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	checkMap = Map{"msg": "message"}
+	if checkMap["msg"] != resMap["msg"] {
+		t.Error(checkMap["msg"])
+		return
+	}
+
+	// 测试HTTPPostString
+	headers = map[string]string{
+		"Content-Type": "text/html",
+	}
+	resString, err = HTTPPostString("http://127.0.0.1:8900/message", headers, nil)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if resString != checkString {
+		t.Error(resString)
+		return
+	}
+
+	// 测试HTTPPostMap
+	headers = map[string]string{
+		"Content-Type": "application/json",
+	}
+	resMap, err = HTTPPostMap("http://127.0.0.1:8900/message", headers, nil)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if resMap["msg"] != checkMap["msg"] {
+		t.Error(resMap["msg"])
+		return
+	}
+
+	// request for a string
+
+	resString, err = HTTPGetString("%v", "http://127.0.0.1:8900/string")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if resString != "string" {
+		t.Error(err)
+		return
+	}
+
+	resMap, err = HTTPGetMap("%v", "http://127.0.0.1:8900/string")
+	if err == nil {
+		t.Error(err)
+		return
+	}
+
+	headers = map[string]string{
+		"Content-Type": "application/json",
+	}
+	resString, err = HTTPPostString("http://127.0.0.1:8900/string", headers, nil)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if resString != "string" {
+		t.Error(err)
+		return
+	}
+
+	headers = map[string]string{
+		"Content-Type": "application/json",
+	}
+	resMap, err = HTTPPostMap("http://127.0.0.1:8900/string", headers, nil)
+	if err == nil {
+		t.Error(err)
+		return
+	}
+
+	fmt.Println("TestRequest测试完毕")
+}

+ 19 - 0
src/github.com/tangs-drm/go-tool/util/time.go

@@ -0,0 +1,19 @@
+package util
+
+import "time"
+
+// Now返回一个13位的时间戳,即单位是毫秒
+func Now() int64 {
+	now := time.Now().UnixNano()/1e6
+	return now
+}
+
+// Now10 返回一个10位的时间戳,即单位是秒
+func Now10() int64 {
+	return Now() / 1000
+}
+
+// Now13返回一个13位的时间戳,即单位是毫秒,同Now()
+func Now13() int64 {
+	return Now()
+}

+ 15 - 0
src/github.com/tangs-drm/go-tool/util/time_test.go

@@ -0,0 +1,15 @@
+package util
+
+import (
+	"testing"
+	"fmt"
+	"time"
+)
+
+func TestTime(t *testing.T) {
+	fmt.Println(time.Now().Unix())
+	fmt.Println(time.Now().UnixNano())
+	fmt.Println(Now())
+	fmt.Println(Now10())
+	fmt.Println(Now13())
+}

+ 83 - 0
src/github.com/tangs-drm/go-tool/util/value.go

@@ -0,0 +1,83 @@
+package util
+
+import (
+	"crypto/md5"
+	"encoding/hex"
+	"io"
+	"strings"
+	"encoding/base64"
+	"fmt"
+	"crypto/rand"
+)
+
+//生成32位md5字串
+func GetMd5String(s string) string {
+	h := md5.New()
+	h.Write([]byte(s))
+	return hex.EncodeToString(h.Sum(nil))
+}
+
+//生成Guid字串
+func UUIDString() string {
+	b := make([]byte, 48)
+
+	if _, err := io.ReadFull(rand.Reader, b); err != nil {
+		return ""
+	}
+	return strings.ToUpper(GetMd5String(base64.URLEncoding.EncodeToString(b)))
+}
+
+func UUID() string {
+	uuid := UUIDString()
+	if "" == uuid {
+		return ""
+	}
+	return fmt.Sprintf("%v-%v-%v-%v-%v", uuid[0:8], uuid[8:12], uuid[12:16], uuid[16:20], uuid[20:])
+}
+// TrimAryStringRepeat trim the s in every string in strs,
+// repeat is decide result string array whether allowed repeat,
+// false is don't allowed
+func TrimAryStringRepeat(strs []string, s string, repeat bool) []string {
+	var strMap = map[string]bool{}
+	var results = []string{}
+	for _, str := range strs {
+		val := strings.Trim(str, s)
+		if len(val) < 1 {
+			continue
+		}
+		if strMap[val] && !repeat {
+			continue
+		}
+		strMap[val] = true
+		results = append(results, val)
+	}
+	return results
+}
+
+// TrimAryInt trim the same int value in vals or which value equal to arg
+func TrimAryInt(vals []int, arg int) []int {
+	var intMap = map[int]bool{}
+	var result = []int{}
+	for _, val := range vals {
+		if intMap[val] {
+			continue
+		}
+		intMap[val] = true
+		result = append(result, val)
+	}
+	return result
+}
+
+// TrimAryInt trim the same int value int vals
+func TrimAryIntRepeat(vals []int) []int {
+	var intMap = map[int]bool{}
+	var result = []int{}
+	for _, val := range vals {
+		if intMap[val] {
+			continue
+		}
+		intMap[val] = true
+		result = append(result, val)
+	}
+	return result
+}

+ 152 - 0
src/github.com/tangs-drm/go-tool/util/value_test.go

@@ -0,0 +1,152 @@
+package util
+
+import (
+	"testing"
+	"fmt"
+	"time"
+)
+
+func TestTrimValue(t *testing.T) {
+	// test string
+	var strs = []string{}
+	strs = TrimAryStringRepeat(strs, "", true)
+	if len(strs) > 0 {
+		t.Error(strs)
+		return
+	}
+
+	strs = []string{"a", "b", "a"}
+	strs = TrimAryStringRepeat(strs, "a", false)
+	if len(strs) != 1 {
+		t.Error(strs)
+		return
+	}
+	if strs[0] != "b" {
+		t.Error(strs)
+		return
+	}
+
+	strs = []string{"a", "b", "a"}
+	strs = TrimAryStringRepeat(strs, "", true)
+	if len(strs) != 3 {
+		t.Error(strs)
+		return
+	}
+	if strs[0] != "a" || strs[1] != "b" || strs[2] != "a" {
+		t.Error(strs)
+		return
+	}
+
+	strs = []string{"ab", "bc", "abc", "de"}
+	strs = TrimAryStringRepeat(strs, "", true)
+	if len(strs) != 4 {
+		t.Error(strs)
+		return
+	}
+	if strs[0] != "ab" || strs[1] != "bc" || strs[2] != "abc" || strs[3] != "de" {
+		t.Error(strs)
+		return
+	}
+
+	strs = TrimAryStringRepeat(strs, "a", true)
+	if len(strs) != 4 {
+		t.Error(strs)
+		return
+	}
+	if strs[0] != "b" || strs[1] != "bc" || strs[2] != "bc" || strs[3] != "de" {
+		t.Error(strs)
+		return
+	}
+
+	strs = []string{"b", "bc", "bc", "de"}
+	strs = TrimAryStringRepeat(strs, "a", false)
+	if len(strs) != 3 {
+		t.Error(strs)
+		return
+	}
+	if strs[0] != "b" || strs[1] != "bc" || strs[2] != "de" {
+		t.Error(strs)
+		return
+	}
+
+
+	// test int
+	var ints = []int{1, 2}
+	ints = TrimAryInt(ints, 1)
+	if len(ints) != 2 {
+		t.Error(ints)
+		return
+	}
+	if ints[0] != 1 || ints[1] != 2 {
+		t.Error(ints)
+		return
+	}
+
+	ints = []int{1, 1, 2, 3}
+	ints = TrimAryInt(ints, -1)
+	if len(ints) != 3 {
+		t.Error(ints)
+		return
+	}
+	if ints[0] != 1 || ints[1] != 2 || ints[2] != 3 {
+		t.Error(ints)
+		return
+	}
+
+
+	// test int
+	ints = []int{1, 1, 2, 3}
+	ints = TrimAryIntRepeat(ints)
+	if len(ints) != 3 {
+		t.Error(ints)
+		return
+	}
+	if ints[0] != 1 || ints[1] != 2 || ints[2] != 3 {
+		t.Error(ints)
+		return
+	}
+}
+
+func TestUuid(t *testing.T) {
+	var times int = 10
+	for i := 0; i < times; i ++ {
+		testUuid(t, times)
+	}
+}
+
+func testUuid(t *testing.T, times int) {
+	fmt.Println("times --- ", times, " START")
+	uuid := UUID()
+	if "" == uuid {
+		t.Error(uuid)
+		return
+	}
+	t.Log(uuid)
+
+	// 连续生成uuid 10000个,查看是否有重复的id生成
+	var length = 10000
+	strs := make([]string, 0, length+10)
+	for i := 0; i < length; i ++ {
+		strs = append(strs, UUID())
+	}
+	strs = TrimAryStringRepeat(strs, "", false)
+	if len(strs) != length {
+		t.Error(len(strs))
+	}
+
+	// 并发生成uuid 10000个,查看是否有重复的uuid生成
+	strs = []string{}
+	strs = make([]string, 0, length+100)
+	for i := 0; i < length; i ++ {
+		go func() {
+			strs = append(strs, UUID())
+		}()
+	}
+	time.Sleep(time.Second*1)
+	originLength := len(strs)
+	strs = TrimAryStringRepeat(strs, "", false)
+	if len(strs) != originLength {
+		t.Error(len(strs))
+	}
+	fmt.Println("times --- ", times, " END")
+}

+ 1 - 0
src/gopkg.in/mgo.v2

@@ -0,0 +1 @@
+Subproject commit 3f83fa5005286a7fe593b055f0d7771a7dce4655

+ 51 - 0
src/main.go

@@ -0,0 +1,51 @@
+package main
+
+import (
+	"github.com/tangs-drm/go-tool/config"
+	"github.com/tangs-drm/go-tool/log"
+	"fmt"
+	"gopkg.in/mgo.v2"
+	"view/viewdb"
+	"view/viewapi"
+	thttp "github.com/tangs-drm/go-tool/http"
+	"net/http"
+)
+
+func main() {
+
+	cfgPath := "config.conf"
+	cfg := config.NewConfig()
+	err := cfg.Config(cfgPath)
+	if err != nil {
+		fmt.Printf("[MAIN] read config(%v) error ->(%v) \n", cfgPath, err)
+		log.Error("[MAIN] read config(%v) error ->(%v)", cfgPath, err)
+		return
+	}
+	cfg.Print()
+
+	// 链接数据库
+	session, err := mgo.Dial(cfg.String("db_url"))
+	if err != nil {
+		fmt.Printf("[MAIN] dail to db with db_url(%v) error ->(%v) \n", cfg.String("db_url"), err)
+		log.Error("[MAIN] dail to db with db_url(%v) error ->(%v)", cfg.String("db_url"), err)
+		return
+	}
+
+	if len(cfg.String("db_name")) < 1 {
+		fmt.Printf("[MAIN] get db name empty")
+		log.Error("[MAIN] get db name empty")
+		return
+	}
+	db := session.DB(cfg.String("db_name"))
+	viewdb.SetC(db)
+
+	// 创建http对象
+	mux := thttp.NewServerMux()
+
+	viewapi.Router(mux)
+
+	fmt.Printf("[MAIN] process will start with port: %v \n", cfg.String("listen_port"))
+	log.Debug("[MAIN] process will start with port: %v", cfg.String("listen_port"))
+
+	fmt.Println(http.ListenAndServe(cfg.String("listen_port"), mux))
+}

+ 7 - 0
src/response/message.go

@@ -0,0 +1,7 @@
+package response
+
+var Message = map[int]string{
+	0: "成功",
+	1: "参数错误",
+	2: "系统错误",
+}

+ 1 - 0
src/view/README.md

@@ -0,0 +1 @@
+### 页面的浏览次数统计

+ 7 - 0
src/view/viewapi/api.go

@@ -0,0 +1,7 @@
+package viewapi
+
+import "github.com/tangs-drm/go-tool/http"
+
+func Router(mux *http.Mux) {
+	mux.HandleFunc("/view_article", ViewRecord)
+}

+ 39 - 0
src/view/viewapi/viewapi.go

@@ -0,0 +1,39 @@
+package viewapi
+
+import (
+	"net/http"
+	"github.com/tangs-drm/go-tool/log"
+	"github.com/tangs-drm/go-tool/util"
+	thttp "github.com/tangs-drm/go-tool/http"
+	"view/viewdb"
+	"response"
+)
+
+func ViewRecord(w http.ResponseWriter, r *http.Request) {
+	var code int
+	var message string
+	var err error
+	var result util.Map
+	defer func() {
+		message = response.Message[code]
+		thttp.WriteResponse(w, code, message, err, result)
+	}()
+
+	article_id := r.FormValue("aid")
+	if len(article_id) < 1 {
+		code = 1
+		err = util.Error("[ViewRecord] receive invalid article_id")
+		log.Error("[ViewRecord] receive invalid article_id")
+		return
+	}
+
+	err = viewdb.Record(article_id)
+	if err != nil {
+		code = 2
+		log.Error("[ViewRecord] record with id(%v) error =>(%v)", article_id, err)
+		return
+	}
+
+	log.Debug("[ViewRecord] record with id(%v) success", article_id)
+	return
+}

+ 17 - 0
src/view/viewdb/db.go

@@ -0,0 +1,17 @@
+package viewdb
+
+import "gopkg.in/mgo.v2"
+
+const (
+	VIEW_TIMES = "view_times"
+)
+
+var Database *mgo.Database
+
+func SetC(db *mgo.Database) {
+	Database = db
+}
+
+func C(collection string) *mgo.Collection {
+	return Database.C(collection)
+}

+ 13 - 0
src/view/viewdb/items.go

@@ -0,0 +1,13 @@
+package viewdb
+
+import "github.com/tangs-drm/go-tool/util"
+
+// PageView 记录了页面被浏览的次数
+type PageView struct {
+	Id string `json:"id" bson:"_id"`
+	Article string `json:"article" bson:"article"`
+	Count int `json:"count" bson:"count"`
+	Attrs util.Map `json:"attrs" bson:"attrs"`
+	Time int64 `json:"time" bson:"time"`
+	LastTime int64 `json:"last_time" bson:"lastTime"`
+}

+ 32 - 0
src/view/viewdb/record.go

@@ -0,0 +1,32 @@
+package viewdb
+
+import (
+	"github.com/tangs-drm/go-tool/util"
+	"gopkg.in/mgo.v2/bson"
+	"github.com/tangs-drm/go-tool/log"
+)
+
+func Record(article_id string) error {
+	info, err := C(VIEW_TIMES).Upsert(bson.M{
+		"article": article_id,
+	}, bson.M{
+		"$setOnInsert": bson.M{
+			"_id": bson.NewObjectId().Hex(),
+			"time": util.Now(),
+			"attrs": util.Map{},
+		},
+		"$set": bson.M{
+			"lastTime": util.Now(),
+		},
+		"$inc": bson.M{
+			"count": 1,
+		},
+	})
+	if err != nil {
+		log.Error("[Record] record with id(%v), time(%v) error ->(%v)", article_id, util.Now(), err)
+		return err
+	}
+
+	log.Debug("[Record] record with id(%v), time(%v) success with info(%v)", article_id, util.Now(), util.S2Json(info))
+	return nil
+}

+ 0 - 0
src/www/index.html