mysql: pin the mysql dependency

This commit is contained in:
bergquist 2018-01-23 20:08:47 +01:00
parent d7f6f11eb1
commit 6618bff806
47 changed files with 1965 additions and 628 deletions

33
Gopkg.lock generated
View File

@ -4,8 +4,8 @@
[[projects]] [[projects]]
name = "cloud.google.com/go" name = "cloud.google.com/go"
packages = ["compute/metadata"] packages = ["compute/metadata"]
revision = "050b16d2314d5fc3d4c9a51e4cd5c7468e77f162" revision = "767c40d6a2e058483c25fa193e963a22da17236d"
version = "v0.17.0" version = "v0.18.0"
[[projects]] [[projects]]
name = "github.com/BurntSushi/toml" name = "github.com/BurntSushi/toml"
@ -28,8 +28,8 @@
[[projects]] [[projects]]
name = "github.com/aws/aws-sdk-go" name = "github.com/aws/aws-sdk-go"
packages = ["aws","aws/awserr","aws/awsutil","aws/client","aws/client/metadata","aws/corehandlers","aws/credentials","aws/credentials/ec2rolecreds","aws/credentials/endpointcreds","aws/credentials/stscreds","aws/defaults","aws/ec2metadata","aws/endpoints","aws/request","aws/session","aws/signer/v4","internal/shareddefaults","private/protocol","private/protocol/ec2query","private/protocol/query","private/protocol/query/queryutil","private/protocol/rest","private/protocol/restxml","private/protocol/xml/xmlutil","service/cloudwatch","service/ec2","service/ec2/ec2iface","service/s3","service/sts"] packages = ["aws","aws/awserr","aws/awsutil","aws/client","aws/client/metadata","aws/corehandlers","aws/credentials","aws/credentials/ec2rolecreds","aws/credentials/endpointcreds","aws/credentials/stscreds","aws/defaults","aws/ec2metadata","aws/endpoints","aws/request","aws/session","aws/signer/v4","internal/shareddefaults","private/protocol","private/protocol/ec2query","private/protocol/query","private/protocol/query/queryutil","private/protocol/rest","private/protocol/restxml","private/protocol/xml/xmlutil","service/cloudwatch","service/ec2","service/ec2/ec2iface","service/s3","service/sts"]
revision = "c13a879e75646fe750d21bcb05bf2cabea9791c1" revision = "decd990ddc5dcdf2f73309cbcab90d06b996ca28"
version = "v1.12.65" version = "v1.12.67"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -118,8 +118,7 @@
[[projects]] [[projects]]
name = "github.com/go-sql-driver/mysql" name = "github.com/go-sql-driver/mysql"
packages = ["."] packages = ["."]
revision = "a0583e0143b1624142adab07e0e97fe106d99561" revision = "2cc627ac8defc45d65066ae98f898166f580f9a4"
version = "v1.3"
[[projects]] [[projects]]
name = "github.com/go-stack/stack" name = "github.com/go-stack/stack"
@ -147,7 +146,7 @@
branch = "master" branch = "master"
name = "github.com/golang/protobuf" name = "github.com/golang/protobuf"
packages = ["proto","ptypes","ptypes/any","ptypes/duration","ptypes/timestamp"] packages = ["proto","ptypes","ptypes/any","ptypes/duration","ptypes/timestamp"]
revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845" revision = "c65a0412e71e8b9b3bfd22925720d23c0f054237"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -177,7 +176,7 @@
branch = "master" branch = "master"
name = "github.com/hashicorp/go-hclog" name = "github.com/hashicorp/go-hclog"
packages = ["."] packages = ["."]
revision = "ca137eb4b4389c9bc6f1a6d887f056bf16c00510" revision = "5bcb0f17e36442247290887cc914a6e507afa5c4"
[[projects]] [[projects]]
name = "github.com/hashicorp/go-plugin" name = "github.com/hashicorp/go-plugin"
@ -247,7 +246,7 @@
branch = "master" branch = "master"
name = "github.com/lib/pq" name = "github.com/lib/pq"
packages = [".","oid"] packages = [".","oid"]
revision = "27ea5d92de30060e7121ddd543fe14e9a327e0cc" revision = "61fe37aa2ee24fabcdbe5c4ac1d4ac566f88f345"
[[projects]] [[projects]]
name = "github.com/mattn/go-colorable" name = "github.com/mattn/go-colorable"
@ -312,8 +311,8 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "github.com/prometheus/procfs" name = "github.com/prometheus/procfs"
packages = [".","xfs"] packages = [".","internal/util","nfsd","xfs"]
revision = "b15cd069a83443be3154b719d0cc9fe8117f09fb" revision = "85fadb6e89903ef7cca6f6a804474cd5ea85b6e1"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -367,7 +366,7 @@
branch = "master" branch = "master"
name = "golang.org/x/crypto" name = "golang.org/x/crypto"
packages = ["pbkdf2"] packages = ["pbkdf2"]
revision = "13931e22f9e72ea58bb73048bc752b48c6d4d4ac" revision = "3d37316aaa6bd9929127ac9a527abf408178ea7b"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -391,7 +390,7 @@
branch = "master" branch = "master"
name = "golang.org/x/sys" name = "golang.org/x/sys"
packages = ["unix"] packages = ["unix"]
revision = "2c42eef0765b9837fbdab12011af7830f55f88f0" revision = "af50095a40f9041b3b38960738837185c26e9419"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -401,7 +400,7 @@
[[projects]] [[projects]]
name = "google.golang.org/appengine" name = "google.golang.org/appengine"
packages = [".","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"] packages = [".","cloudsql","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"]
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
version = "v1.0.0" version = "v1.0.0"
@ -414,8 +413,8 @@
[[projects]] [[projects]]
name = "google.golang.org/grpc" name = "google.golang.org/grpc"
packages = [".","balancer","balancer/base","balancer/roundrobin","codes","connectivity","credentials","encoding","grpclb/grpc_lb_v1/messages","grpclog","health","health/grpc_health_v1","internal","keepalive","metadata","naming","peer","resolver","resolver/dns","resolver/passthrough","stats","status","tap","transport"] packages = [".","balancer","balancer/base","balancer/roundrobin","codes","connectivity","credentials","encoding","grpclb/grpc_lb_v1/messages","grpclog","health","health/grpc_health_v1","internal","keepalive","metadata","naming","peer","resolver","resolver/dns","resolver/passthrough","stats","status","tap","transport"]
revision = "7cea4cc846bcf00cbb27595b07da5de875ef7de9" revision = "6b51017f791ae1cfbec89c52efdf444b13b550ef"
version = "v1.9.1" version = "v1.9.2"
[[projects]] [[projects]]
branch = "v3" branch = "v3"
@ -468,6 +467,6 @@
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "dbc036f87b80a8874e6df839df1dea4c18898cf23a147b1183da986621a5c43e" inputs-digest = "6dc0af6501d9038e9803db1dc8b2876a86524920418dc0159bb3a23e2fc3e487"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View File

@ -76,7 +76,8 @@ ignored = [
[[constraint]] [[constraint]]
name = "github.com/go-sql-driver/mysql" name = "github.com/go-sql-driver/mysql"
version = "1.3.0" revision = "2cc627ac8defc45d65066ae98f898166f580f9a4"
#version = "1.3.0" //keeping this since we would rather depend on version then commit
[[constraint]] [[constraint]]
name = "github.com/go-stack/stack" name = "github.com/go-stack/stack"

View File

@ -27,6 +27,7 @@ Jonathan Amsterdam <jba@google.com>
Kunpei Sakai <namusyaka@gmail.com> Kunpei Sakai <namusyaka@gmail.com>
Luna Duclos <luna.duclos@palmstonegames.com> Luna Duclos <luna.duclos@palmstonegames.com>
Magnus Hiie <magnus.hiie@gmail.com> Magnus Hiie <magnus.hiie@gmail.com>
Mario Castro <mariocaster@gmail.com>
Michael McGreevy <mcgreevy@golang.org> Michael McGreevy <mcgreevy@golang.org>
Omar Jarjur <ojarjur@google.com> Omar Jarjur <ojarjur@google.com>
Paweł Knap <pawelknap88@gmail.com> Paweł Knap <pawelknap88@gmail.com>

2
vendor/cloud.google.com/go/LICENSE generated vendored
View File

@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright 2014 Google Inc. Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

70
vendor/cloud.google.com/go/README.md generated vendored
View File

@ -33,6 +33,38 @@ make backwards-incompatible changes.
## News ## News
_January 18, 2018_
*v0.18.0*
- bigquery:
- Marked stable.
- Schema inference of nullable fields supported.
- Added TimePartitioning to QueryConfig.
- firestore: Data provided to DocumentRef.Set with a Merge option can contain
Delete sentinels.
- logging: Clients can accept parent resources other than projects.
- pubsub:
- pubsub/pstest: A lighweight fake for pubsub. Experimental; feedback welcome.
- Support updating more subscription metadata: AckDeadline,
RetainAckedMessages and RetentionDuration.
- oslogin/apiv1beta: New client for the Cloud OS Login API.
- rpcreplay: A package for recording and replaying gRPC traffic.
- spanner:
- Add a ReadWithOptions that supports a row limit, as well as an index.
- Support query plan and execution statistics.
- Added [OpenCensus](http://opencensus.io) support.
- storage: Clarify checksum validation for gzipped files (it is not validated
when the file is served uncompressed).
_December 11, 2017_ _December 11, 2017_
*v0.17.0* *v0.17.0*
@ -85,7 +117,7 @@ _October 30, 2017_
- Support updating a table's view configuration. - Support updating a table's view configuration.
- Fix uploading civil times with nanoseconds. - Fix uploading civil times with nanoseconds.
- storage: - storage:
- Support PubSub notifications. - Support PubSub notifications.
- Support Requester Pays buckets. - Support Requester Pays buckets.
@ -110,22 +142,25 @@ _October 3, 2017_
Google API | Status | Package Google API | Status | Package
---------------------------------|--------------|----------------------------------------------------------- ---------------------------------|--------------|-----------------------------------------------------------
[Datastore][cloud-datastore] | stable | [`cloud.google.com/go/datastore`][cloud-datastore-ref] [BigQuery][cloud-bigquery] | stable | [`cloud.google.com/go/bigquery`][cloud-bigquery-ref]
[Firestore][cloud-firestore] | beta | [`cloud.google.com/go/firestore`][cloud-firestore-ref]
[Storage][cloud-storage] | stable | [`cloud.google.com/go/storage`][cloud-storage-ref]
[Bigtable][cloud-bigtable] | beta | [`cloud.google.com/go/bigtable`][cloud-bigtable-ref] [Bigtable][cloud-bigtable] | beta | [`cloud.google.com/go/bigtable`][cloud-bigtable-ref]
[BigQuery][cloud-bigquery] | beta | [`cloud.google.com/go/bigquery`][cloud-bigquery-ref] [Container][cloud-container] | alpha | [`cloud.google.com/go/container/apiv1`][cloud-container-ref]
[Data Loss Prevention][cloud-dlp]| alpha | [`cloud.google.com/go/dlp/apiv2beta1`][cloud-dlp-ref]
[Datastore][cloud-datastore] | stable | [`cloud.google.com/go/datastore`][cloud-datastore-ref]
[Debugger][cloud-debugger] | alpha | [`cloud.google.com/go/debugger/apiv2`][cloud-debugger-ref]
[ErrorReporting][cloud-errors] | alpha | [`cloud.google.com/go/errorreporting`][cloud-errors-ref]
[Firestore][cloud-firestore] | beta | [`cloud.google.com/go/firestore`][cloud-firestore-ref]
[Language][cloud-language] | stable | [`cloud.google.com/go/language/apiv1`][cloud-language-ref]
[Logging][cloud-logging] | stable | [`cloud.google.com/go/logging`][cloud-logging-ref] [Logging][cloud-logging] | stable | [`cloud.google.com/go/logging`][cloud-logging-ref]
[Monitoring][cloud-monitoring] | beta | [`cloud.google.com/go/monitoring/apiv3`][cloud-monitoring-ref] [Monitoring][cloud-monitoring] | beta | [`cloud.google.com/go/monitoring/apiv3`][cloud-monitoring-ref]
[OS Login][cloud-oslogin] | alpha | [`cloud.google.com/compute/docs/oslogin/rest`][cloud-oslogin-ref]
[Pub/Sub][cloud-pubsub] | beta | [`cloud.google.com/go/pubsub`][cloud-pubsub-ref] [Pub/Sub][cloud-pubsub] | beta | [`cloud.google.com/go/pubsub`][cloud-pubsub-ref]
[Vision][cloud-vision] | stable | [`cloud.google.com/go/vision/apiv1`][cloud-vision-ref]
[Language][cloud-language] | stable | [`cloud.google.com/go/language/apiv1`][cloud-language-ref]
[Speech][cloud-speech] | stable | [`cloud.google.com/go/speech/apiv1`][cloud-speech-ref]
[Spanner][cloud-spanner] | stable | [`cloud.google.com/go/spanner`][cloud-spanner-ref] [Spanner][cloud-spanner] | stable | [`cloud.google.com/go/spanner`][cloud-spanner-ref]
[Speech][cloud-speech] | stable | [`cloud.google.com/go/speech/apiv1`][cloud-speech-ref]
[Storage][cloud-storage] | stable | [`cloud.google.com/go/storage`][cloud-storage-ref]
[Translation][cloud-translation] | stable | [`cloud.google.com/go/translate`][cloud-translation-ref] [Translation][cloud-translation] | stable | [`cloud.google.com/go/translate`][cloud-translation-ref]
[Trace][cloud-trace] | alpha | [`cloud.google.com/go/trace`][cloud-trace-ref]
[Video Intelligence][cloud-video]| beta | [`cloud.google.com/go/videointelligence/apiv1beta1`][cloud-video-ref] [Video Intelligence][cloud-video]| beta | [`cloud.google.com/go/videointelligence/apiv1beta1`][cloud-video-ref]
[ErrorReporting][cloud-errors] | alpha | [`cloud.google.com/go/errorreporting`][cloud-errors-ref] [Vision][cloud-vision] | stable | [`cloud.google.com/go/vision/apiv1`][cloud-vision-ref]
> **Alpha status**: the API is still being actively developed. As a > **Alpha status**: the API is still being actively developed. As a
@ -480,6 +515,9 @@ for more information.
[cloud-language]: https://cloud.google.com/natural-language [cloud-language]: https://cloud.google.com/natural-language
[cloud-language-ref]: https://godoc.org/cloud.google.com/go/language/apiv1 [cloud-language-ref]: https://godoc.org/cloud.google.com/go/language/apiv1
[cloud-oslogin]: https://cloud.google.com/compute/docs/oslogin/rest
[cloud-oslogin-ref]: https://cloud.google.com/compute/docs/oslogin/rest
[cloud-speech]: https://cloud.google.com/speech [cloud-speech]: https://cloud.google.com/speech
[cloud-speech-ref]: https://godoc.org/cloud.google.com/go/speech/apiv1 [cloud-speech-ref]: https://godoc.org/cloud.google.com/go/speech/apiv1
@ -490,13 +528,19 @@ for more information.
[cloud-translation]: https://cloud.google.com/translation [cloud-translation]: https://cloud.google.com/translation
[cloud-translation-ref]: https://godoc.org/cloud.google.com/go/translation [cloud-translation-ref]: https://godoc.org/cloud.google.com/go/translation
[cloud-trace]: https://cloud.google.com/trace/
[cloud-trace-ref]: https://godoc.org/cloud.google.com/go/trace
[cloud-video]: https://cloud.google.com/video-intelligence/ [cloud-video]: https://cloud.google.com/video-intelligence/
[cloud-video-ref]: https://godoc.org/cloud.google.com/go/videointelligence/apiv1beta1 [cloud-video-ref]: https://godoc.org/cloud.google.com/go/videointelligence/apiv1beta1
[cloud-errors]: https://cloud.google.com/error-reporting/ [cloud-errors]: https://cloud.google.com/error-reporting/
[cloud-errors-ref]: https://godoc.org/cloud.google.com/go/errorreporting [cloud-errors-ref]: https://godoc.org/cloud.google.com/go/errorreporting
[cloud-container]: https://cloud.google.com/containers/
[cloud-container-ref]: https://godoc.org/cloud.google.com/go/container/apiv1
[cloud-debugger]: https://cloud.google.com/debugger/
[cloud-debugger-ref]: https://godoc.org/cloud.google.com/go/debugger/apiv2
[cloud-dlp]: https://cloud.google.com/dlp/
[cloud-dlp-ref]: https://godoc.org/cloud.google.com/go/dlp/apiv2beta1
[default-creds]: https://developers.google.com/identity/protocols/application-default-credentials [default-creds]: https://developers.google.com/identity/protocols/application-default-credentials

63
vendor/cloud.google.com/go/regen-gapic.sh generated vendored Executable file
View File

@ -0,0 +1,63 @@
#!/bin/bash
# This script generates all GAPIC clients in this repo.
# One-time setup:
# cd path/to/googleapis # https://github.com/googleapis/googleapis
# virtualenv env
# . env/bin/activate
# pip install googleapis-artman
# deactivate
#
# Regenerate:
# cd path/to/googleapis
# . env/bin/activate
# $GOPATH/src/cloud.google.com/go/regen-gapic.sh
# deactivate
#
# Being in googleapis directory is important;
# that's where we find YAML files and where artman puts the "artman-genfiles" directory.
#
# NOTE: This script does not generate the "raw" gRPC client found in google.golang.org/genproto.
# To do that, use the regen.sh script in the genproto repo instead.
set -ex
APIS=(
google/cloud/bigquery/datatransfer/artman_bigquerydatatransfer.yaml
google/cloud/dataproc/artman_dataproc_v1.yaml
google/cloud/language/artman_language_v1.yaml
google/cloud/language/artman_language_v1beta2.yaml
google/cloud/oslogin/artman_oslogin_v1beta.yaml
google/cloud/speech/artman_speech_v1.yaml
google/cloud/speech/artman_speech_v1beta1.yaml
google/cloud/videointelligence/artman_videointelligence_v1beta1.yaml
google/cloud/videointelligence/artman_videointelligence_v1beta2.yaml
google/cloud/vision/artman_vision_v1.yaml
google/cloud/vision/artman_vision_v1p1beta1.yaml
google/container/artman_container.yaml
google/devtools/artman_clouddebugger.yaml
google/devtools/clouderrorreporting/artman_errorreporting.yaml
google/devtools/cloudtrace/artman_cloudtrace_v1.yaml
google/devtools/cloudtrace/artman_cloudtrace_v2.yaml
google/firestore/artman_firestore.yaml
google/logging/artman_logging.yaml
google/longrunning/artman_longrunning.yaml
google/monitoring/artman_monitoring.yaml
google/privacy/dlp/artman_dlp.yaml
google/pubsub/artman_pubsub.yaml
google/spanner/admin/database/artman_spanner_admin_database.yaml
google/spanner/admin/instance/artman_spanner_admin_instance.yaml
google/spanner/artman_spanner.yaml
)
for api in "${APIS[@]}"; do
rm -rf artman-genfiles/*
artman2 --config "$api" generate go_gapic
cp -r artman-genfiles/gapi-*/cloud.google.com/go/* $GOPATH/src/cloud.google.com/go/
done
go list cloud.google.com/go/... | grep apiv | xargs go test
go test -short cloud.google.com/go/...
echo "googleapis version: $(git rev-parse HEAD)"

View File

@ -1,3 +1,19 @@
Release v1.12.67 (2018-01-22)
===
### Service Client Updates
* `service/budgets`: Updates service API and documentation
* Add additional costTypes: IncludeDiscount, UseAmortized, to support finer control for different charges included in a cost budget.
Release v1.12.66 (2018-01-19)
===
### Service Client Updates
* `aws/endpoints`: Updated Regions and Endpoints metadata.
* `service/glue`: Updates service API and documentation
* New AWS Glue DataCatalog APIs to manage table versions and a new feature to skip archiving of the old table version when updating table.
* `service/transcribe`: Adds new service
Release v1.12.65 (2018-01-18) Release v1.12.65 (2018-01-18)
=== ===

View File

@ -1044,10 +1044,13 @@ var awsPartition = partition{
Endpoints: endpoints{ Endpoints: endpoints{
"ap-northeast-1": endpoint{}, "ap-northeast-1": endpoint{},
"ap-southeast-1": endpoint{},
"ap-southeast-2": endpoint{},
"eu-central-1": endpoint{}, "eu-central-1": endpoint{},
"eu-west-1": endpoint{}, "eu-west-1": endpoint{},
"us-east-1": endpoint{}, "us-east-1": endpoint{},
"us-east-2": endpoint{}, "us-east-2": endpoint{},
"us-west-1": endpoint{},
"us-west-2": endpoint{}, "us-west-2": endpoint{},
}, },
}, },
@ -1094,10 +1097,11 @@ var awsPartition = partition{
"glue": service{ "glue": service{
Endpoints: endpoints{ Endpoints: endpoints{
"eu-west-1": endpoint{}, "ap-northeast-1": endpoint{},
"us-east-1": endpoint{}, "eu-west-1": endpoint{},
"us-east-2": endpoint{}, "us-east-1": endpoint{},
"us-west-2": endpoint{}, "us-east-2": endpoint{},
"us-west-2": endpoint{},
}, },
}, },
"greengrass": service{ "greengrass": service{
@ -1490,6 +1494,7 @@ var awsPartition = partition{
Endpoints: endpoints{ Endpoints: endpoints{
"eu-west-1": endpoint{}, "eu-west-1": endpoint{},
"us-east-1": endpoint{}, "us-east-1": endpoint{},
"us-east-2": endpoint{},
"us-west-2": endpoint{}, "us-west-2": endpoint{},
}, },
}, },

View File

@ -5,4 +5,4 @@ package aws
const SDKName = "aws-sdk-go" const SDKName = "aws-sdk-go"
// SDKVersion is the version of this SDK // SDKVersion is the version of this SDK
const SDKVersion = "1.12.65" const SDKVersion = "1.12.67"

View File

@ -6,3 +6,4 @@
Icon? Icon?
ehthumbs.db ehthumbs.db
Thumbs.db Thumbs.db
.idea

View File

@ -12,35 +12,57 @@
# Individual Persons # Individual Persons
Aaron Hopkins <go-sql-driver at die.net> Aaron Hopkins <go-sql-driver at die.net>
Achille Roussel <achille.roussel at gmail.com>
Arne Hormann <arnehormann at gmail.com> Arne Hormann <arnehormann at gmail.com>
Asta Xie <xiemengjun at gmail.com>
Bulat Gaifullin <gaifullinbf at gmail.com>
Carlos Nieto <jose.carlos at menteslibres.net> Carlos Nieto <jose.carlos at menteslibres.net>
Chris Moos <chris at tech9computers.com> Chris Moos <chris at tech9computers.com>
Daniel Montoya <dsmontoyam at gmail.com>
Daniel Nichter <nil at codenode.com> Daniel Nichter <nil at codenode.com>
Daniël van Eeden <git at myname.nl> Daniël van Eeden <git at myname.nl>
Dave Protasowski <dprotaso at gmail.com>
DisposaBoy <disposaboy at dby.me> DisposaBoy <disposaboy at dby.me>
Egor Smolyakov <egorsmkv at gmail.com>
Evan Shaw <evan at vendhq.com>
Frederick Mayle <frederickmayle at gmail.com> Frederick Mayle <frederickmayle at gmail.com>
Gustavo Kristic <gkristic at gmail.com> Gustavo Kristic <gkristic at gmail.com>
Hanno Braun <mail at hannobraun.com> Hanno Braun <mail at hannobraun.com>
Henri Yandell <flamefew at gmail.com> Henri Yandell <flamefew at gmail.com>
Hirotaka Yamamoto <ymmt2005 at gmail.com> Hirotaka Yamamoto <ymmt2005 at gmail.com>
ICHINOSE Shogo <shogo82148 at gmail.com>
INADA Naoki <songofacandy at gmail.com> INADA Naoki <songofacandy at gmail.com>
Jacek Szwec <szwec.jacek at gmail.com>
James Harr <james.harr at gmail.com> James Harr <james.harr at gmail.com>
Jeff Hodges <jeff at somethingsimilar.com>
Jeffrey Charles <jeffreycharles at gmail.com>
Jian Zhen <zhenjl at gmail.com> Jian Zhen <zhenjl at gmail.com>
Joshua Prunier <joshua.prunier at gmail.com> Joshua Prunier <joshua.prunier at gmail.com>
Julien Lefevre <julien.lefevr at gmail.com> Julien Lefevre <julien.lefevr at gmail.com>
Julien Schmidt <go-sql-driver at julienschmidt.com> Julien Schmidt <go-sql-driver at julienschmidt.com>
Justin Li <jli at j-li.net>
Justin Nuß <nuss.justin at gmail.com>
Kamil Dziedzic <kamil at klecza.pl> Kamil Dziedzic <kamil at klecza.pl>
Kevin Malachowski <kevin at chowski.com> Kevin Malachowski <kevin at chowski.com>
Kieron Woodhouse <kieron.woodhouse at infosum.com>
Lennart Rudolph <lrudolph at hmc.edu> Lennart Rudolph <lrudolph at hmc.edu>
Leonardo YongUk Kim <dalinaum at gmail.com> Leonardo YongUk Kim <dalinaum at gmail.com>
Linh Tran Tuan <linhduonggnu at gmail.com>
Lion Yang <lion at aosc.xyz>
Luca Looz <luca.looz92 at gmail.com> Luca Looz <luca.looz92 at gmail.com>
Lucas Liu <extrafliu at gmail.com> Lucas Liu <extrafliu at gmail.com>
Luke Scott <luke at webconnex.com> Luke Scott <luke at webconnex.com>
Maciej Zimnoch <maciej.zimnoch@codilime.com>
Michael Woolnough <michael.woolnough at gmail.com> Michael Woolnough <michael.woolnough at gmail.com>
Nicola Peduzzi <thenikso at gmail.com> Nicola Peduzzi <thenikso at gmail.com>
Olivier Mengué <dolmen at cpan.org> Olivier Mengué <dolmen at cpan.org>
oscarzhao <oscarzhaosl at gmail.com>
Paul Bonser <misterpib at gmail.com> Paul Bonser <misterpib at gmail.com>
Peter Schultz <peter.schultz at classmarkets.com>
Rebecca Chin <rchin at pivotal.io>
Runrioter Wung <runrioter at gmail.com> Runrioter Wung <runrioter at gmail.com>
Robert Russell <robert at rrbrussell.com>
Shuode Li <elemount at qq.com>
Soroush Pour <me at soroushjp.com> Soroush Pour <me at soroushjp.com>
Stan Putrya <root.vagner at gmail.com> Stan Putrya <root.vagner at gmail.com>
Stanley Gunawan <gunawan.stanley at gmail.com> Stanley Gunawan <gunawan.stanley at gmail.com>
@ -52,5 +74,9 @@ Zhenye Xie <xiezhenye at gmail.com>
# Organizations # Organizations
Barracuda Networks, Inc. Barracuda Networks, Inc.
Counting Ltd.
Google Inc. Google Inc.
InfoSum Ltd.
Keybase Inc.
Pivotal Inc.
Stripe Inc. Stripe Inc.

View File

@ -1,6 +1,6 @@
# Go-MySQL-Driver # Go-MySQL-Driver
A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) package A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) package
![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin") ![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin")
@ -15,6 +15,9 @@ A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) packa
* [Address](#address) * [Address](#address)
* [Parameters](#parameters) * [Parameters](#parameters)
* [Examples](#examples) * [Examples](#examples)
* [Connection pool and timeouts](#connection-pool-and-timeouts)
* [context.Context Support](#contextcontext-support)
* [ColumnType Support](#columntype-support)
* [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support) * [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support)
* [time.Time support](#timetime-support) * [time.Time support](#timetime-support)
* [Unicode support](#unicode-support) * [Unicode support](#unicode-support)
@ -26,31 +29,31 @@ A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) packa
## Features ## Features
* Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance") * Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance")
* Native Go implementation. No C-bindings, just pure Go * Native Go implementation. No C-bindings, just pure Go
* Connections over TCP/IPv4, TCP/IPv6, Unix domain sockets or [custom protocols](http://godoc.org/github.com/go-sql-driver/mysql#DialFunc) * Connections over TCP/IPv4, TCP/IPv6, Unix domain sockets or [custom protocols](https://godoc.org/github.com/go-sql-driver/mysql#DialFunc)
* Automatic handling of broken connections * Automatic handling of broken connections
* Automatic Connection Pooling *(by database/sql package)* * Automatic Connection Pooling *(by database/sql package)*
* Supports queries larger than 16MB * Supports queries larger than 16MB
* Full [`sql.RawBytes`](http://golang.org/pkg/database/sql/#RawBytes) support. * Full [`sql.RawBytes`](https://golang.org/pkg/database/sql/#RawBytes) support.
* Intelligent `LONG DATA` handling in prepared statements * Intelligent `LONG DATA` handling in prepared statements
* Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support * Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support
* Optional `time.Time` parsing * Optional `time.Time` parsing
* Optional placeholder interpolation * Optional placeholder interpolation
## Requirements ## Requirements
* Go 1.2 or higher * Go 1.7 or higher. We aim to support the 3 latest versions of Go.
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+) * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
--------------------------------------- ---------------------------------------
## Installation ## Installation
Simple install the package to your [$GOPATH](http://code.google.com/p/go-wiki/wiki/GOPATH "GOPATH") with the [go tool](http://golang.org/cmd/go/ "go command") from shell: Simple install the package to your [$GOPATH](https://github.com/golang/go/wiki/GOPATH "GOPATH") with the [go tool](https://golang.org/cmd/go/ "go command") from shell:
```bash ```bash
$ go get github.com/go-sql-driver/mysql $ go get -u github.com/go-sql-driver/mysql
``` ```
Make sure [Git is installed](http://git-scm.com/downloads) on your machine and in your system's `PATH`. Make sure [Git is installed](https://git-scm.com/downloads) on your machine and in your system's `PATH`.
## Usage ## Usage
_Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](http://golang.org/pkg/database/sql) API then. _Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](https://golang.org/pkg/database/sql/) API then.
Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name) as `dataSourceName`: Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name) as `dataSourceName`:
```go ```go
@ -95,13 +98,14 @@ Alternatively, [Config.FormatDSN](https://godoc.org/github.com/go-sql-driver/mys
Passwords can consist of any character. Escaping is **not** necessary. Passwords can consist of any character. Escaping is **not** necessary.
#### Protocol #### Protocol
See [net.Dial](http://golang.org/pkg/net/#Dial) for more information which networks are available. See [net.Dial](https://golang.org/pkg/net/#Dial) for more information which networks are available.
In general you should use an Unix domain socket if available and TCP otherwise for best performance. In general you should use an Unix domain socket if available and TCP otherwise for best performance.
#### Address #### Address
For TCP and UDP networks, addresses have the form `host:port`. For TCP and UDP networks, addresses have the form `host[:port]`.
If `port` is omitted, the default port will be used.
If `host` is a literal IPv6 address, it must be enclosed in square brackets. If `host` is a literal IPv6 address, it must be enclosed in square brackets.
The functions [net.JoinHostPort](http://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](http://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form. The functions [net.JoinHostPort](https://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](https://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`. For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`.
@ -136,9 +140,9 @@ Default: false
``` ```
Type: bool Type: bool
Valid Values: true, false Valid Values: true, false
Default: false Default: true
``` ```
`allowNativePasswords=true` allows the usage of the mysql native password method. `allowNativePasswords=false` disallows the usage of MySQL native password method.
##### `allowOldPasswords` ##### `allowOldPasswords`
@ -220,19 +224,19 @@ Valid Values: <escaped name>
Default: UTC Default: UTC
``` ```
Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details. Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](https://golang.org/pkg/time/#LoadLocation) for details.
Note that this sets the location for time.Time values but does not change MySQL's [time_zone setting](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html). For that see the [time_zone system variable](#system-variables), which can also be set as a DSN parameter. Note that this sets the location for time.Time values but does not change MySQL's [time_zone setting](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html). For that see the [time_zone system variable](#system-variables), which can also be set as a DSN parameter.
Please keep in mind, that param values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`. Please keep in mind, that param values must be [url.QueryEscape](https://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`.
##### `maxAllowedPacket` ##### `maxAllowedPacket`
``` ```
Type: decimal number Type: decimal number
Default: 0 Default: 4194304
``` ```
Max packet size allowed in bytes. Use `maxAllowedPacket=0` to automatically fetch the `max_allowed_packet` variable from server. Max packet size allowed in bytes. The default value is 4 MiB and should be adjusted to match the server settings. `maxAllowedPacket=0` can be used to automatically fetch the `max_allowed_packet` variable from server *on every connection*.
##### `multiStatements` ##### `multiStatements`
@ -260,13 +264,13 @@ Default: false
##### `readTimeout` ##### `readTimeout`
``` ```
Type: decimal number Type: duration
Default: 0 Default: 0
``` ```
I/O read timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. I/O read timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
##### `strict` ##### `rejectReadOnly`
``` ```
Type: bool Type: bool
@ -274,20 +278,37 @@ Valid Values: true, false
Default: false Default: false
``` ```
`strict=true` enables a driver-side strict mode in which MySQL warnings are treated as errors. This mode should not be used in production as it may lead to data corruption in certain situations.
A server-side strict mode, which is safe for production use, can be set via the [`sql_mode`](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html) system variable. `rejectReadOnly=true` causes the driver to reject read-only connections. This
is for a possible race condition during an automatic failover, where the mysql
client gets connected to a read-only replica after the failover.
Note that this should be a fairly rare case, as an automatic failover normally
happens when the primary is down, and the race condition shouldn't happen
unless it comes back up online as soon as the failover is kicked off. On the
other hand, when this happens, a MySQL application can get stuck on a
read-only connection until restarted. It is however fairly easy to reproduce,
for example, using a manual failover on AWS Aurora's MySQL-compatible cluster.
If you are not relying on read-only transactions to reject writes that aren't
supposed to happen, setting this on some MySQL providers (such as AWS Aurora)
is safer for failovers.
Note that ERROR 1290 can be returned for a `read-only` server and this option will
cause a retry for that error. However the same error number is used for some
other cases. You should ensure your application will never cause an ERROR 1290
except for `read-only` mode when enabling this option.
By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_notes) to ignore notes.
##### `timeout` ##### `timeout`
``` ```
Type: decimal number Type: duration
Default: OS default Default: OS default
``` ```
*Driver* side connection timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout). Timeout for establishing connections, aka dial timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
##### `tls` ##### `tls`
@ -297,16 +318,17 @@ Valid Values: true, false, skip-verify, <name>
Default: false Default: false
``` ```
`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](http://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig). `tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
##### `writeTimeout` ##### `writeTimeout`
``` ```
Type: decimal number Type: duration
Default: 0 Default: 0
``` ```
I/O write timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. I/O write timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
##### System Variables ##### System Variables
@ -317,9 +339,9 @@ Any other parameters are interpreted as system variables:
* `<string_var>=%27<value>%27`: `SET <string_var>='<value>'` * `<string_var>=%27<value>%27`: `SET <string_var>='<value>'`
Rules: Rules:
* The values for string variables must be quoted with ' * The values for string variables must be quoted with `'`.
* The values must also be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed! * The values must also be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!
(which implies values of string variables must be wrapped with `%27`) (which implies values of string variables must be wrapped with `%27`).
Examples: Examples:
* `autocommit=1`: `SET autocommit=1` * `autocommit=1`: `SET autocommit=1`
@ -380,6 +402,18 @@ No Database preselected:
user:password@/ user:password@/
``` ```
### Connection pool and timeouts
The connection pool is managed by Go's database/sql package. For details on how to configure the size of the pool and how long connections stay in the pool see `*DB.SetMaxOpenConns`, `*DB.SetMaxIdleConns`, and `*DB.SetConnMaxLifetime` in the [database/sql documentation](https://golang.org/pkg/database/sql/). The read, write, and dial timeouts for each individual connection are configured with the DSN parameters [`readTimeout`](#readtimeout), [`writeTimeout`](#writetimeout), and [`timeout`](#timeout), respectively.
## `ColumnType` Support
This driver supports the [`ColumnType` interface](https://golang.org/pkg/database/sql/#ColumnType) introduced in Go 1.8, with the exception of [`ColumnType.Length()`](https://golang.org/pkg/database/sql/#ColumnType.Length), which is currently not supported.
## `context.Context` Support
Go 1.8 added `database/sql` support for `context.Context`. This driver supports query timeouts and cancellation via contexts.
See [context support in the database/sql package](https://golang.org/doc/go1.8#database_sql) for more details.
### `LOAD DATA LOCAL INFILE` support ### `LOAD DATA LOCAL INFILE` support
For this feature you need direct access to the package. Therefore you must change the import path (no `_`): For this feature you need direct access to the package. Therefore you must change the import path (no `_`):
```go ```go
@ -390,17 +424,17 @@ Files must be whitelisted by registering them with `mysql.RegisterLocalFile(file
To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore. To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore.
See the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details. See the [godoc of Go-MySQL-Driver](https://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
### `time.Time` support ### `time.Time` support
The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your programm. The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your program.
However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](http://golang.org/pkg/time/#Location) with the `loc` DSN parameter. However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](https://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
**Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes). **Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
Alternatively you can use the [`NullTime`](http://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`. Alternatively you can use the [`NullTime`](https://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`.
### Unicode support ### Unicode support
@ -412,7 +446,6 @@ Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAM
See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support. See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
## Testing / Development ## Testing / Development
To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details. To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
@ -431,13 +464,13 @@ Mozilla summarizes the license scope as follows:
That means: That means:
* You can **use** the **unchanged** source code both in private and commercially * You can **use** the **unchanged** source code both in private and commercially.
* When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0) * When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0).
* You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged** * You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**.
Please read the [MPL 2.0 FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html) if you have further questions regarding the license. Please read the [MPL 2.0 FAQ](https://www.mozilla.org/en-US/MPL/2.0/FAQ/) if you have further questions regarding the license.
You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE) You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE).
![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow") ![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow")

View File

@ -11,7 +11,7 @@
package mysql package mysql
import ( import (
"appengine/cloudsql" "google.golang.org/appengine/cloudsql"
) )
func init() { func init() {

View File

@ -9,6 +9,7 @@
package mysql package mysql
const defaultCollation = "utf8_general_ci" const defaultCollation = "utf8_general_ci"
const binaryCollation = "binary"
// A list of available collations mapped to the internal ID. // A list of available collations mapped to the internal ID.
// To update this map use the following MySQL query: // To update this map use the following MySQL query:

View File

@ -10,12 +10,23 @@ package mysql
import ( import (
"database/sql/driver" "database/sql/driver"
"io"
"net" "net"
"strconv" "strconv"
"strings" "strings"
"time" "time"
) )
// a copy of context.Context for Go 1.7 and earlier
type mysqlContext interface {
Done() <-chan struct{}
Err() error
// defined in context.Context, but not used in this driver:
// Deadline() (deadline time.Time, ok bool)
// Value(key interface{}) interface{}
}
type mysqlConn struct { type mysqlConn struct {
buf buffer buf buffer
netConn net.Conn netConn net.Conn
@ -29,7 +40,14 @@ type mysqlConn struct {
status statusFlag status statusFlag
sequence uint8 sequence uint8
parseTime bool parseTime bool
strict bool
// for context support (Go 1.8+)
watching bool
watcher chan<- mysqlContext
closech chan struct{}
finished chan<- struct{}
canceled atomicError // set non-nil if conn is canceled
closed atomicBool // set when conn is closed, before closech is closed
} }
// Handles parameters set in DSN after the connection is established // Handles parameters set in DSN after the connection is established
@ -62,22 +80,41 @@ func (mc *mysqlConn) handleParams() (err error) {
return return
} }
func (mc *mysqlConn) markBadConn(err error) error {
if mc == nil {
return err
}
if err != errBadConnNoWrite {
return err
}
return driver.ErrBadConn
}
func (mc *mysqlConn) Begin() (driver.Tx, error) { func (mc *mysqlConn) Begin() (driver.Tx, error) {
if mc.netConn == nil { return mc.begin(false)
}
func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
err := mc.exec("START TRANSACTION") var q string
if readOnly {
q = "START TRANSACTION READ ONLY"
} else {
q = "START TRANSACTION"
}
err := mc.exec(q)
if err == nil { if err == nil {
return &mysqlTx{mc}, err return &mysqlTx{mc}, err
} }
return nil, mc.markBadConn(err)
return nil, err
} }
func (mc *mysqlConn) Close() (err error) { func (mc *mysqlConn) Close() (err error) {
// Makes Close idempotent // Makes Close idempotent
if mc.netConn != nil { if !mc.closed.IsSet() {
err = mc.writeCommandPacket(comQuit) err = mc.writeCommandPacket(comQuit)
} }
@ -91,26 +128,39 @@ func (mc *mysqlConn) Close() (err error) {
// is called before auth or on auth failure because MySQL will have already // is called before auth or on auth failure because MySQL will have already
// closed the network connection. // closed the network connection.
func (mc *mysqlConn) cleanup() { func (mc *mysqlConn) cleanup() {
// Makes cleanup idempotent if !mc.closed.TrySet(true) {
if mc.netConn != nil { return
if err := mc.netConn.Close(); err != nil {
errLog.Print(err)
}
mc.netConn = nil
} }
mc.cfg = nil
mc.buf.nc = nil // Makes cleanup idempotent
close(mc.closech)
if mc.netConn == nil {
return
}
if err := mc.netConn.Close(); err != nil {
errLog.Print(err)
}
}
func (mc *mysqlConn) error() error {
if mc.closed.IsSet() {
if err := mc.canceled.Value(); err != nil {
return err
}
return ErrInvalidConn
}
return nil
} }
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) { func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
if mc.netConn == nil { if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
// Send command // Send command
err := mc.writeCommandPacketStr(comStmtPrepare, query) err := mc.writeCommandPacketStr(comStmtPrepare, query)
if err != nil { if err != nil {
return nil, err return nil, mc.markBadConn(err)
} }
stmt := &mysqlStmt{ stmt := &mysqlStmt{
@ -144,7 +194,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
if buf == nil { if buf == nil {
// can not take the buffer. Something must be wrong with the connection // can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(ErrBusyBuffer)
return "", driver.ErrBadConn return "", ErrInvalidConn
} }
buf = buf[:0] buf = buf[:0]
argPos := 0 argPos := 0
@ -257,7 +307,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
} }
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) { func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
if mc.netConn == nil { if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
@ -271,7 +321,6 @@ func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, err
return nil, err return nil, err
} }
query = prepared query = prepared
args = nil
} }
mc.affectedRows = 0 mc.affectedRows = 0
mc.insertId = 0 mc.insertId = 0
@ -283,32 +332,43 @@ func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, err
insertId: int64(mc.insertId), insertId: int64(mc.insertId),
}, err }, err
} }
return nil, err return nil, mc.markBadConn(err)
} }
// Internal function to execute commands // Internal function to execute commands
func (mc *mysqlConn) exec(query string) error { func (mc *mysqlConn) exec(query string) error {
// Send command // Send command
err := mc.writeCommandPacketStr(comQuery, query) if err := mc.writeCommandPacketStr(comQuery, query); err != nil {
if err != nil { return mc.markBadConn(err)
return err
} }
// Read Result // Read Result
resLen, err := mc.readResultSetHeaderPacket() resLen, err := mc.readResultSetHeaderPacket()
if err == nil && resLen > 0 { if err != nil {
if err = mc.readUntilEOF(); err != nil { return err
}
if resLen > 0 {
// columns
if err := mc.readUntilEOF(); err != nil {
return err return err
} }
err = mc.readUntilEOF() // rows
if err := mc.readUntilEOF(); err != nil {
return err
}
} }
return err return mc.discardResults()
} }
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) { func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
if mc.netConn == nil { return mc.query(query, args)
}
func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
@ -322,7 +382,6 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
return nil, err return nil, err
} }
query = prepared query = prepared
args = nil
} }
// Send command // Send command
err := mc.writeCommandPacketStr(comQuery, query) err := mc.writeCommandPacketStr(comQuery, query)
@ -335,15 +394,22 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
rows.mc = mc rows.mc = mc
if resLen == 0 { if resLen == 0 {
// no columns, no more data rows.rs.done = true
return emptyRows{}, nil
switch err := rows.NextResultSet(); err {
case nil, io.EOF:
return rows, nil
default:
return nil, err
}
} }
// Columns // Columns
rows.columns, err = mc.readColumns(resLen) rows.rs.columns, err = mc.readColumns(resLen)
return rows, err return rows, err
} }
} }
return nil, err return nil, mc.markBadConn(err)
} }
// Gets the value of the given MySQL System Variable // Gets the value of the given MySQL System Variable
@ -359,7 +425,7 @@ func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
if err == nil { if err == nil {
rows := new(textRows) rows := new(textRows)
rows.mc = mc rows.mc = mc
rows.columns = []mysqlField{{fieldType: fieldTypeVarChar}} rows.rs.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
if resLen > 0 { if resLen > 0 {
// Columns // Columns
@ -375,3 +441,21 @@ func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
} }
return nil, err return nil, err
} }
// finish is called when the query has canceled.
func (mc *mysqlConn) cancel(err error) {
mc.canceled.Set(err)
mc.cleanup()
}
// finish is called when the query has succeeded.
func (mc *mysqlConn) finish() {
if !mc.watching || mc.finished == nil {
return
}
select {
case mc.finished <- struct{}{}:
mc.watching = false
case <-mc.closech:
}
}

View File

@ -0,0 +1,202 @@
// 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/.
// +build go1.8
package mysql
import (
"context"
"database/sql"
"database/sql/driver"
)
// Ping implements driver.Pinger interface
func (mc *mysqlConn) Ping(ctx context.Context) error {
if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return driver.ErrBadConn
}
if err := mc.watchCancel(ctx); err != nil {
return err
}
defer mc.finish()
if err := mc.writeCommandPacket(comPing); err != nil {
return err
}
if _, err := mc.readResultOK(); err != nil {
return err
}
return nil
}
// BeginTx implements driver.ConnBeginTx interface
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
defer mc.finish()
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
level, err := mapIsolationLevel(opts.Isolation)
if err != nil {
return nil, err
}
err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
if err != nil {
return nil, err
}
}
return mc.begin(opts.ReadOnly)
}
func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
rows, err := mc.query(query, dargs)
if err != nil {
mc.finish()
return nil, err
}
rows.finish = mc.finish
return rows, err
}
func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
defer mc.finish()
return mc.Exec(query, dargs)
}
func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
stmt, err := mc.Prepare(query)
mc.finish()
if err != nil {
return nil, err
}
select {
default:
case <-ctx.Done():
stmt.Close()
return nil, ctx.Err()
}
return stmt, nil
}
func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := stmt.mc.watchCancel(ctx); err != nil {
return nil, err
}
rows, err := stmt.query(dargs)
if err != nil {
stmt.mc.finish()
return nil, err
}
rows.finish = stmt.mc.finish
return rows, err
}
func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := stmt.mc.watchCancel(ctx); err != nil {
return nil, err
}
defer stmt.mc.finish()
return stmt.Exec(dargs)
}
func (mc *mysqlConn) watchCancel(ctx context.Context) error {
if mc.watching {
// Reach here if canceled,
// so the connection is already invalid
mc.cleanup()
return nil
}
if ctx.Done() == nil {
return nil
}
mc.watching = true
select {
default:
case <-ctx.Done():
return ctx.Err()
}
if mc.watcher == nil {
return nil
}
mc.watcher <- ctx
return nil
}
func (mc *mysqlConn) startWatcher() {
watcher := make(chan mysqlContext, 1)
mc.watcher = watcher
finished := make(chan struct{})
mc.finished = finished
go func() {
for {
var ctx mysqlContext
select {
case ctx = <-watcher:
case <-mc.closech:
return
}
select {
case <-ctx.Done():
mc.cancel(ctx.Err())
case <-finished:
case <-mc.closech:
return
}
}
}()
}
func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
nv.Value, err = converter{}.ConvertValue(nv.Value)
return
}

View File

@ -9,7 +9,8 @@
package mysql package mysql
const ( const (
minProtocolVersion byte = 10 defaultMaxAllowedPacket = 4 << 20 // 4 MiB
minProtocolVersion = 10
maxPacketSize = 1<<24 - 1 maxPacketSize = 1<<24 - 1
timeFormat = "2006-01-02 15:04:05.999999" timeFormat = "2006-01-02 15:04:05.999999"
) )
@ -87,8 +88,10 @@ const (
) )
// https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType // https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType
type fieldType byte
const ( const (
fieldTypeDecimal byte = iota fieldTypeDecimal fieldType = iota
fieldTypeTiny fieldTypeTiny
fieldTypeShort fieldTypeShort
fieldTypeLong fieldTypeLong
@ -107,7 +110,7 @@ const (
fieldTypeBit fieldTypeBit
) )
const ( const (
fieldTypeJSON byte = iota + 0xf5 fieldTypeJSON fieldType = iota + 0xf5
fieldTypeNewDecimal fieldTypeNewDecimal
fieldTypeEnum fieldTypeEnum
fieldTypeSet fieldTypeSet

View File

@ -4,7 +4,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file, // 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/. // You can obtain one at http://mozilla.org/MPL/2.0/.
// Package mysql provides a MySQL driver for Go's database/sql package // Package mysql provides a MySQL driver for Go's database/sql package.
// //
// The driver should be used via the database/sql package: // The driver should be used via the database/sql package:
// //
@ -22,6 +22,11 @@ import (
"net" "net"
) )
// watcher interface is used for context support (From Go 1.8)
type watcher interface {
startWatcher()
}
// MySQLDriver is exported to make the driver directly accessible. // MySQLDriver is exported to make the driver directly accessible.
// In general the driver is used via the database/sql package. // In general the driver is used via the database/sql package.
type MySQLDriver struct{} type MySQLDriver struct{}
@ -52,13 +57,13 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
mc := &mysqlConn{ mc := &mysqlConn{
maxAllowedPacket: maxPacketSize, maxAllowedPacket: maxPacketSize,
maxWriteSize: maxPacketSize - 1, maxWriteSize: maxPacketSize - 1,
closech: make(chan struct{}),
} }
mc.cfg, err = ParseDSN(dsn) mc.cfg, err = ParseDSN(dsn)
if err != nil { if err != nil {
return nil, err return nil, err
} }
mc.parseTime = mc.cfg.ParseTime mc.parseTime = mc.cfg.ParseTime
mc.strict = mc.cfg.Strict
// Connect to Server // Connect to Server
if dial, ok := dials[mc.cfg.Net]; ok { if dial, ok := dials[mc.cfg.Net]; ok {
@ -81,6 +86,11 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
} }
} }
// Call startWatcher for context support (From Go 1.8)
if s, ok := interface{}(mc).(watcher); ok {
s.startWatcher()
}
mc.buf = newBuffer(mc.netConn) mc.buf = newBuffer(mc.netConn)
// Set I/O timeouts // Set I/O timeouts

View File

@ -15,6 +15,7 @@ import (
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
"sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -27,7 +28,9 @@ var (
errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations") errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations")
) )
// Config is a configuration parsed from a DSN string // Config is a configuration parsed from a DSN string.
// If a new Config is created instead of being parsed from a DSN string,
// the NewConfig function should be used, which sets default values.
type Config struct { type Config struct {
User string // Username User string // Username
Passwd string // Password (requires User) Passwd string // Password (requires User)
@ -53,7 +56,54 @@ type Config struct {
InterpolateParams bool // Interpolate placeholders into query string InterpolateParams bool // Interpolate placeholders into query string
MultiStatements bool // Allow multiple statements in one query MultiStatements bool // Allow multiple statements in one query
ParseTime bool // Parse time values to time.Time ParseTime bool // Parse time values to time.Time
Strict bool // Return warnings as errors RejectReadOnly bool // Reject read-only connections
}
// NewConfig creates a new Config and sets default values.
func NewConfig() *Config {
return &Config{
Collation: defaultCollation,
Loc: time.UTC,
MaxAllowedPacket: defaultMaxAllowedPacket,
AllowNativePasswords: true,
}
}
func (cfg *Config) normalize() error {
if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
return errInvalidDSNUnsafeCollation
}
// Set default network if empty
if cfg.Net == "" {
cfg.Net = "tcp"
}
// Set default address if empty
if cfg.Addr == "" {
switch cfg.Net {
case "tcp":
cfg.Addr = "127.0.0.1:3306"
case "unix":
cfg.Addr = "/tmp/mysql.sock"
default:
return errors.New("default addr for network '" + cfg.Net + "' unknown")
}
} else if cfg.Net == "tcp" {
cfg.Addr = ensureHavePort(cfg.Addr)
}
if cfg.tls != nil {
if cfg.tls.ServerName == "" && !cfg.tls.InsecureSkipVerify {
host, _, err := net.SplitHostPort(cfg.Addr)
if err == nil {
cfg.tls.ServerName = host
}
}
}
return nil
} }
// FormatDSN formats the given Config into a DSN string which can be passed to // FormatDSN formats the given Config into a DSN string which can be passed to
@ -102,12 +152,12 @@ func (cfg *Config) FormatDSN() string {
} }
} }
if cfg.AllowNativePasswords { if !cfg.AllowNativePasswords {
if hasParam { if hasParam {
buf.WriteString("&allowNativePasswords=true") buf.WriteString("&allowNativePasswords=false")
} else { } else {
hasParam = true hasParam = true
buf.WriteString("?allowNativePasswords=true") buf.WriteString("?allowNativePasswords=false")
} }
} }
@ -195,12 +245,12 @@ func (cfg *Config) FormatDSN() string {
buf.WriteString(cfg.ReadTimeout.String()) buf.WriteString(cfg.ReadTimeout.String())
} }
if cfg.Strict { if cfg.RejectReadOnly {
if hasParam { if hasParam {
buf.WriteString("&strict=true") buf.WriteString("&rejectReadOnly=true")
} else { } else {
hasParam = true hasParam = true
buf.WriteString("?strict=true") buf.WriteString("?rejectReadOnly=true")
} }
} }
@ -234,7 +284,7 @@ func (cfg *Config) FormatDSN() string {
buf.WriteString(cfg.WriteTimeout.String()) buf.WriteString(cfg.WriteTimeout.String())
} }
if cfg.MaxAllowedPacket > 0 { if cfg.MaxAllowedPacket != defaultMaxAllowedPacket {
if hasParam { if hasParam {
buf.WriteString("&maxAllowedPacket=") buf.WriteString("&maxAllowedPacket=")
} else { } else {
@ -247,7 +297,12 @@ func (cfg *Config) FormatDSN() string {
// other params // other params
if cfg.Params != nil { if cfg.Params != nil {
for param, value := range cfg.Params { var params []string
for param := range cfg.Params {
params = append(params, param)
}
sort.Strings(params)
for _, param := range params {
if hasParam { if hasParam {
buf.WriteByte('&') buf.WriteByte('&')
} else { } else {
@ -257,7 +312,7 @@ func (cfg *Config) FormatDSN() string {
buf.WriteString(param) buf.WriteString(param)
buf.WriteByte('=') buf.WriteByte('=')
buf.WriteString(url.QueryEscape(value)) buf.WriteString(url.QueryEscape(cfg.Params[param]))
} }
} }
@ -267,10 +322,7 @@ func (cfg *Config) FormatDSN() string {
// ParseDSN parses the DSN string to a Config // ParseDSN parses the DSN string to a Config
func ParseDSN(dsn string) (cfg *Config, err error) { func ParseDSN(dsn string) (cfg *Config, err error) {
// New config with some default values // New config with some default values
cfg = &Config{ cfg = NewConfig()
Loc: time.UTC,
Collation: defaultCollation,
}
// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN] // [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
// Find the last '/' (since the password or the net addr might contain a '/') // Find the last '/' (since the password or the net addr might contain a '/')
@ -338,28 +390,9 @@ func ParseDSN(dsn string) (cfg *Config, err error) {
return nil, errInvalidDSNNoSlash return nil, errInvalidDSNNoSlash
} }
if cfg.InterpolateParams && unsafeCollations[cfg.Collation] { if err = cfg.normalize(); err != nil {
return nil, errInvalidDSNUnsafeCollation return nil, err
} }
// 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 return
} }
@ -374,7 +407,6 @@ func parseDSNParams(cfg *Config, params string) (err error) {
// cfg params // cfg params
switch value := param[1]; param[0] { switch value := param[1]; param[0] {
// Disable INFILE whitelist / enable all files // Disable INFILE whitelist / enable all files
case "allowAllFiles": case "allowAllFiles":
var isBool bool var isBool bool
@ -472,14 +504,18 @@ func parseDSNParams(cfg *Config, params string) (err error) {
return return
} }
// Strict mode // Reject read-only connections
case "strict": case "rejectReadOnly":
var isBool bool var isBool bool
cfg.Strict, isBool = readBool(value) cfg.RejectReadOnly, isBool = readBool(value)
if !isBool { if !isBool {
return errors.New("invalid bool value: " + value) return errors.New("invalid bool value: " + value)
} }
// Strict mode
case "strict":
panic("strict mode has been removed. See https://github.com/go-sql-driver/mysql/wiki/strict-mode")
// Dial Timeout // Dial Timeout
case "timeout": case "timeout":
cfg.Timeout, err = time.ParseDuration(value) cfg.Timeout, err = time.ParseDuration(value)
@ -506,14 +542,7 @@ func parseDSNParams(cfg *Config, params string) (err error) {
return fmt.Errorf("invalid value for TLS config name: %v", err) return fmt.Errorf("invalid value for TLS config name: %v", err)
} }
if tlsConfig, ok := tlsConfigRegister[name]; ok { if tlsConfig := getTLSConfigClone(name); tlsConfig != nil {
if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
host, _, err := net.SplitHostPort(cfg.Addr)
if err == nil {
tlsConfig.ServerName = host
}
}
cfg.TLSConfig = name cfg.TLSConfig = name
cfg.tls = tlsConfig cfg.tls = tlsConfig
} else { } else {
@ -546,3 +575,10 @@ func parseDSNParams(cfg *Config, params string) (err error) {
return return
} }
func ensureHavePort(addr string) string {
if _, _, err := net.SplitHostPort(addr); err != nil {
return net.JoinHostPort(addr, "3306")
}
return addr
}

View File

@ -9,10 +9,8 @@
package mysql package mysql
import ( import (
"database/sql/driver"
"errors" "errors"
"fmt" "fmt"
"io"
"log" "log"
"os" "os"
) )
@ -31,6 +29,12 @@ var (
ErrPktSyncMul = errors.New("commands out of sync. Did you run multiple statements at once?") 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") ErrPktTooLarge = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server")
ErrBusyBuffer = errors.New("busy buffer") ErrBusyBuffer = errors.New("busy buffer")
// errBadConnNoWrite is used for connection errors where nothing was sent to the database yet.
// If this happens first in a function starting a database interaction, it should be replaced by driver.ErrBadConn
// to trigger a resend.
// See https://github.com/go-sql-driver/mysql/pull/302
errBadConnNoWrite = errors.New("bad connection")
) )
var errLog = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile)) var errLog = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))
@ -59,74 +63,3 @@ type MySQLError struct {
func (me *MySQLError) Error() string { func (me *MySQLError) Error() string {
return fmt.Sprintf("Error %d: %s", me.Number, me.Message) 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
}
}
}

194
vendor/github.com/go-sql-driver/mysql/fields.go generated vendored Normal file
View File

@ -0,0 +1,194 @@
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"database/sql"
"reflect"
)
func (mf *mysqlField) typeDatabaseName() string {
switch mf.fieldType {
case fieldTypeBit:
return "BIT"
case fieldTypeBLOB:
if mf.charSet != collations[binaryCollation] {
return "TEXT"
}
return "BLOB"
case fieldTypeDate:
return "DATE"
case fieldTypeDateTime:
return "DATETIME"
case fieldTypeDecimal:
return "DECIMAL"
case fieldTypeDouble:
return "DOUBLE"
case fieldTypeEnum:
return "ENUM"
case fieldTypeFloat:
return "FLOAT"
case fieldTypeGeometry:
return "GEOMETRY"
case fieldTypeInt24:
return "MEDIUMINT"
case fieldTypeJSON:
return "JSON"
case fieldTypeLong:
return "INT"
case fieldTypeLongBLOB:
if mf.charSet != collations[binaryCollation] {
return "LONGTEXT"
}
return "LONGBLOB"
case fieldTypeLongLong:
return "BIGINT"
case fieldTypeMediumBLOB:
if mf.charSet != collations[binaryCollation] {
return "MEDIUMTEXT"
}
return "MEDIUMBLOB"
case fieldTypeNewDate:
return "DATE"
case fieldTypeNewDecimal:
return "DECIMAL"
case fieldTypeNULL:
return "NULL"
case fieldTypeSet:
return "SET"
case fieldTypeShort:
return "SMALLINT"
case fieldTypeString:
if mf.charSet == collations[binaryCollation] {
return "BINARY"
}
return "CHAR"
case fieldTypeTime:
return "TIME"
case fieldTypeTimestamp:
return "TIMESTAMP"
case fieldTypeTiny:
return "TINYINT"
case fieldTypeTinyBLOB:
if mf.charSet != collations[binaryCollation] {
return "TINYTEXT"
}
return "TINYBLOB"
case fieldTypeVarChar:
if mf.charSet == collations[binaryCollation] {
return "VARBINARY"
}
return "VARCHAR"
case fieldTypeVarString:
if mf.charSet == collations[binaryCollation] {
return "VARBINARY"
}
return "VARCHAR"
case fieldTypeYear:
return "YEAR"
default:
return ""
}
}
var (
scanTypeFloat32 = reflect.TypeOf(float32(0))
scanTypeFloat64 = reflect.TypeOf(float64(0))
scanTypeInt8 = reflect.TypeOf(int8(0))
scanTypeInt16 = reflect.TypeOf(int16(0))
scanTypeInt32 = reflect.TypeOf(int32(0))
scanTypeInt64 = reflect.TypeOf(int64(0))
scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
scanTypeNullInt = reflect.TypeOf(sql.NullInt64{})
scanTypeNullTime = reflect.TypeOf(NullTime{})
scanTypeUint8 = reflect.TypeOf(uint8(0))
scanTypeUint16 = reflect.TypeOf(uint16(0))
scanTypeUint32 = reflect.TypeOf(uint32(0))
scanTypeUint64 = reflect.TypeOf(uint64(0))
scanTypeRawBytes = reflect.TypeOf(sql.RawBytes{})
scanTypeUnknown = reflect.TypeOf(new(interface{}))
)
type mysqlField struct {
tableName string
name string
length uint32
flags fieldFlag
fieldType fieldType
decimals byte
charSet uint8
}
func (mf *mysqlField) scanType() reflect.Type {
switch mf.fieldType {
case fieldTypeTiny:
if mf.flags&flagNotNULL != 0 {
if mf.flags&flagUnsigned != 0 {
return scanTypeUint8
}
return scanTypeInt8
}
return scanTypeNullInt
case fieldTypeShort, fieldTypeYear:
if mf.flags&flagNotNULL != 0 {
if mf.flags&flagUnsigned != 0 {
return scanTypeUint16
}
return scanTypeInt16
}
return scanTypeNullInt
case fieldTypeInt24, fieldTypeLong:
if mf.flags&flagNotNULL != 0 {
if mf.flags&flagUnsigned != 0 {
return scanTypeUint32
}
return scanTypeInt32
}
return scanTypeNullInt
case fieldTypeLongLong:
if mf.flags&flagNotNULL != 0 {
if mf.flags&flagUnsigned != 0 {
return scanTypeUint64
}
return scanTypeInt64
}
return scanTypeNullInt
case fieldTypeFloat:
if mf.flags&flagNotNULL != 0 {
return scanTypeFloat32
}
return scanTypeNullFloat
case fieldTypeDouble:
if mf.flags&flagNotNULL != 0 {
return scanTypeFloat64
}
return scanTypeNullFloat
case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar,
fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB,
fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB,
fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON,
fieldTypeTime:
return scanTypeRawBytes
case fieldTypeDate, fieldTypeNewDate,
fieldTypeTimestamp, fieldTypeDateTime:
// NullTime is always returned for more consistent behavior as it can
// handle both cases of parseTime regardless if the field is nullable.
return scanTypeNullTime
default:
return scanTypeUnknown
}
}

View File

@ -147,7 +147,8 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
} }
// send content packets // send content packets
if err == nil { // if packetSize == 0, the Reader contains no data
if err == nil && packetSize > 0 {
data := make([]byte, 4+packetSize) data := make([]byte, 4+packetSize)
var n int var n int
for err == nil { for err == nil {

View File

@ -30,9 +30,12 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
// read packet header // read packet header
data, err := mc.buf.readNext(4) data, err := mc.buf.readNext(4)
if err != nil { if err != nil {
if cerr := mc.canceled.Value(); cerr != nil {
return nil, cerr
}
errLog.Print(err) errLog.Print(err)
mc.Close() mc.Close()
return nil, driver.ErrBadConn return nil, ErrInvalidConn
} }
// packet length [24 bit] // packet length [24 bit]
@ -54,7 +57,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
if prevData == nil { if prevData == nil {
errLog.Print(ErrMalformPkt) errLog.Print(ErrMalformPkt)
mc.Close() mc.Close()
return nil, driver.ErrBadConn return nil, ErrInvalidConn
} }
return prevData, nil return prevData, nil
@ -63,9 +66,12 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
// read packet body [pktLen bytes] // read packet body [pktLen bytes]
data, err = mc.buf.readNext(pktLen) data, err = mc.buf.readNext(pktLen)
if err != nil { if err != nil {
if cerr := mc.canceled.Value(); cerr != nil {
return nil, cerr
}
errLog.Print(err) errLog.Print(err)
mc.Close() mc.Close()
return nil, driver.ErrBadConn return nil, ErrInvalidConn
} }
// return data if this was the last packet // return data if this was the last packet
@ -125,11 +131,20 @@ func (mc *mysqlConn) writePacket(data []byte) error {
// Handle error // Handle error
if err == nil { // n != len(data) if err == nil { // n != len(data)
mc.cleanup()
errLog.Print(ErrMalformPkt) errLog.Print(ErrMalformPkt)
} else { } else {
if cerr := mc.canceled.Value(); cerr != nil {
return cerr
}
if n == 0 && pktLen == len(data)-4 {
// only for the first loop iteration when nothing was written yet
return errBadConnNoWrite
}
mc.cleanup()
errLog.Print(err) errLog.Print(err)
} }
return driver.ErrBadConn return ErrInvalidConn
} }
} }
@ -263,7 +278,7 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
if data == nil { if data == nil {
// can not take the buffer. Something must be wrong with the connection // can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(ErrBusyBuffer)
return driver.ErrBadConn return errBadConnNoWrite
} }
// ClientFlags [32 bit] // ClientFlags [32 bit]
@ -341,7 +356,9 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error { func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {
// User password // User password
scrambleBuff := scrambleOldPassword(cipher, []byte(mc.cfg.Passwd)) // https://dev.mysql.com/doc/internals/en/old-password-authentication.html
// Old password authentication only need and will need 8-byte challenge.
scrambleBuff := scrambleOldPassword(cipher[:8], []byte(mc.cfg.Passwd))
// Calculate the packet length and add a tailing 0 // Calculate the packet length and add a tailing 0
pktLen := len(scrambleBuff) + 1 pktLen := len(scrambleBuff) + 1
@ -349,7 +366,7 @@ func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {
if data == nil { if data == nil {
// can not take the buffer. Something must be wrong with the connection // can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(ErrBusyBuffer)
return driver.ErrBadConn return errBadConnNoWrite
} }
// Add the scrambled password [null terminated string] // Add the scrambled password [null terminated string]
@ -368,7 +385,7 @@ func (mc *mysqlConn) writeClearAuthPacket() error {
if data == nil { if data == nil {
// can not take the buffer. Something must be wrong with the connection // can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(ErrBusyBuffer)
return driver.ErrBadConn return errBadConnNoWrite
} }
// Add the clear password [null terminated string] // Add the clear password [null terminated string]
@ -381,7 +398,9 @@ func (mc *mysqlConn) writeClearAuthPacket() error {
// Native password authentication method // Native password authentication method
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
func (mc *mysqlConn) writeNativeAuthPacket(cipher []byte) error { func (mc *mysqlConn) writeNativeAuthPacket(cipher []byte) error {
scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.Passwd)) // https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
// Native password authentication only need and will need 20-byte challenge.
scrambleBuff := scramblePassword(cipher[0:20], []byte(mc.cfg.Passwd))
// Calculate the packet length and add a tailing 0 // Calculate the packet length and add a tailing 0
pktLen := len(scrambleBuff) pktLen := len(scrambleBuff)
@ -389,7 +408,7 @@ func (mc *mysqlConn) writeNativeAuthPacket(cipher []byte) error {
if data == nil { if data == nil {
// can not take the buffer. Something must be wrong with the connection // can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(ErrBusyBuffer)
return driver.ErrBadConn return errBadConnNoWrite
} }
// Add the scramble // Add the scramble
@ -410,7 +429,7 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error {
if data == nil { if data == nil {
// can not take the buffer. Something must be wrong with the connection // can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(ErrBusyBuffer)
return driver.ErrBadConn return errBadConnNoWrite
} }
// Add command byte // Add command byte
@ -429,7 +448,7 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
if data == nil { if data == nil {
// can not take the buffer. Something must be wrong with the connection // can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(ErrBusyBuffer)
return driver.ErrBadConn return errBadConnNoWrite
} }
// Add command byte // Add command byte
@ -450,7 +469,7 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
if data == nil { if data == nil {
// can not take the buffer. Something must be wrong with the connection // can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(ErrBusyBuffer)
return driver.ErrBadConn return errBadConnNoWrite
} }
// Add command byte // Add command byte
@ -484,25 +503,26 @@ func (mc *mysqlConn) readResultOK() ([]byte, error) {
if len(data) > 1 { if len(data) > 1 {
pluginEndIndex := bytes.IndexByte(data, 0x00) pluginEndIndex := bytes.IndexByte(data, 0x00)
plugin := string(data[1:pluginEndIndex]) plugin := string(data[1:pluginEndIndex])
cipher := data[pluginEndIndex+1 : len(data)-1] cipher := data[pluginEndIndex+1:]
if plugin == "mysql_old_password" { switch plugin {
case "mysql_old_password":
// using old_passwords // using old_passwords
return cipher, ErrOldPassword return cipher, ErrOldPassword
} else if plugin == "mysql_clear_password" { case "mysql_clear_password":
// using clear text password // using clear text password
return cipher, ErrCleartextPassword return cipher, ErrCleartextPassword
} else if plugin == "mysql_native_password" { case "mysql_native_password":
// using mysql default authentication method // using mysql default authentication method
return cipher, ErrNativePassword return cipher, ErrNativePassword
} else { default:
return cipher, ErrUnknownPlugin return cipher, ErrUnknownPlugin
} }
} else {
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
return nil, ErrOldPassword
} }
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
return nil, ErrOldPassword
default: // Error otherwise default: // Error otherwise
return nil, mc.handleErrorPacket(data) return nil, mc.handleErrorPacket(data)
} }
@ -550,6 +570,22 @@ func (mc *mysqlConn) handleErrorPacket(data []byte) error {
// Error Number [16 bit uint] // Error Number [16 bit uint]
errno := binary.LittleEndian.Uint16(data[1:3]) errno := binary.LittleEndian.Uint16(data[1:3])
// 1792: ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION
// 1290: ER_OPTION_PREVENTS_STATEMENT (returned by Aurora during failover)
if (errno == 1792 || errno == 1290) && mc.cfg.RejectReadOnly {
// Oops; we are connected to a read-only connection, and won't be able
// to issue any write statements. Since RejectReadOnly is configured,
// we throw away this connection hoping this one would have write
// permission. This is specifically for a possible race condition
// during failover (e.g. on AWS Aurora). See README.md for more.
//
// We explicitly close the connection before returning
// driver.ErrBadConn to ensure that `database/sql` purges this
// connection and initiates a new one for next statement next time.
mc.Close()
return driver.ErrBadConn
}
pos := 3 pos := 3
// SQL State [optional: # + 5bytes string] // SQL State [optional: # + 5bytes string]
@ -584,19 +620,12 @@ func (mc *mysqlConn) handleOkPacket(data []byte) error {
// server_status [2 bytes] // server_status [2 bytes]
mc.status = readStatus(data[1+n+m : 1+n+m+2]) mc.status = readStatus(data[1+n+m : 1+n+m+2])
if err := mc.discardResults(); err != nil { if mc.status&statusMoreResultsExists != 0 {
return err
}
// warning count [2 bytes]
if !mc.strict {
return nil return nil
} }
pos := 1 + n + m + 2 // warning count [2 bytes]
if binary.LittleEndian.Uint16(data[pos:pos+2]) > 0 {
return mc.getWarnings()
}
return nil return nil
} }
@ -668,14 +697,21 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
pos += n
// Filler [uint8] // Filler [uint8]
pos++
// Charset [charset, collation uint8] // Charset [charset, collation uint8]
columns[i].charSet = data[pos]
pos += 2
// Length [uint32] // Length [uint32]
pos += n + 1 + 2 + 4 columns[i].length = binary.LittleEndian.Uint32(data[pos : pos+4])
pos += 4
// Field type [uint8] // Field type [uint8]
columns[i].fieldType = data[pos] columns[i].fieldType = fieldType(data[pos])
pos++ pos++
// Flags [uint16] // Flags [uint16]
@ -698,6 +734,10 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
func (rows *textRows) readRow(dest []driver.Value) error { func (rows *textRows) readRow(dest []driver.Value) error {
mc := rows.mc mc := rows.mc
if rows.rs.done {
return io.EOF
}
data, err := mc.readPacket() data, err := mc.readPacket()
if err != nil { if err != nil {
return err return err
@ -707,15 +747,11 @@ func (rows *textRows) readRow(dest []driver.Value) error {
if data[0] == iEOF && len(data) == 5 { if data[0] == iEOF && len(data) == 5 {
// server_status [2 bytes] // server_status [2 bytes]
rows.mc.status = readStatus(data[3:]) rows.mc.status = readStatus(data[3:])
err = rows.mc.discardResults() rows.rs.done = true
if err == nil { if !rows.HasNextResultSet() {
err = io.EOF rows.mc = nil
} else {
// connection unusable
rows.mc.Close()
} }
rows.mc = nil return io.EOF
return err
} }
if data[0] == iERR { if data[0] == iERR {
rows.mc = nil rows.mc = nil
@ -736,7 +772,7 @@ func (rows *textRows) readRow(dest []driver.Value) error {
if !mc.parseTime { if !mc.parseTime {
continue continue
} else { } else {
switch rows.columns[i].fieldType { switch rows.rs.columns[i].fieldType {
case fieldTypeTimestamp, fieldTypeDateTime, case fieldTypeTimestamp, fieldTypeDateTime,
fieldTypeDate, fieldTypeNewDate: fieldTypeDate, fieldTypeNewDate:
dest[i], err = parseDateTime( dest[i], err = parseDateTime(
@ -808,14 +844,7 @@ func (stmt *mysqlStmt) readPrepareResultPacket() (uint16, error) {
// Reserved [8 bit] // Reserved [8 bit]
// Warning count [16 bit uint] // Warning count [16 bit uint]
if !stmt.mc.strict {
return columnCount, nil
}
// Check for warnings count > 0, only available in MySQL > 4.1
if len(data) >= 12 && binary.LittleEndian.Uint16(data[10:12]) > 0 {
return columnCount, stmt.mc.getWarnings()
}
return columnCount, nil return columnCount, nil
} }
return 0, err return 0, err
@ -887,6 +916,12 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
const minPktLen = 4 + 1 + 4 + 1 + 4 const minPktLen = 4 + 1 + 4 + 1 + 4
mc := stmt.mc mc := stmt.mc
// Determine threshould dynamically to avoid packet size shortage.
longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1)
if longDataSize < 64 {
longDataSize = 64
}
// Reset packet-sequence // Reset packet-sequence
mc.sequence = 0 mc.sequence = 0
@ -900,7 +935,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
if data == nil { if data == nil {
// can not take the buffer. Something must be wrong with the connection // can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(ErrBusyBuffer)
return driver.ErrBadConn return errBadConnNoWrite
} }
// command [1 byte] // command [1 byte]
@ -959,7 +994,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
// build NULL-bitmap // build NULL-bitmap
if arg == nil { if arg == nil {
nullMask[i/8] |= 1 << (uint(i) & 7) nullMask[i/8] |= 1 << (uint(i) & 7)
paramTypes[i+i] = fieldTypeNULL paramTypes[i+i] = byte(fieldTypeNULL)
paramTypes[i+i+1] = 0x00 paramTypes[i+i+1] = 0x00
continue continue
} }
@ -967,7 +1002,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
// cache types and values // cache types and values
switch v := arg.(type) { switch v := arg.(type) {
case int64: case int64:
paramTypes[i+i] = fieldTypeLongLong paramTypes[i+i] = byte(fieldTypeLongLong)
paramTypes[i+i+1] = 0x00 paramTypes[i+i+1] = 0x00
if cap(paramValues)-len(paramValues)-8 >= 0 { if cap(paramValues)-len(paramValues)-8 >= 0 {
@ -983,7 +1018,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
} }
case float64: case float64:
paramTypes[i+i] = fieldTypeDouble paramTypes[i+i] = byte(fieldTypeDouble)
paramTypes[i+i+1] = 0x00 paramTypes[i+i+1] = 0x00
if cap(paramValues)-len(paramValues)-8 >= 0 { if cap(paramValues)-len(paramValues)-8 >= 0 {
@ -999,7 +1034,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
} }
case bool: case bool:
paramTypes[i+i] = fieldTypeTiny paramTypes[i+i] = byte(fieldTypeTiny)
paramTypes[i+i+1] = 0x00 paramTypes[i+i+1] = 0x00
if v { if v {
@ -1011,10 +1046,10 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
case []byte: case []byte:
// Common case (non-nil value) first // Common case (non-nil value) first
if v != nil { if v != nil {
paramTypes[i+i] = fieldTypeString paramTypes[i+i] = byte(fieldTypeString)
paramTypes[i+i+1] = 0x00 paramTypes[i+i+1] = 0x00
if len(v) < mc.maxAllowedPacket-pos-len(paramValues)-(len(args)-(i+1))*64 { if len(v) < longDataSize {
paramValues = appendLengthEncodedInteger(paramValues, paramValues = appendLengthEncodedInteger(paramValues,
uint64(len(v)), uint64(len(v)),
) )
@ -1029,14 +1064,14 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
// Handle []byte(nil) as a NULL value // Handle []byte(nil) as a NULL value
nullMask[i/8] |= 1 << (uint(i) & 7) nullMask[i/8] |= 1 << (uint(i) & 7)
paramTypes[i+i] = fieldTypeNULL paramTypes[i+i] = byte(fieldTypeNULL)
paramTypes[i+i+1] = 0x00 paramTypes[i+i+1] = 0x00
case string: case string:
paramTypes[i+i] = fieldTypeString paramTypes[i+i] = byte(fieldTypeString)
paramTypes[i+i+1] = 0x00 paramTypes[i+i+1] = 0x00
if len(v) < mc.maxAllowedPacket-pos-len(paramValues)-(len(args)-(i+1))*64 { if len(v) < longDataSize {
paramValues = appendLengthEncodedInteger(paramValues, paramValues = appendLengthEncodedInteger(paramValues,
uint64(len(v)), uint64(len(v)),
) )
@ -1048,20 +1083,22 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
} }
case time.Time: case time.Time:
paramTypes[i+i] = fieldTypeString paramTypes[i+i] = byte(fieldTypeString)
paramTypes[i+i+1] = 0x00 paramTypes[i+i+1] = 0x00
var val []byte var a [64]byte
var b = a[:0]
if v.IsZero() { if v.IsZero() {
val = []byte("0000-00-00") b = append(b, "0000-00-00"...)
} else { } else {
val = []byte(v.In(mc.cfg.Loc).Format(timeFormat)) b = v.In(mc.cfg.Loc).AppendFormat(b, timeFormat)
} }
paramValues = appendLengthEncodedInteger(paramValues, paramValues = appendLengthEncodedInteger(paramValues,
uint64(len(val)), uint64(len(b)),
) )
paramValues = append(paramValues, val...) paramValues = append(paramValues, b...)
default: default:
return fmt.Errorf("can not convert type: %T", arg) return fmt.Errorf("can not convert type: %T", arg)
@ -1097,8 +1134,6 @@ func (mc *mysqlConn) discardResults() error {
if err := mc.readUntilEOF(); err != nil { if err := mc.readUntilEOF(); err != nil {
return err return err
} }
} else {
mc.status &^= statusMoreResultsExists
} }
} }
return nil return nil
@ -1116,20 +1151,17 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
// EOF Packet // EOF Packet
if data[0] == iEOF && len(data) == 5 { if data[0] == iEOF && len(data) == 5 {
rows.mc.status = readStatus(data[3:]) rows.mc.status = readStatus(data[3:])
err = rows.mc.discardResults() rows.rs.done = true
if err == nil { if !rows.HasNextResultSet() {
err = io.EOF rows.mc = nil
} else {
// connection unusable
rows.mc.Close()
} }
rows.mc = nil return io.EOF
return err
} }
mc := rows.mc
rows.mc = nil rows.mc = nil
// Error otherwise // Error otherwise
return rows.mc.handleErrorPacket(data) return mc.handleErrorPacket(data)
} }
// NULL-bitmap, [(column-count + 7 + 2) / 8 bytes] // NULL-bitmap, [(column-count + 7 + 2) / 8 bytes]
@ -1145,14 +1177,14 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
} }
// Convert to byte-coded string // Convert to byte-coded string
switch rows.columns[i].fieldType { switch rows.rs.columns[i].fieldType {
case fieldTypeNULL: case fieldTypeNULL:
dest[i] = nil dest[i] = nil
continue continue
// Numeric Types // Numeric Types
case fieldTypeTiny: case fieldTypeTiny:
if rows.columns[i].flags&flagUnsigned != 0 { if rows.rs.columns[i].flags&flagUnsigned != 0 {
dest[i] = int64(data[pos]) dest[i] = int64(data[pos])
} else { } else {
dest[i] = int64(int8(data[pos])) dest[i] = int64(int8(data[pos]))
@ -1161,7 +1193,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
continue continue
case fieldTypeShort, fieldTypeYear: case fieldTypeShort, fieldTypeYear:
if rows.columns[i].flags&flagUnsigned != 0 { if rows.rs.columns[i].flags&flagUnsigned != 0 {
dest[i] = int64(binary.LittleEndian.Uint16(data[pos : pos+2])) dest[i] = int64(binary.LittleEndian.Uint16(data[pos : pos+2]))
} else { } else {
dest[i] = int64(int16(binary.LittleEndian.Uint16(data[pos : pos+2]))) dest[i] = int64(int16(binary.LittleEndian.Uint16(data[pos : pos+2])))
@ -1170,7 +1202,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
continue continue
case fieldTypeInt24, fieldTypeLong: case fieldTypeInt24, fieldTypeLong:
if rows.columns[i].flags&flagUnsigned != 0 { if rows.rs.columns[i].flags&flagUnsigned != 0 {
dest[i] = int64(binary.LittleEndian.Uint32(data[pos : pos+4])) dest[i] = int64(binary.LittleEndian.Uint32(data[pos : pos+4]))
} else { } else {
dest[i] = int64(int32(binary.LittleEndian.Uint32(data[pos : pos+4]))) dest[i] = int64(int32(binary.LittleEndian.Uint32(data[pos : pos+4])))
@ -1179,7 +1211,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
continue continue
case fieldTypeLongLong: case fieldTypeLongLong:
if rows.columns[i].flags&flagUnsigned != 0 { if rows.rs.columns[i].flags&flagUnsigned != 0 {
val := binary.LittleEndian.Uint64(data[pos : pos+8]) val := binary.LittleEndian.Uint64(data[pos : pos+8])
if val > math.MaxInt64 { if val > math.MaxInt64 {
dest[i] = uint64ToString(val) dest[i] = uint64ToString(val)
@ -1193,7 +1225,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
continue continue
case fieldTypeFloat: case fieldTypeFloat:
dest[i] = float32(math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4]))) dest[i] = math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4]))
pos += 4 pos += 4
continue continue
@ -1233,10 +1265,10 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
case isNull: case isNull:
dest[i] = nil dest[i] = nil
continue continue
case rows.columns[i].fieldType == fieldTypeTime: case rows.rs.columns[i].fieldType == fieldTypeTime:
// database/sql does not support an equivalent to TIME, return a string // database/sql does not support an equivalent to TIME, return a string
var dstlen uint8 var dstlen uint8
switch decimals := rows.columns[i].decimals; decimals { switch decimals := rows.rs.columns[i].decimals; decimals {
case 0x00, 0x1f: case 0x00, 0x1f:
dstlen = 8 dstlen = 8
case 1, 2, 3, 4, 5, 6: case 1, 2, 3, 4, 5, 6:
@ -1244,7 +1276,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
default: default:
return fmt.Errorf( return fmt.Errorf(
"protocol error, illegal decimals value %d", "protocol error, illegal decimals value %d",
rows.columns[i].decimals, rows.rs.columns[i].decimals,
) )
} }
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true) dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true)
@ -1252,10 +1284,10 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc) dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc)
default: default:
var dstlen uint8 var dstlen uint8
if rows.columns[i].fieldType == fieldTypeDate { if rows.rs.columns[i].fieldType == fieldTypeDate {
dstlen = 10 dstlen = 10
} else { } else {
switch decimals := rows.columns[i].decimals; decimals { switch decimals := rows.rs.columns[i].decimals; decimals {
case 0x00, 0x1f: case 0x00, 0x1f:
dstlen = 19 dstlen = 19
case 1, 2, 3, 4, 5, 6: case 1, 2, 3, 4, 5, 6:
@ -1263,7 +1295,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
default: default:
return fmt.Errorf( return fmt.Errorf(
"protocol error, illegal decimals value %d", "protocol error, illegal decimals value %d",
rows.columns[i].decimals, rows.rs.columns[i].decimals,
) )
} }
} }
@ -1279,7 +1311,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
// Please report if this happens! // Please report if this happens!
default: default:
return fmt.Errorf("unknown field type %d", rows.columns[i].fieldType) return fmt.Errorf("unknown field type %d", rows.rs.columns[i].fieldType)
} }
} }

View File

@ -11,19 +11,20 @@ package mysql
import ( import (
"database/sql/driver" "database/sql/driver"
"io" "io"
"math"
"reflect"
) )
type mysqlField struct { type resultSet struct {
tableName string columns []mysqlField
name string columnNames []string
flags fieldFlag done bool
fieldType byte
decimals byte
} }
type mysqlRows struct { type mysqlRows struct {
mc *mysqlConn mc *mysqlConn
columns []mysqlField rs resultSet
finish func()
} }
type binaryRows struct { type binaryRows struct {
@ -34,37 +35,86 @@ type textRows struct {
mysqlRows mysqlRows
} }
type emptyRows struct{}
func (rows *mysqlRows) Columns() []string { func (rows *mysqlRows) Columns() []string {
columns := make([]string, len(rows.columns)) if rows.rs.columnNames != nil {
return rows.rs.columnNames
}
columns := make([]string, len(rows.rs.columns))
if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias { if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias {
for i := range columns { for i := range columns {
if tableName := rows.columns[i].tableName; len(tableName) > 0 { if tableName := rows.rs.columns[i].tableName; len(tableName) > 0 {
columns[i] = tableName + "." + rows.columns[i].name columns[i] = tableName + "." + rows.rs.columns[i].name
} else { } else {
columns[i] = rows.columns[i].name columns[i] = rows.rs.columns[i].name
} }
} }
} else { } else {
for i := range columns { for i := range columns {
columns[i] = rows.columns[i].name columns[i] = rows.rs.columns[i].name
} }
} }
rows.rs.columnNames = columns
return columns return columns
} }
func (rows *mysqlRows) Close() error { func (rows *mysqlRows) ColumnTypeDatabaseTypeName(i int) string {
return rows.rs.columns[i].typeDatabaseName()
}
// func (rows *mysqlRows) ColumnTypeLength(i int) (length int64, ok bool) {
// return int64(rows.rs.columns[i].length), true
// }
func (rows *mysqlRows) ColumnTypeNullable(i int) (nullable, ok bool) {
return rows.rs.columns[i].flags&flagNotNULL == 0, true
}
func (rows *mysqlRows) ColumnTypePrecisionScale(i int) (int64, int64, bool) {
column := rows.rs.columns[i]
decimals := int64(column.decimals)
switch column.fieldType {
case fieldTypeDecimal, fieldTypeNewDecimal:
if decimals > 0 {
return int64(column.length) - 2, decimals, true
}
return int64(column.length) - 1, decimals, true
case fieldTypeTimestamp, fieldTypeDateTime, fieldTypeTime:
return decimals, decimals, true
case fieldTypeFloat, fieldTypeDouble:
if decimals == 0x1f {
return math.MaxInt64, math.MaxInt64, true
}
return math.MaxInt64, decimals, true
}
return 0, 0, false
}
func (rows *mysqlRows) ColumnTypeScanType(i int) reflect.Type {
return rows.rs.columns[i].scanType()
}
func (rows *mysqlRows) Close() (err error) {
if f := rows.finish; f != nil {
f()
rows.finish = nil
}
mc := rows.mc mc := rows.mc
if mc == nil { if mc == nil {
return nil return nil
} }
if mc.netConn == nil { if err := mc.error(); err != nil {
return ErrInvalidConn return err
} }
// Remove unread packets from stream // Remove unread packets from stream
err := mc.readUntilEOF() if !rows.rs.done {
err = mc.readUntilEOF()
}
if err == nil { if err == nil {
if err = mc.discardResults(); err != nil { if err = mc.discardResults(); err != nil {
return err return err
@ -75,10 +125,66 @@ func (rows *mysqlRows) Close() error {
return err return err
} }
func (rows *mysqlRows) HasNextResultSet() (b bool) {
if rows.mc == nil {
return false
}
return rows.mc.status&statusMoreResultsExists != 0
}
func (rows *mysqlRows) nextResultSet() (int, error) {
if rows.mc == nil {
return 0, io.EOF
}
if err := rows.mc.error(); err != nil {
return 0, err
}
// Remove unread packets from stream
if !rows.rs.done {
if err := rows.mc.readUntilEOF(); err != nil {
return 0, err
}
rows.rs.done = true
}
if !rows.HasNextResultSet() {
rows.mc = nil
return 0, io.EOF
}
rows.rs = resultSet{}
return rows.mc.readResultSetHeaderPacket()
}
func (rows *mysqlRows) nextNotEmptyResultSet() (int, error) {
for {
resLen, err := rows.nextResultSet()
if err != nil {
return 0, err
}
if resLen > 0 {
return resLen, nil
}
rows.rs.done = true
}
}
func (rows *binaryRows) NextResultSet() error {
resLen, err := rows.nextNotEmptyResultSet()
if err != nil {
return err
}
rows.rs.columns, err = rows.mc.readColumns(resLen)
return err
}
func (rows *binaryRows) Next(dest []driver.Value) error { func (rows *binaryRows) Next(dest []driver.Value) error {
if mc := rows.mc; mc != nil { if mc := rows.mc; mc != nil {
if mc.netConn == nil { if err := mc.error(); err != nil {
return ErrInvalidConn return err
} }
// Fetch next row from stream // Fetch next row from stream
@ -87,10 +193,20 @@ func (rows *binaryRows) Next(dest []driver.Value) error {
return io.EOF return io.EOF
} }
func (rows *textRows) NextResultSet() (err error) {
resLen, err := rows.nextNotEmptyResultSet()
if err != nil {
return err
}
rows.rs.columns, err = rows.mc.readColumns(resLen)
return err
}
func (rows *textRows) Next(dest []driver.Value) error { func (rows *textRows) Next(dest []driver.Value) error {
if mc := rows.mc; mc != nil { if mc := rows.mc; mc != nil {
if mc.netConn == nil { if err := mc.error(); err != nil {
return ErrInvalidConn return err
} }
// Fetch next row from stream // Fetch next row from stream
@ -98,15 +214,3 @@ func (rows *textRows) Next(dest []driver.Value) error {
} }
return io.EOF 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
}

View File

@ -11,6 +11,7 @@ package mysql
import ( import (
"database/sql/driver" "database/sql/driver"
"fmt" "fmt"
"io"
"reflect" "reflect"
"strconv" "strconv"
) )
@ -19,11 +20,10 @@ type mysqlStmt struct {
mc *mysqlConn mc *mysqlConn
id uint32 id uint32
paramCount int paramCount int
columns []mysqlField // cached from the first query
} }
func (stmt *mysqlStmt) Close() error { func (stmt *mysqlStmt) Close() error {
if stmt.mc == nil || stmt.mc.netConn == nil { if stmt.mc == nil || stmt.mc.closed.IsSet() {
// driver.Stmt.Close can be called more than once, thus this function // driver.Stmt.Close can be called more than once, thus this function
// has to be idempotent. // has to be idempotent.
// See also Issue #450 and golang/go#16019. // See also Issue #450 and golang/go#16019.
@ -45,14 +45,14 @@ func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
} }
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) { func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
if stmt.mc.netConn == nil { if stmt.mc.closed.IsSet() {
errLog.Print(ErrInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
// Send command // Send command
err := stmt.writeExecutePacket(args) err := stmt.writeExecutePacket(args)
if err != nil { if err != nil {
return nil, err return nil, stmt.mc.markBadConn(err)
} }
mc := stmt.mc mc := stmt.mc
@ -62,37 +62,45 @@ func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
// Read Result // Read Result
resLen, err := mc.readResultSetHeaderPacket() resLen, err := mc.readResultSetHeaderPacket()
if err == nil { if err != nil {
if resLen > 0 { return nil, err
// Columns }
err = mc.readUntilEOF()
if err != nil {
return nil, err
}
// Rows if resLen > 0 {
err = mc.readUntilEOF() // Columns
if err = mc.readUntilEOF(); err != nil {
return nil, err
} }
if err == nil {
return &mysqlResult{ // Rows
affectedRows: int64(mc.affectedRows), if err := mc.readUntilEOF(); err != nil {
insertId: int64(mc.insertId), return nil, err
}, nil
} }
} }
return nil, err if err := mc.discardResults(); err != nil {
return nil, err
}
return &mysqlResult{
affectedRows: int64(mc.affectedRows),
insertId: int64(mc.insertId),
}, nil
} }
func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) { func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
if stmt.mc.netConn == nil { return stmt.query(args)
}
func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
if stmt.mc.closed.IsSet() {
errLog.Print(ErrInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
// Send command // Send command
err := stmt.writeExecutePacket(args) err := stmt.writeExecutePacket(args)
if err != nil { if err != nil {
return nil, err return nil, stmt.mc.markBadConn(err)
} }
mc := stmt.mc mc := stmt.mc
@ -107,14 +115,15 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
if resLen > 0 { if resLen > 0 {
rows.mc = mc rows.mc = mc
// Columns rows.rs.columns, err = mc.readColumns(resLen)
// If not cached, read them and cache them } else {
if stmt.columns == nil { rows.rs.done = true
rows.columns, err = mc.readColumns(resLen)
stmt.columns = rows.columns switch err := rows.NextResultSet(); err {
} else { case nil, io.EOF:
rows.columns = stmt.columns return rows, nil
err = mc.readUntilEOF() default:
return nil, err
} }
} }
@ -128,6 +137,12 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
return v, nil return v, nil
} }
if v != nil {
if valuer, ok := v.(driver.Valuer); ok {
return valuer.Value()
}
}
rv := reflect.ValueOf(v) rv := reflect.ValueOf(v)
switch rv.Kind() { switch rv.Kind() {
case reflect.Ptr: case reflect.Ptr:
@ -148,6 +163,16 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
return int64(u64), nil return int64(u64), nil
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
return rv.Float(), nil return rv.Float(), nil
case reflect.Bool:
return rv.Bool(), nil
case reflect.Slice:
ek := rv.Type().Elem().Kind()
if ek == reflect.Uint8 {
return rv.Bytes(), nil
}
return nil, fmt.Errorf("unsupported type %T, a slice of %s", v, ek)
case reflect.String:
return rv.String(), nil
} }
return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind()) return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
} }

View File

@ -13,7 +13,7 @@ type mysqlTx struct {
} }
func (tx *mysqlTx) Commit() (err error) { func (tx *mysqlTx) Commit() (err error) {
if tx.mc == nil || tx.mc.netConn == nil { if tx.mc == nil || tx.mc.closed.IsSet() {
return ErrInvalidConn return ErrInvalidConn
} }
err = tx.mc.exec("COMMIT") err = tx.mc.exec("COMMIT")
@ -22,7 +22,7 @@ func (tx *mysqlTx) Commit() (err error) {
} }
func (tx *mysqlTx) Rollback() (err error) { func (tx *mysqlTx) Rollback() (err error) {
if tx.mc == nil || tx.mc.netConn == nil { if tx.mc == nil || tx.mc.closed.IsSet() {
return ErrInvalidConn return ErrInvalidConn
} }
err = tx.mc.exec("ROLLBACK") err = tx.mc.exec("ROLLBACK")

View File

@ -16,16 +16,21 @@ import (
"fmt" "fmt"
"io" "io"
"strings" "strings"
"sync"
"sync/atomic"
"time" "time"
) )
var ( var (
tlsConfigLock sync.RWMutex
tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
) )
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open. // RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
// Use the key as a value in the DSN where tls=value. // Use the key as a value in the DSN where tls=value.
// //
// Note: The tls.Config provided to needs to be exclusively owned by the driver after registering.
//
// rootCertPool := x509.NewCertPool() // rootCertPool := x509.NewCertPool()
// pem, err := ioutil.ReadFile("/path/ca-cert.pem") // pem, err := ioutil.ReadFile("/path/ca-cert.pem")
// if err != nil { // if err != nil {
@ -51,19 +56,32 @@ func RegisterTLSConfig(key string, config *tls.Config) error {
return fmt.Errorf("key '%s' is reserved", key) return fmt.Errorf("key '%s' is reserved", key)
} }
tlsConfigLock.Lock()
if tlsConfigRegister == nil { if tlsConfigRegister == nil {
tlsConfigRegister = make(map[string]*tls.Config) tlsConfigRegister = make(map[string]*tls.Config)
} }
tlsConfigRegister[key] = config tlsConfigRegister[key] = config
tlsConfigLock.Unlock()
return nil return nil
} }
// DeregisterTLSConfig removes the tls.Config associated with key. // DeregisterTLSConfig removes the tls.Config associated with key.
func DeregisterTLSConfig(key string) { func DeregisterTLSConfig(key string) {
tlsConfigLock.Lock()
if tlsConfigRegister != nil { if tlsConfigRegister != nil {
delete(tlsConfigRegister, key) delete(tlsConfigRegister, key)
} }
tlsConfigLock.Unlock()
}
func getTLSConfigClone(key string) (config *tls.Config) {
tlsConfigLock.RLock()
if v, ok := tlsConfigRegister[key]; ok {
config = cloneTLSConfig(v)
}
tlsConfigLock.RUnlock()
return
} }
// Returns the bool value of the input. // Returns the bool value of the input.
@ -548,8 +566,8 @@ func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
if len(b) == 0 { if len(b) == 0 {
return 0, true, 1 return 0, true, 1
} }
switch b[0] {
switch b[0] {
// 251: NULL // 251: NULL
case 0xfb: case 0xfb:
return 0, true, 1 return 0, true, 1
@ -738,3 +756,67 @@ func escapeStringQuotes(buf []byte, v string) []byte {
return buf[:pos] return buf[:pos]
} }
/******************************************************************************
* Sync utils *
******************************************************************************/
// noCopy may be embedded into structs which must not be copied
// after the first use.
//
// See https://github.com/golang/go/issues/8005#issuecomment-190753527
// for details.
type noCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {}
// atomicBool is a wrapper around uint32 for usage as a boolean value with
// atomic access.
type atomicBool struct {
_noCopy noCopy
value uint32
}
// IsSet returns wether the current boolean value is true
func (ab *atomicBool) IsSet() bool {
return atomic.LoadUint32(&ab.value) > 0
}
// Set sets the value of the bool regardless of the previous value
func (ab *atomicBool) Set(value bool) {
if value {
atomic.StoreUint32(&ab.value, 1)
} else {
atomic.StoreUint32(&ab.value, 0)
}
}
// TrySet sets the value of the bool and returns wether the value changed
func (ab *atomicBool) TrySet(value bool) bool {
if value {
return atomic.SwapUint32(&ab.value, 1) == 0
}
return atomic.SwapUint32(&ab.value, 0) > 0
}
// atomicBool is a wrapper for atomically accessed error values
type atomicError struct {
_noCopy noCopy
value atomic.Value
}
// Set sets the error value regardless of the previous value.
// The value must not be nil
func (ae *atomicError) Set(value error) {
ae.value.Store(value)
}
// Value returns the current error value
func (ae *atomicError) Value() error {
if v := ae.value.Load(); v != nil {
// this will panic if the value doesn't implement the error interface
return v.(error)
}
return nil
}

40
vendor/github.com/go-sql-driver/mysql/utils_go17.go generated vendored Normal file
View File

@ -0,0 +1,40 @@
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build go1.7
// +build !go1.8
package mysql
import "crypto/tls"
func cloneTLSConfig(c *tls.Config) *tls.Config {
return &tls.Config{
Rand: c.Rand,
Time: c.Time,
Certificates: c.Certificates,
NameToCertificate: c.NameToCertificate,
GetCertificate: c.GetCertificate,
RootCAs: c.RootCAs,
NextProtos: c.NextProtos,
ServerName: c.ServerName,
ClientAuth: c.ClientAuth,
ClientCAs: c.ClientCAs,
InsecureSkipVerify: c.InsecureSkipVerify,
CipherSuites: c.CipherSuites,
PreferServerCipherSuites: c.PreferServerCipherSuites,
SessionTicketsDisabled: c.SessionTicketsDisabled,
SessionTicketKey: c.SessionTicketKey,
ClientSessionCache: c.ClientSessionCache,
MinVersion: c.MinVersion,
MaxVersion: c.MaxVersion,
CurvePreferences: c.CurvePreferences,
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
Renegotiation: c.Renegotiation,
}
}

49
vendor/github.com/go-sql-driver/mysql/utils_go18.go generated vendored Normal file
View File

@ -0,0 +1,49 @@
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build go1.8
package mysql
import (
"crypto/tls"
"database/sql"
"database/sql/driver"
"errors"
)
func cloneTLSConfig(c *tls.Config) *tls.Config {
return c.Clone()
}
func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
dargs := make([]driver.Value, len(named))
for n, param := range named {
if len(param.Name) > 0 {
// TODO: support the use of Named Parameters #561
return nil, errors.New("mysql: driver does not support the use of Named Parameters")
}
dargs[n] = param.Value
}
return dargs, nil
}
func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
switch sql.IsolationLevel(level) {
case sql.LevelRepeatableRead:
return "REPEATABLE READ", nil
case sql.LevelReadCommitted:
return "READ COMMITTED", nil
case sql.LevelReadUncommitted:
return "READ UNCOMMITTED", nil
case sql.LevelSerializable:
return "SERIALIZABLE", nil
default:
return "", errors.New("mysql: unsupported isolation level: " + string(level))
}
}

View File

@ -45,22 +45,28 @@ func New(opts *LoggerOptions) Logger {
mtx = new(sync.Mutex) mtx = new(sync.Mutex)
} }
return &intLogger{ ret := &intLogger{
m: mtx, m: mtx,
json: opts.JSONFormat, json: opts.JSONFormat,
caller: opts.IncludeLocation, caller: opts.IncludeLocation,
name: opts.Name, name: opts.Name,
w: bufio.NewWriter(output), timeFormat: TimeFormat,
level: level, w: bufio.NewWriter(output),
level: level,
} }
if opts.TimeFormat != "" {
ret.timeFormat = opts.TimeFormat
}
return ret
} }
// The internal logger implementation. Internal in that it is defined entirely // The internal logger implementation. Internal in that it is defined entirely
// by this package. // by this package.
type intLogger struct { type intLogger struct {
json bool json bool
caller bool caller bool
name string name string
timeFormat string
// this is a pointer so that it's shared by any derived loggers, since // this is a pointer so that it's shared by any derived loggers, since
// those derived loggers share the bufio.Writer as well. // those derived loggers share the bufio.Writer as well.
@ -132,7 +138,7 @@ func trimCallerPath(path string) string {
// Non-JSON logging format function // Non-JSON logging format function
func (z *intLogger) log(t time.Time, level Level, msg string, args ...interface{}) { func (z *intLogger) log(t time.Time, level Level, msg string, args ...interface{}) {
z.w.WriteString(t.Format(TimeFormat)) z.w.WriteString(t.Format(z.timeFormat))
z.w.WriteByte(' ') z.w.WriteByte(' ')
s, ok := _levelToBracket[level] s, ok := _levelToBracket[level]

View File

@ -139,4 +139,7 @@ type LoggerOptions struct {
// Include file and line information in each log line // Include file and line information in each log line
IncludeLocation bool IncludeLocation bool
// The time format to use instead of the default
TimeFormat string
} }

10
vendor/github.com/lib/pq/conn.go generated vendored
View File

@ -339,6 +339,15 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// cn.ssl and cn.startup panic on error. Make sure we don't leak cn.c.
panicking := true
defer func() {
if panicking {
cn.c.Close()
}
}()
cn.ssl(o) cn.ssl(o)
cn.buf = bufio.NewReader(cn.c) cn.buf = bufio.NewReader(cn.c)
cn.startup(o) cn.startup(o)
@ -347,6 +356,7 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) {
if timeout, ok := o["connect_timeout"]; ok && timeout != "0" { if timeout, ok := o["connect_timeout"]; ok && timeout != "0" {
err = cn.c.SetDeadline(time.Time{}) err = cn.c.SetDeadline(time.Time{})
} }
panicking = false
return cn, err return cn, err
} }

View File

@ -5,6 +5,7 @@ import (
"os" "os"
"path" "path"
"github.com/prometheus/procfs/nfsd"
"github.com/prometheus/procfs/xfs" "github.com/prometheus/procfs/xfs"
) )
@ -44,3 +45,14 @@ func (fs FS) XFSStats() (*xfs.Stats, error) {
return xfs.ParseStats(f) return xfs.ParseStats(f)
} }
// NFSdRPCStats retrieves NFS daemon RPC statistics.
func (fs FS) NFSdRPCStats() (*nfsd.RPCStats, error) {
f, err := os.Open(fs.Path("net/rpc/nfsd"))
if err != nil {
return nil, err
}
defer f.Close()
return nfsd.ParseRPCStats(f)
}

View File

@ -0,0 +1,46 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import "strconv"
// ParseUint32s parses a slice of strings into a slice of uint32s.
func ParseUint32s(ss []string) ([]uint32, error) {
us := make([]uint32, 0, len(ss))
for _, s := range ss {
u, err := strconv.ParseUint(s, 10, 32)
if err != nil {
return nil, err
}
us = append(us, uint32(u))
}
return us, nil
}
// ParseUint64s parses a slice of strings into a slice of uint64s.
func ParseUint64s(ss []string) ([]uint64, error) {
us := make([]uint64, 0, len(ss))
for _, s := range ss {
u, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return nil, err
}
us = append(us, u)
}
return us, nil
}

184
vendor/github.com/prometheus/procfs/nfsd/nfsd.go generated vendored Normal file
View File

@ -0,0 +1,184 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package nfsd implements parsing of /proc/net/rpc/nfsd.
// Fields are documented in https://www.svennd.be/nfsd-stats-explained-procnetrpcnfsd/
package nfsd
// ReplyCache models the "rc" line.
type ReplyCache struct {
Hits uint64
Misses uint64
NoCache uint64
}
// FileHandles models the "fh" line.
type FileHandles struct {
Stale uint64
TotalLookups uint64
AnonLookups uint64
DirNoCache uint64
NoDirNoCache uint64
}
// InputOutput models the "io" line.
type InputOutput struct {
Read uint64
Write uint64
}
// Threads models the "th" line.
type Threads struct {
Threads uint64
FullCnt uint64
}
// ReadAheadCache models the "ra" line.
type ReadAheadCache struct {
CacheSize uint64
CacheHistogram []uint64
NotFound uint64
}
// Network models the "net" line.
type Network struct {
NetCount uint64
UDPCount uint64
TCPCount uint64
TCPConnect uint64
}
// RPC models the "rpc" line.
type RPC struct {
RPCCount uint64
BadCnt uint64
BadFmt uint64
BadAuth uint64
BadcInt uint64
}
// V2Stats models the "proc2" line.
type V2Stats struct {
Null uint64
GetAttr uint64
SetAttr uint64
Root uint64
Lookup uint64
ReadLink uint64
Read uint64
WrCache uint64
Write uint64
Create uint64
Remove uint64
Rename uint64
Link uint64
SymLink uint64
MkDir uint64
RmDir uint64
ReadDir uint64
FsStat uint64
}
// V3Stats models the "proc3" line.
type V3Stats struct {
Null uint64
GetAttr uint64
SetAttr uint64
Lookup uint64
Access uint64
ReadLink uint64
Read uint64
Write uint64
Create uint64
MkDir uint64
SymLink uint64
MkNod uint64
Remove uint64
RmDir uint64
Rename uint64
Link uint64
ReadDir uint64
ReadDirPlus uint64
FsStat uint64
FsInfo uint64
PathConf uint64
Commit uint64
}
// V4Stats models the "proc4" line.
type V4Stats struct {
Null uint64
Compound uint64
}
// V4Ops models the "proc4ops" line: NFSv4 operations
// Variable list, see:
// v4.0 https://tools.ietf.org/html/rfc3010 (38 operations)
// v4.1 https://tools.ietf.org/html/rfc5661 (58 operations)
// v4.2 https://tools.ietf.org/html/draft-ietf-nfsv4-minorversion2-41 (71 operations)
type V4Ops struct {
//Values uint64 // Variable depending on v4.x sub-version. TODO: Will this always at least include the fields in this struct?
Op0Unused uint64
Op1Unused uint64
Op2Future uint64
Access uint64
Close uint64
Commit uint64
Create uint64
DelegPurge uint64
DelegReturn uint64
GetAttr uint64
GetFH uint64
Link uint64
Lock uint64
Lockt uint64
Locku uint64
Lookup uint64
LookupRoot uint64
Nverify uint64
Open uint64
OpenAttr uint64
OpenConfirm uint64
OpenDgrd uint64
PutFH uint64
PutPubFH uint64
PutRootFH uint64
Read uint64
ReadDir uint64
ReadLink uint64
Remove uint64
Rename uint64
Renew uint64
RestoreFH uint64
SaveFH uint64
SecInfo uint64
SetAttr uint64
Verify uint64
Write uint64
RelLockOwner uint64
}
// RPCStats models all stats from /proc/net/rpc/nfsd.
type RPCStats struct {
ReplyCache ReplyCache
FileHandles FileHandles
InputOutput InputOutput
Threads Threads
ReadAheadCache ReadAheadCache
Network Network
RPC RPC
V2Stats V2Stats
V3Stats V3Stats
V4Stats V4Stats
V4Ops V4Ops
}

298
vendor/github.com/prometheus/procfs/nfsd/parse_nfsd.go generated vendored Normal file
View File

@ -0,0 +1,298 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nfsd
import (
"bufio"
"fmt"
"io"
"strings"
"github.com/prometheus/procfs/internal/util"
)
// ParseRPCStats returns stats read from /proc/net/rpc/nfsd
func ParseRPCStats(r io.Reader) (*RPCStats, error) {
stats := &RPCStats{}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(scanner.Text())
// require at least <key> <value>
if len(parts) < 2 {
return nil, fmt.Errorf("invalid NFSd metric line %q", line)
}
label := parts[0]
var values []uint64
var err error
if label == "th" {
if len(parts) < 3 {
return nil, fmt.Errorf("invalid NFSd th metric line %q", line)
}
values, err = util.ParseUint64s(parts[1:3])
} else {
values, err = util.ParseUint64s(parts[1:])
}
if err != nil {
return nil, fmt.Errorf("error parsing NFSd metric line: %s", err)
}
switch metricLine := parts[0]; metricLine {
case "rc":
stats.ReplyCache, err = parseReplyCache(values)
case "fh":
stats.FileHandles, err = parseFileHandles(values)
case "io":
stats.InputOutput, err = parseInputOutput(values)
case "th":
stats.Threads, err = parseThreads(values)
case "ra":
stats.ReadAheadCache, err = parseReadAheadCache(values)
case "net":
stats.Network, err = parseNetwork(values)
case "rpc":
stats.RPC, err = parseRPC(values)
case "proc2":
stats.V2Stats, err = parseV2Stats(values)
case "proc3":
stats.V3Stats, err = parseV3Stats(values)
case "proc4":
stats.V4Stats, err = parseV4Stats(values)
case "proc4ops":
stats.V4Ops, err = parseV4Ops(values)
default:
return nil, fmt.Errorf("unknown NFSd metric line %q", metricLine)
}
if err != nil {
return nil, fmt.Errorf("errors parsing NFSd metric line: %s", err)
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("error scanning NFSd file: %s", err)
}
return stats, nil
}
func parseReplyCache(v []uint64) (ReplyCache, error) {
if len(v) != 3 {
return ReplyCache{}, fmt.Errorf("invalid ReplyCache line %q", v)
}
return ReplyCache{
Hits: v[0],
Misses: v[1],
NoCache: v[2],
}, nil
}
func parseFileHandles(v []uint64) (FileHandles, error) {
if len(v) != 5 {
return FileHandles{}, fmt.Errorf("invalid FileHandles, line %q", v)
}
return FileHandles{
Stale: v[0],
TotalLookups: v[1],
AnonLookups: v[2],
DirNoCache: v[3],
NoDirNoCache: v[4],
}, nil
}
func parseInputOutput(v []uint64) (InputOutput, error) {
if len(v) != 2 {
return InputOutput{}, fmt.Errorf("invalid InputOutput line %q", v)
}
return InputOutput{
Read: v[0],
Write: v[1],
}, nil
}
func parseThreads(v []uint64) (Threads, error) {
if len(v) != 2 {
return Threads{}, fmt.Errorf("invalid Threads line %q", v)
}
return Threads{
Threads: v[0],
FullCnt: v[1],
}, nil
}
func parseReadAheadCache(v []uint64) (ReadAheadCache, error) {
if len(v) != 12 {
return ReadAheadCache{}, fmt.Errorf("invalid ReadAheadCache line %q", v)
}
return ReadAheadCache{
CacheSize: v[0],
CacheHistogram: v[1:11],
NotFound: v[11],
}, nil
}
func parseNetwork(v []uint64) (Network, error) {
if len(v) != 4 {
return Network{}, fmt.Errorf("invalid Network line %q", v)
}
return Network{
NetCount: v[0],
UDPCount: v[1],
TCPCount: v[2],
TCPConnect: v[3],
}, nil
}
func parseRPC(v []uint64) (RPC, error) {
if len(v) != 5 {
return RPC{}, fmt.Errorf("invalid RPC line %q", v)
}
return RPC{
RPCCount: v[0],
BadCnt: v[1],
BadFmt: v[2],
BadAuth: v[3],
BadcInt: v[4],
}, nil
}
func parseV2Stats(v []uint64) (V2Stats, error) {
values := int(v[0])
if len(v[1:]) != values || values != 18 {
return V2Stats{}, fmt.Errorf("invalid V2Stats line %q", v)
}
return V2Stats{
Null: v[1],
GetAttr: v[2],
SetAttr: v[3],
Root: v[4],
Lookup: v[5],
ReadLink: v[6],
Read: v[7],
WrCache: v[8],
Write: v[9],
Create: v[10],
Remove: v[11],
Rename: v[12],
Link: v[13],
SymLink: v[14],
MkDir: v[15],
RmDir: v[16],
ReadDir: v[17],
FsStat: v[18],
}, nil
}
func parseV3Stats(v []uint64) (V3Stats, error) {
values := int(v[0])
if len(v[1:]) != values || values != 22 {
return V3Stats{}, fmt.Errorf("invalid V3Stats line %q", v)
}
return V3Stats{
Null: v[1],
GetAttr: v[2],
SetAttr: v[3],
Lookup: v[4],
Access: v[5],
ReadLink: v[6],
Read: v[7],
Write: v[8],
Create: v[9],
MkDir: v[10],
SymLink: v[11],
MkNod: v[12],
Remove: v[13],
RmDir: v[14],
Rename: v[15],
Link: v[16],
ReadDir: v[17],
ReadDirPlus: v[18],
FsStat: v[19],
FsInfo: v[20],
PathConf: v[21],
Commit: v[22],
}, nil
}
func parseV4Stats(v []uint64) (V4Stats, error) {
values := int(v[0])
if len(v[1:]) != values || values != 2 {
return V4Stats{}, fmt.Errorf("invalid V4Stats line %q", v)
}
return V4Stats{
Null: v[1],
Compound: v[2],
}, nil
}
func parseV4Ops(v []uint64) (V4Ops, error) {
values := int(v[0])
if len(v[1:]) != values || values < 39 {
return V4Ops{}, fmt.Errorf("invalid V4Ops line %q", v)
}
stats := V4Ops{
Op0Unused: v[1],
Op1Unused: v[2],
Op2Future: v[3],
Access: v[4],
Close: v[5],
Commit: v[6],
Create: v[7],
DelegPurge: v[8],
DelegReturn: v[9],
GetAttr: v[10],
GetFH: v[11],
Link: v[12],
Lock: v[13],
Lockt: v[14],
Locku: v[15],
Lookup: v[16],
LookupRoot: v[17],
Nverify: v[18],
Open: v[19],
OpenAttr: v[20],
OpenConfirm: v[21],
OpenDgrd: v[22],
PutFH: v[23],
PutPubFH: v[24],
PutRootFH: v[25],
Read: v[26],
ReadDir: v[27],
ReadLink: v[28],
Remove: v[29],
Rename: v[30],
Renew: v[31],
RestoreFH: v[32],
SaveFH: v[33],
SecInfo: v[34],
SetAttr: v[35],
Verify: v[36],
Write: v[37],
RelLockOwner: v[38],
}
return stats, nil
}

View File

@ -17,8 +17,9 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"io" "io"
"strconv"
"strings" "strings"
"github.com/prometheus/procfs/internal/util"
) )
// ParseStats parses a Stats from an input io.Reader, using the format // ParseStats parses a Stats from an input io.Reader, using the format
@ -68,7 +69,7 @@ func ParseStats(r io.Reader) (*Stats, error) {
// Extended precision counters are uint64 values. // Extended precision counters are uint64 values.
if label == fieldXpc { if label == fieldXpc {
us, err := parseUint64s(ss[1:]) us, err := util.ParseUint64s(ss[1:])
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -82,7 +83,7 @@ func ParseStats(r io.Reader) (*Stats, error) {
} }
// All other counters are uint32 values. // All other counters are uint32 values.
us, err := parseUint32s(ss[1:]) us, err := util.ParseUint32s(ss[1:])
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -327,33 +328,3 @@ func extendedPrecisionStats(us []uint64) (ExtendedPrecisionStats, error) {
ReadBytes: us[2], ReadBytes: us[2],
}, nil }, nil
} }
// parseUint32s parses a slice of strings into a slice of uint32s.
func parseUint32s(ss []string) ([]uint32, error) {
us := make([]uint32, 0, len(ss))
for _, s := range ss {
u, err := strconv.ParseUint(s, 10, 32)
if err != nil {
return nil, err
}
us = append(us, uint32(u))
}
return us, nil
}
// parseUint64s parses a slice of strings into a slice of uint64s.
func parseUint64s(ss []string) ([]uint64, error) {
us := make([]uint64, 0, len(ss))
for _, s := range ss {
u, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return nil, err
}
us = append(us, u)
}
return us, nil
}

View File

@ -6,97 +6,12 @@
package unix package unix
import "unsafe" import "syscall"
// readInt returns the size-bytes unsigned integer in native byte order at offset off.
func readInt(b []byte, off, size uintptr) (u uint64, ok bool) {
if len(b) < int(off+size) {
return 0, false
}
if isBigEndian {
return readIntBE(b[off:], size), true
}
return readIntLE(b[off:], size), true
}
func readIntBE(b []byte, size uintptr) uint64 {
switch size {
case 1:
return uint64(b[0])
case 2:
_ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[1]) | uint64(b[0])<<8
case 4:
_ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[3]) | uint64(b[2])<<8 | uint64(b[1])<<16 | uint64(b[0])<<24
case 8:
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
default:
panic("syscall: readInt with unsupported size")
}
}
func readIntLE(b []byte, size uintptr) uint64 {
switch size {
case 1:
return uint64(b[0])
case 2:
_ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[0]) | uint64(b[1])<<8
case 4:
_ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24
case 8:
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
default:
panic("syscall: readInt with unsupported size")
}
}
// ParseDirent parses up to max directory entries in buf, // ParseDirent parses up to max directory entries in buf,
// appending the names to names. It returns the number of // appending the names to names. It returns the number of
// bytes consumed from buf, the number of entries added // bytes consumed from buf, the number of entries added
// to names, and the new names slice. // to names, and the new names slice.
func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, newnames []string) { func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, newnames []string) {
origlen := len(buf) return syscall.ParseDirent(buf, max, names)
count = 0
for max != 0 && len(buf) > 0 {
reclen, ok := direntReclen(buf)
if !ok || reclen > uint64(len(buf)) {
return origlen, count, names
}
rec := buf[:reclen]
buf = buf[reclen:]
ino, ok := direntIno(rec)
if !ok {
break
}
if ino == 0 { // File absent in directory.
continue
}
const namoff = uint64(unsafe.Offsetof(Dirent{}.Name))
namlen, ok := direntNamlen(rec)
if !ok || namoff+namlen > uint64(len(rec)) {
break
}
name := rec[namoff : namoff+namlen]
for i, c := range name {
if c == 0 {
name = name[:i]
break
}
}
// Check for useless names before allocating a string.
if string(name) == "." || string(name) == ".." {
continue
}
max--
count++
names = append(names, string(name))
}
return origlen - len(buf), count, names
} }

View File

@ -76,18 +76,6 @@ func nametomib(name string) (mib []_C_int, err error) {
return buf[0 : n/siz], nil return buf[0 : n/siz], nil
} }
func direntIno(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Ino), unsafe.Sizeof(Dirent{}.Ino))
}
func direntReclen(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Reclen), unsafe.Sizeof(Dirent{}.Reclen))
}
func direntNamlen(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Namlen), unsafe.Sizeof(Dirent{}.Namlen))
}
//sys ptrace(request int, pid int, addr uintptr, data uintptr) (err error) //sys ptrace(request int, pid int, addr uintptr, data uintptr) (err error)
func PtraceAttach(pid int) (err error) { return ptrace(PT_ATTACH, pid, 0, 0) } func PtraceAttach(pid int) (err error) { return ptrace(PT_ATTACH, pid, 0, 0) }
func PtraceDetach(pid int) (err error) { return ptrace(PT_DETACH, pid, 0, 0) } func PtraceDetach(pid int) (err error) { return ptrace(PT_DETACH, pid, 0, 0) }

View File

@ -56,22 +56,6 @@ func nametomib(name string) (mib []_C_int, err error) {
return buf[0 : n/siz], nil return buf[0 : n/siz], nil
} }
func direntIno(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Fileno), unsafe.Sizeof(Dirent{}.Fileno))
}
func direntReclen(buf []byte) (uint64, bool) {
namlen, ok := direntNamlen(buf)
if !ok {
return 0, false
}
return (16 + namlen + 1 + 7) &^ 7, true
}
func direntNamlen(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Namlen), unsafe.Sizeof(Dirent{}.Namlen))
}
//sysnb pipe() (r int, w int, err error) //sysnb pipe() (r int, w int, err error)
func Pipe(p []int) (err error) { func Pipe(p []int) (err error) {

View File

@ -54,18 +54,6 @@ func nametomib(name string) (mib []_C_int, err error) {
return buf[0 : n/siz], nil return buf[0 : n/siz], nil
} }
func direntIno(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Fileno), unsafe.Sizeof(Dirent{}.Fileno))
}
func direntReclen(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Reclen), unsafe.Sizeof(Dirent{}.Reclen))
}
func direntNamlen(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Namlen), unsafe.Sizeof(Dirent{}.Namlen))
}
//sysnb pipe() (r int, w int, err error) //sysnb pipe() (r int, w int, err error)
func Pipe(p []int) (err error) { func Pipe(p []int) (err error) {

View File

@ -1197,22 +1197,6 @@ func ReadDirent(fd int, buf []byte) (n int, err error) {
return Getdents(fd, buf) return Getdents(fd, buf)
} }
func direntIno(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Ino), unsafe.Sizeof(Dirent{}.Ino))
}
func direntReclen(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Reclen), unsafe.Sizeof(Dirent{}.Reclen))
}
func direntNamlen(buf []byte) (uint64, bool) {
reclen, ok := direntReclen(buf)
if !ok {
return 0, false
}
return reclen - uint64(unsafe.Offsetof(Dirent{}.Name)), true
}
//sys mount(source string, target string, fstype string, flags uintptr, data *byte) (err error) //sys mount(source string, target string, fstype string, flags uintptr, data *byte) (err error)
func Mount(source string, target string, fstype string, flags uintptr, data string) (err error) { func Mount(source string, target string, fstype string, flags uintptr, data string) (err error) {

View File

@ -92,18 +92,6 @@ func nametomib(name string) (mib []_C_int, err error) {
return mib, nil return mib, nil
} }
func direntIno(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Fileno), unsafe.Sizeof(Dirent{}.Fileno))
}
func direntReclen(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Reclen), unsafe.Sizeof(Dirent{}.Reclen))
}
func direntNamlen(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Namlen), unsafe.Sizeof(Dirent{}.Namlen))
}
//sysnb pipe() (fd1 int, fd2 int, err error) //sysnb pipe() (fd1 int, fd2 int, err error)
func Pipe(p []int) (err error) { func Pipe(p []int) (err error) {
if len(p) != 2 { if len(p) != 2 {

View File

@ -42,18 +42,6 @@ func nametomib(name string) (mib []_C_int, err error) {
return nil, EINVAL return nil, EINVAL
} }
func direntIno(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Fileno), unsafe.Sizeof(Dirent{}.Fileno))
}
func direntReclen(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Reclen), unsafe.Sizeof(Dirent{}.Reclen))
}
func direntNamlen(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Namlen), unsafe.Sizeof(Dirent{}.Namlen))
}
//sysnb pipe(p *[2]_C_int) (err error) //sysnb pipe(p *[2]_C_int) (err error)
func Pipe(p []int) (err error) { func Pipe(p []int) (err error) {
if len(p) != 2 { if len(p) != 2 {

View File

@ -34,22 +34,6 @@ type SockaddrDatalink struct {
raw RawSockaddrDatalink raw RawSockaddrDatalink
} }
func direntIno(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Ino), unsafe.Sizeof(Dirent{}.Ino))
}
func direntReclen(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Reclen), unsafe.Sizeof(Dirent{}.Reclen))
}
func direntNamlen(buf []byte) (uint64, bool) {
reclen, ok := direntReclen(buf)
if !ok {
return 0, false
}
return reclen - uint64(unsafe.Offsetof(Dirent{}.Name)), true
}
//sysnb pipe(p *[2]_C_int) (n int, err error) //sysnb pipe(p *[2]_C_int) (n int, err error)
func Pipe(p []int) (err error) { func Pipe(p []int) (err error) {

View File

@ -500,6 +500,6 @@ const (
) )
// Version is the current grpc version. // Version is the current grpc version.
const Version = "1.9.1" const Version = "1.9.2"
const grpcUA = "grpc-go/" + Version const grpcUA = "grpc-go/" + Version

View File

@ -645,6 +645,8 @@ func (t *http2Client) Write(s *Stream, hdr []byte, data []byte, opts *Options) e
select { select {
case <-s.ctx.Done(): case <-s.ctx.Done():
return ContextErr(s.ctx.Err()) return ContextErr(s.ctx.Err())
case <-s.done:
return io.EOF
case <-t.ctx.Done(): case <-t.ctx.Done():
return ErrConnClosing return ErrConnClosing
default: default:
@ -1110,12 +1112,13 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) {
}() }()
s.mu.Lock() s.mu.Lock()
if !endStream {
s.recvCompress = state.encoding
}
if !s.headerDone { if !s.headerDone {
if !endStream && len(state.mdata) > 0 { // Headers frame is not actually a trailers-only frame.
s.header = state.mdata if !endStream {
s.recvCompress = state.encoding
if len(state.mdata) > 0 {
s.header = state.mdata
}
} }
close(s.headerChan) close(s.headerChan)
s.headerDone = true s.headerDone = true