Tests can now use config files (#36)

This commit is contained in:
konrad 2018-12-18 16:01:46 +00:00 committed by Gitea
parent 67e94f95b0
commit 97febdb397
58 changed files with 16740 additions and 7785 deletions

View file

@ -8,6 +8,13 @@ clone:
depth: 50 depth: 50
tags: true tags: true
services:
database:
image: mariadb:10
environment:
- MYSQL_ROOT_PASSWORD=vikunjatest
- MYSQL_DATABASE=vikunjatest
pipeline: pipeline:
build: build:
image: vikunja/golang-build:latest image: vikunja/golang-build:latest
@ -27,12 +34,41 @@ pipeline:
test: test:
image: vikunja/golang-build:latest image: vikunja/golang-build:latest
group: test
pull: true pull: true
commands: commands:
- make test - make test
when: when:
event: [ push, tag, pull_request ] event: [ push, tag, pull_request ]
test-sqlite:
image: vikunja/golang-build:latest
group: test
pull: true
environment:
- VIKUNJA_TESTS_USE_CONFIG=1
- VIKUNJA_DATABASE_TYPE=sqlite
commands:
- make test
when:
event: [ push, tag, pull_request ]
test-mysql:
image: vikunja/golang-build:latest
group: test
pull: true
environment:
- VIKUNJA_TESTS_USE_CONFIG=1
- VIKUNJA_DATABASE_TYPE=mysql
- VIKUNJA_DATABASE_HOST=database
- VIKUNJA_DATABASE_USER=root
- VIKUNJA_DATABASE_PASSWORD=vikunjatest
- VIKUNJA_DATABASE_DATABASE=vikunjatest
commands:
- make test
when:
event: [ push, tag, pull_request ]
# Build a release when tagging # Build a release when tagging
before-static-build: before-static-build:
image: karalabe/xgo-latest:latest image: karalabe/xgo-latest:latest
@ -159,3 +195,6 @@ pipeline:
{{repo.owner}}/{{repo.name}}: \[{{build.status}}] Build {{build.number}} {{repo.owner}}/{{repo.name}}: \[{{build.status}}] Build {{build.number}}
{{commit.author}} pushed to {{commit.branch}} {{commit.sha}}: `{{commit.message}}` {{commit.author}} pushed to {{commit.branch}} {{commit.sha}}: `{{commit.message}}`
Build started at {{datetime build.started "2006-Jan-02T15:04:05Z" "GMT+2"}} finished at {{datetime build.finished "2006-Jan-02T15:04:05Z" "GMT+2"}}. Build started at {{datetime build.started "2006-Jan-02T15:04:05Z" "GMT+2"}} finished at {{datetime build.finished "2006-Jan-02T15:04:05Z" "GMT+2"}}.
when:
event: [ push, tag ]
branch: [ master ]

View file

@ -21,7 +21,7 @@ EXTRA_GOFLAGS ?=
LDFLAGS := -X "main.Version=$(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')" -X "main.Tags=$(TAGS)" LDFLAGS := -X "main.Version=$(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')" -X "main.Tags=$(TAGS)"
PACKAGES ?= $(filter-out code.vikunja.io/api/integrations,$(shell go list ./... | grep -v /vendor/)) PACKAGES ?= $(filter-out code.vikunja.io/api/integrations,$(shell go list -mod=vendor ./... | grep -v /vendor/))
SOURCES ?= $(shell find . -name "*.go" -type f) SOURCES ?= $(shell find . -name "*.go" -type f)
TAGS ?= TAGS ?=
@ -56,7 +56,7 @@ clean:
.PHONY: test .PHONY: test
test: test:
go test -cover -coverprofile cover.out $(PACKAGES) VIKUNJA_SERVICE_ROOTPATH=$(shell pwd) go test $(GOFLAGS) -cover -coverprofile cover.out $(PACKAGES)
go tool cover -html=cover.out -o cover.html go tool cover -html=cover.out -o cover.html
required-gofmt-version: required-gofmt-version:

View file

@ -7,11 +7,15 @@ service:
interface: ":3456" interface: ":3456"
# The URL of the frontend, used to send password reset emails. # The URL of the frontend, used to send password reset emails.
frontendurl: "" frontendurl: ""
# The base path on the file system where the binary and assets are.
# Vikunja will also look in this path for a config file, so you could provide only this variable to point to a folder
# with a config file which will then be used.
rootpath: <the path of the executable>
# The number of items which gets returned per page # The number of items which gets returned per page
pagecount: 50 pagecount: 50
# If set to true, enables a /metrics endpoint for prometheus to collect metrics about the system # If set to true, enables a /metrics endpoint for prometheus to collect metrics about the system
# You'll need to use redis for this in order to enable common metrics over multiple nodes # You'll need to use redis for this in order to enable common metrics over multiple nodes
enablemetrics: true enablemetrics: false
database: database:
# Database type to use. Supported types are mysql and sqlite. # Database type to use. Supported types are mysql and sqlite.
@ -31,6 +35,14 @@ database:
# Sets the max open connections to the database. Only used when using mysql. # Sets the max open connections to the database. Only used when using mysql.
openconnections: 100 openconnections: 100
cache:
# If cache is enabled or not
enabled: false
# Cache type. Possible values are memory or redis, you'll need to enable redis below when using redis
type: memory
# When using memory this defines the maximum size an element can take
maxelementsize: 1000
redis: redis:
# Whether to enable redis or not # Whether to enable redis or not
enabled: false enabled: false
@ -41,24 +53,20 @@ redis:
# 0 means default database # 0 means default database
db: 0 db: 0
cache:
# If cache is enabled or not
enabled: false
# Cache type. Possible values are memory or redis, you'll need to enable redis below when using redis
type: memory
# When using memory this defines the maximum size an element can take
maxelementsize: 1000
mailer: mailer:
# SMTP Host # SMTP Host
host: '' host: ""
# SMTP Host port # SMTP Host port
port: 587 port: 587
# SMTP username # SMTP username
username: 'user' username: "user"
# SMTP password # SMTP password
password: '' password: ""
# Wether to skip verification of the tls certificate on the server # Wether to skip verification of the tls certificate on the server
skiptlsverify: false skiptlsverify: false
# The default from address when sending emails # The default from address when sending emails
fromemail: 'mail@vikunja' fromemail: "mail@vikunja"
# The length of the mail queue.
queuelength: 100
# The timeout in seconds after which the current open connection to the mailserver will be closed.
queuetimeout: 30

View file

@ -19,7 +19,7 @@ first:
# Default configuration with explanations # Default configuration with explanations
This is the same as the `config.yaml` file you'll find in the root of vikunja. This is the same as the `config.yml.sample` file you'll find in the root of vikunja.
```yaml ```yaml
service: service:
@ -31,7 +31,9 @@ service:
interface: ":3456" interface: ":3456"
# The URL of the frontend, used to send password reset emails. # The URL of the frontend, used to send password reset emails.
frontendurl: "" frontendurl: ""
# The base path on the file system where the binary and assets are # The base path on the file system where the binary and assets are.
# Vikunja will also look in this path for a config file, so you could provide only this variable to point to a folder
# with a config file which will then be used.
rootpath: <the path of the executable> rootpath: <the path of the executable>
# The number of items which gets returned per page # The number of items which gets returned per page
pagecount: 50 pagecount: 50

17
docs/test.md Normal file
View file

@ -0,0 +1,17 @@
# Testing
You can run unit tests with our `Makefile` with
```bash
make test
```
### Running tests with config
You can run tests with all available config variables if you want, enabeling you to run tests for a lot of scenarios.
To use the normal config set the enviroment variable `VIKUNJA_TESTS_USE_CONFIG=1`.
### Show sql queries
When `UNIT_TESTS_VERBOSE=1` is set, all sql queries will be shown when tests are run.

21
go.mod
View file

@ -17,13 +17,12 @@
module code.vikunja.io/api module code.vikunja.io/api
require ( require (
cloud.google.com/go v0.30.0 // indirect cloud.google.com/go v0.34.0 // indirect
code.vikunja.io/web v0.0.0-20181130231148-b061c20192fb code.vikunja.io/web v0.0.0-20181130231148-b061c20192fb
github.com/BurntSushi/toml v0.3.1 // indirect github.com/BurntSushi/toml v0.3.1 // indirect
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf
github.com/client9/misspell v0.3.4 github.com/client9/misspell v0.3.4
github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835
github.com/garyburd/redigo v1.6.0 // indirect github.com/garyburd/redigo v1.6.0 // indirect
@ -31,23 +30,22 @@ require (
github.com/go-openapi/spec v0.17.2 // indirect github.com/go-openapi/spec v0.17.2 // indirect
github.com/go-openapi/swag v0.17.2 // indirect github.com/go-openapi/swag v0.17.2 // indirect
github.com/go-redis/redis v6.14.2+incompatible github.com/go-redis/redis v6.14.2+incompatible
github.com/go-sql-driver/mysql v0.0.0-20171007150158-ee359f95877b github.com/go-sql-driver/mysql v1.4.1
github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25 // indirect github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25 // indirect
github.com/go-xorm/core v0.5.8 github.com/go-xorm/core v0.5.8
github.com/go-xorm/tests v0.5.6 // indirect github.com/go-xorm/tests v0.5.6 // indirect
github.com/go-xorm/xorm v0.0.0-20170930012613-29d4a0330a00 github.com/go-xorm/xorm v0.0.0-20170930012613-29d4a0330a00
github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2 github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2
github.com/google/go-cmp v0.2.0 // indirect
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc
github.com/imdario/mergo v0.3.6 github.com/imdario/mergo v0.3.6
github.com/joho/godotenv v1.3.0 // indirect
github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970 github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970
github.com/labstack/echo v3.3.5+incompatible github.com/labstack/echo v3.3.5+incompatible
github.com/labstack/gommon v0.2.8 github.com/labstack/gommon v0.2.8
github.com/lib/pq v1.0.0 // indirect github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6 // indirect
github.com/mattn/go-oci8 v0.0.0-20181011085415-1a014d1384b5 // indirect github.com/mattn/go-sqlite3 v1.10.0
github.com/mattn/go-sqlite3 v1.9.0
github.com/mitchellh/mapstructure v1.1.2 // indirect github.com/mitchellh/mapstructure v1.1.2 // indirect
github.com/onsi/ginkgo v1.7.0 // indirect
github.com/onsi/gomega v1.4.3 // indirect
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pkg/errors v0.8.0 // indirect github.com/pkg/errors v0.8.0 // indirect
github.com/prometheus/client_golang v0.9.2 github.com/prometheus/client_golang v0.9.2
@ -59,10 +57,13 @@ require (
github.com/swaggo/swag v1.4.1-0.20181129020348-1c8533a91397 github.com/swaggo/swag v1.4.1-0.20181129020348-1c8533a91397
github.com/urfave/cli v1.20.0 // indirect github.com/urfave/cli v1.20.0 // indirect
github.com/ziutek/mymysql v1.5.4 // indirect github.com/ziutek/mymysql v1.5.4 // indirect
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3
golang.org/x/net v0.0.0-20181217023233-e147a9138326 // indirect
golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081 // indirect golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/testfixtures.v2 v2.4.5 gopkg.in/testfixtures.v2 v2.5.3
gopkg.in/yaml.v2 v2.2.2 // indirect
) )

37
go.sum
View file

@ -1,5 +1,7 @@
cloud.google.com/go v0.30.0 h1:xKvyLgk56d0nksWq49J0UyGEeUIicTl4+UBiX1NPX9g= cloud.google.com/go v0.30.0 h1:xKvyLgk56d0nksWq49J0UyGEeUIicTl4+UBiX1NPX9g=
cloud.google.com/go v0.30.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.30.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
code.vikunja.io/web v0.0.0-20181130221802-d23d2a4c1efb h1:OyXe+z31V3xUUEOv/kVJmkfp/9y/YdlaKjfhGpDAmNQ= code.vikunja.io/web v0.0.0-20181130221802-d23d2a4c1efb h1:OyXe+z31V3xUUEOv/kVJmkfp/9y/YdlaKjfhGpDAmNQ=
code.vikunja.io/web v0.0.0-20181130221802-d23d2a4c1efb/go.mod h1:PmGEu9qI7nbEKDn38H0SWgCoGO4GLdbjdlnWSzFi2PA= code.vikunja.io/web v0.0.0-20181130221802-d23d2a4c1efb/go.mod h1:PmGEu9qI7nbEKDn38H0SWgCoGO4GLdbjdlnWSzFi2PA=
code.vikunja.io/web v0.0.0-20181130231148-b061c20192fb h1:LROmRUOGTxOpOxKy9S6XONDnT+t0v0j8+MZCedssTCc= code.vikunja.io/web v0.0.0-20181130231148-b061c20192fb h1:LROmRUOGTxOpOxKy9S6XONDnT+t0v0j8+MZCedssTCc=
@ -23,6 +25,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6 h1:BZGp1dbKFjqlGmxEpwkDpCWNxVwEYnUPoncIzLiHlPo= github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6 h1:BZGp1dbKFjqlGmxEpwkDpCWNxVwEYnUPoncIzLiHlPo=
github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
@ -46,6 +49,8 @@ github.com/go-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiN
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v0.0.0-20171007150158-ee359f95877b h1:/CMGgAYard7jx9+bI7tUIqafFDR7Pv2BRu2Tb5dDaqM= github.com/go-sql-driver/mysql v0.0.0-20171007150158-ee359f95877b h1:/CMGgAYard7jx9+bI7tUIqafFDR7Pv2BRu2Tb5dDaqM=
github.com/go-sql-driver/mysql v0.0.0-20171007150158-ee359f95877b/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v0.0.0-20171007150158-ee359f95877b/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25 h1:jUX9yw6+iKrs/WuysV2M6ap/ObK/07SE/a7I2uxitwM= github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25 h1:jUX9yw6+iKrs/WuysV2M6ap/ObK/07SE/a7I2uxitwM=
github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25/go.mod h1:M+P3wv0K2C+ynucGDEqJCeOTc+6DcAtiiqU8GrCksXY= github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25/go.mod h1:M+P3wv0K2C+ynucGDEqJCeOTc+6DcAtiiqU8GrCksXY=
github.com/go-xorm/core v0.5.8 h1:vQ0ghlVGnlnFmm4SpHY+xNnPlH810paMcw+Hwz9BCqE= github.com/go-xorm/core v0.5.8 h1:vQ0ghlVGnlnFmm4SpHY+xNnPlH810paMcw+Hwz9BCqE=
@ -64,6 +69,8 @@ github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc h1:cJlkeAx
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
@ -89,14 +96,23 @@ github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-oci8 v0.0.0-20181011085415-1a014d1384b5 h1:+IPgoz43mdEYG5lrqNcjr3DQpAE38SqHtyx1IsqqQGM= github.com/mattn/go-oci8 v0.0.0-20181011085415-1a014d1384b5 h1:+IPgoz43mdEYG5lrqNcjr3DQpAE38SqHtyx1IsqqQGM=
github.com/mattn/go-oci8 v0.0.0-20181011085415-1a014d1384b5/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8= github.com/mattn/go-oci8 v0.0.0-20181011085415-1a014d1384b5/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
github.com/mattn/go-oci8 v0.0.0-20181115070430-6eefff3c767c/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I= github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I=
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
@ -144,17 +160,27 @@ github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50w
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI= golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 h1:x/bBzNauLQAlE3fLku/xy92Y8QwKX5HZymrMz2IiKFc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 h1:x/bBzNauLQAlE3fLku/xy92Y8QwKX5HZymrMz2IiKFc=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58 h1:otZG8yDCO4LVps5+9bxOeNiCvgmOyt96J3roHTYs7oE= golang.org/x/net v0.0.0-20181005035420-146acd28ed58 h1:otZG8yDCO4LVps5+9bxOeNiCvgmOyt96J3roHTYs7oE=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181217023233-e147a9138326 h1:iCzOf0xz39Tstp+Tu/WwyGjUXCk34QhQORRxBeXXTA4=
golang.org/x/net v0.0.0-20181217023233-e147a9138326/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 h1:BH3eQWeGbwRU2+wxxuuPOdFBmaiBH81O8BugSjHeTFg= golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 h1:BH3eQWeGbwRU2+wxxuuPOdFBmaiBH81O8BugSjHeTFg=
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU= golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -162,13 +188,24 @@ golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081 h1:QJP9sxq2/KbTxFnGduVryxJOt6r/UVGyom3tLaqu7tc= golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081 h1:QJP9sxq2/KbTxFnGduVryxJOt6r/UVGyom3tLaqu7tc=
golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/testfixtures.v2 v2.4.5 h1:mnfYPBNoJnis+4crs6UzC4lv4GjTVoLXE9B/tW802q0= gopkg.in/testfixtures.v2 v2.4.5 h1:mnfYPBNoJnis+4crs6UzC4lv4GjTVoLXE9B/tW802q0=
gopkg.in/testfixtures.v2 v2.4.5/go.mod h1:vyAq+MYCgNpR29qitQdLZhdbLFf4mR/2MFJRFoQZZ2M= gopkg.in/testfixtures.v2 v2.4.5/go.mod h1:vyAq+MYCgNpR29qitQdLZhdbLFf4mR/2MFJRFoQZZ2M=
gopkg.in/testfixtures.v2 v2.5.3 h1:P8gDACSLJGxutzBqbzvfiXYgmQ2s00LIr4uAvWBCPAg=
gopkg.in/testfixtures.v2 v2.5.3/go.mod h1:rGPtsOtPcZhs7AsHYf1WmufW1hEsM6DXdLrYz60nrQQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -40,6 +40,7 @@ func init() {
viper.SetDefault("service.JWTSecret", random) viper.SetDefault("service.JWTSecret", random)
viper.SetDefault("service.interface", ":3456") viper.SetDefault("service.interface", ":3456")
viper.SetDefault("service.frontendurl", "") viper.SetDefault("service.frontendurl", "")
ex, err := os.Executable() ex, err := os.Executable()
if err != nil { if err != nil {
panic(err) panic(err)
@ -82,6 +83,8 @@ func init() {
viper.AutomaticEnv() viper.AutomaticEnv()
// Load the config file // Load the config file
viper.AddConfigPath(exPath)
viper.AddConfigPath(viper.GetString("service.rootpath"))
viper.AddConfigPath(".") viper.AddConfigPath(".")
viper.SetConfigName("config") viper.SetConfigName("config")
err = viper.ReadInConfig() err = viper.ReadInConfig()

View file

@ -14,10 +14,29 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2018 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package models package models
import "testing" import (
"github.com/spf13/viper"
"testing"
)
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
MainTest(m, "..") MainTest(m, viper.GetString("service.rootpath"))
} }

View file

@ -1,29 +0,0 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2018 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package models
import (
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"testing"
)
func TestSetEngine(t *testing.T) {
viper.Set("database.path", "file::memory:?cache=shared")
err := SetEngine()
assert.NoError(t, err)
}

View file

@ -23,6 +23,7 @@ import (
"fmt" "fmt"
"github.com/go-xorm/core" "github.com/go-xorm/core"
"github.com/go-xorm/xorm" "github.com/go-xorm/xorm"
"github.com/spf13/viper"
"gopkg.in/testfixtures.v2" "gopkg.in/testfixtures.v2"
"os" "os"
"path/filepath" "path/filepath"
@ -36,7 +37,7 @@ var IsTesting bool
// MainTest creates the test engine // MainTest creates the test engine
func MainTest(m *testing.M, pathToRoot string) { func MainTest(m *testing.M, pathToRoot string) {
var err error var err error
fixturesDir := filepath.Join(pathToRoot, "models", "fixtures") fixturesDir := filepath.Join(pathToRoot, "pkg", "models", "fixtures")
if err = createTestEngine(fixturesDir); err != nil { if err = createTestEngine(fixturesDir); err != nil {
log.Log.Fatalf("Error creating test engine: %v\n", err) log.Log.Fatalf("Error creating test engine: %v\n", err)
} }
@ -48,7 +49,7 @@ func MainTest(m *testing.M, pathToRoot string) {
// Create test database // Create test database
if err = PrepareTestDatabase(); err != nil { if err = PrepareTestDatabase(); err != nil {
log.Log.Fatal(err.Error()) log.Log.Fatalf("Error preparing test database: %v", err.Error())
} }
os.Exit(m.Run()) os.Exit(m.Run())
@ -56,24 +57,39 @@ func MainTest(m *testing.M, pathToRoot string) {
func createTestEngine(fixturesDir string) error { func createTestEngine(fixturesDir string) error {
var err error var err error
x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared") // If set, use the config we provided instead of normal
//x, err = xorm.NewEngine("sqlite3", "db.db") if os.Getenv("VIKUNJA_TESTS_USE_CONFIG") == "1" {
if err != nil { err = SetEngine()
return err if err != nil {
} return err
x.SetMapper(core.GonicMapper{}) }
} else {
x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared")
if err != nil {
return err
}
// Sync dat shit x.SetMapper(core.GonicMapper{})
if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil {
return fmt.Errorf("sync database struct error: %v", err) // Sync dat shit
if err := x.StoreEngine("InnoDB").Sync2(tables...); err != nil {
return fmt.Errorf("sync database struct error: %v", err)
}
// Show SQL-Queries if necessary
if os.Getenv("UNIT_TESTS_VERBOSE") == "1" {
x.ShowSQL(true)
}
} }
// Show SQL-Queries if necessary var fixturesHelper testfixtures.Helper
if os.Getenv("UNIT_TESTS_VERBOSE") == "1" { if viper.GetString("database.type") == "mysql" {
x.ShowSQL(true) fixturesHelper = &testfixtures.MySQL{}
} else {
fixturesHelper = &testfixtures.SQLite{}
} }
return InitFixtures(&testfixtures.SQLite{}, fixturesDir) return InitFixtures(fixturesHelper, fixturesDir)
} }
// PrepareTestDatabase load test fixtures into test database // PrepareTestDatabase load test fixtures into test database

View file

@ -1,12 +1,11 @@
sudo: false sudo: false
language: go language: go
go: go:
- 1.5 - 1.7.x
- 1.6 - 1.8.x
- 1.7 - 1.9.x
- 1.8 - 1.10.x
- 1.9 - master
- tip
before_install: before_install:
- go get golang.org/x/tools/cmd/cover - go get golang.org/x/tools/cmd/cover
@ -20,10 +19,30 @@ before_script:
matrix: matrix:
include: include:
- env: DB=MYSQL8
sudo: required
dist: trusty
go: 1.10.x
services:
- docker
before_install:
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
- docker pull mysql:8.0
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
mysql:8.0 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
- cp .travis/docker.cnf ~/.my.cnf
- .travis/wait_mysql.sh
before_script:
- export MYSQL_TEST_USER=gotest
- export MYSQL_TEST_PASS=secret
- export MYSQL_TEST_ADDR=127.0.0.1:3307
- export MYSQL_TEST_CONCURRENT=1
- env: DB=MYSQL57 - env: DB=MYSQL57
sudo: required sudo: required
dist: trusty dist: trusty
go: 1.9 go: 1.10.x
services: services:
- docker - docker
before_install: before_install:
@ -31,10 +50,8 @@ matrix:
- go get github.com/mattn/goveralls - go get github.com/mattn/goveralls
- docker pull mysql:5.7 - docker pull mysql:5.7
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
mysql:5.7 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB mysql:5.7 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
- sleep 30
- cp .travis/docker.cnf ~/.my.cnf - cp .travis/docker.cnf ~/.my.cnf
- mysql --print-defaults
- .travis/wait_mysql.sh - .travis/wait_mysql.sh
before_script: before_script:
- export MYSQL_TEST_USER=gotest - export MYSQL_TEST_USER=gotest
@ -45,7 +62,7 @@ matrix:
- env: DB=MARIA55 - env: DB=MARIA55
sudo: required sudo: required
dist: trusty dist: trusty
go: 1.9 go: 1.10.x
services: services:
- docker - docker
before_install: before_install:
@ -53,10 +70,8 @@ matrix:
- go get github.com/mattn/goveralls - go get github.com/mattn/goveralls
- docker pull mariadb:5.5 - docker pull mariadb:5.5
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
mariadb:5.5 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB mariadb:5.5 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
- sleep 30
- cp .travis/docker.cnf ~/.my.cnf - cp .travis/docker.cnf ~/.my.cnf
- mysql --print-defaults
- .travis/wait_mysql.sh - .travis/wait_mysql.sh
before_script: before_script:
- export MYSQL_TEST_USER=gotest - export MYSQL_TEST_USER=gotest
@ -67,7 +82,7 @@ matrix:
- env: DB=MARIA10_1 - env: DB=MARIA10_1
sudo: required sudo: required
dist: trusty dist: trusty
go: 1.9 go: 1.10.x
services: services:
- docker - docker
before_install: before_install:
@ -75,10 +90,8 @@ matrix:
- go get github.com/mattn/goveralls - go get github.com/mattn/goveralls
- docker pull mariadb:10.1 - docker pull mariadb:10.1
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
mariadb:10.1 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB mariadb:10.1 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
- sleep 30
- cp .travis/docker.cnf ~/.my.cnf - cp .travis/docker.cnf ~/.my.cnf
- mysql --print-defaults
- .travis/wait_mysql.sh - .travis/wait_mysql.sh
before_script: before_script:
- export MYSQL_TEST_USER=gotest - export MYSQL_TEST_USER=gotest
@ -89,5 +102,6 @@ matrix:
script: script:
- go test -v -covermode=count -coverprofile=coverage.out - go test -v -covermode=count -coverprofile=coverage.out
- go vet ./... - go vet ./...
- test -z "$(gofmt -d -s . | tee /dev/stderr)" - .travis/gofmt.sh
after_script:
- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci

View file

@ -13,11 +13,15 @@
Aaron Hopkins <go-sql-driver at die.net> Aaron Hopkins <go-sql-driver at die.net>
Achille Roussel <achille.roussel at gmail.com> Achille Roussel <achille.roussel at gmail.com>
Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
Andrew Reid <andrew.reid at tixtrack.com>
Arne Hormann <arnehormann at gmail.com> Arne Hormann <arnehormann at gmail.com>
Asta Xie <xiemengjun at gmail.com> Asta Xie <xiemengjun at gmail.com>
Bulat Gaifullin <gaifullinbf 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>
Craig Wilson <craiggwilson at gmail.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> Dave Protasowski <dprotaso at gmail.com>
@ -26,6 +30,7 @@ Egor Smolyakov <egorsmkv at gmail.com>
Evan Shaw <evan at vendhq.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>
Hajime Nakagami <nakagami 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>
@ -34,20 +39,24 @@ INADA Naoki <songofacandy at gmail.com>
Jacek Szwec <szwec.jacek 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> 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> 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> 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> Maciej Zimnoch <maciej.zimnoch at 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>
@ -55,6 +64,9 @@ oscarzhao <oscarzhaosl at gmail.com>
Paul Bonser <misterpib at gmail.com> Paul Bonser <misterpib at gmail.com>
Peter Schultz <peter.schultz at classmarkets.com> Peter Schultz <peter.schultz at classmarkets.com>
Rebecca Chin <rchin at pivotal.io> Rebecca Chin <rchin at pivotal.io>
Reed Allman <rdallman10 at gmail.com>
Richard Wilkes <wilkes at me.com>
Robert Russell <robert at rrbrussell.com>
Runrioter Wung <runrioter at gmail.com> Runrioter Wung <runrioter at gmail.com>
Shuode Li <elemount at qq.com> Shuode Li <elemount at qq.com>
Soroush Pour <me at soroushjp.com> Soroush Pour <me at soroushjp.com>
@ -68,7 +80,10 @@ Zhenye Xie <xiezhenye at gmail.com>
# Organizations # Organizations
Barracuda Networks, Inc. Barracuda Networks, Inc.
Counting Ltd.
Google Inc. Google Inc.
InfoSum Ltd.
Keybase Inc. Keybase Inc.
Percona LLC
Pivotal Inc. Pivotal Inc.
Stripe Inc. Stripe Inc.

View file

@ -1,3 +1,62 @@
## Version 1.4.1 (2018-11-14)
Bugfixes:
- Fix TIME format for binary columns (#818)
- Fix handling of empty auth plugin names (#835)
- Fix caching_sha2_password with empty password (#826)
- Fix canceled context broke mysqlConn (#862)
- Fix OldAuthSwitchRequest support (#870)
- Fix Auth Response packet for cleartext password (#887)
## Version 1.4 (2018-06-03)
Changes:
- Documentation fixes (#530, #535, #567)
- Refactoring (#575, #579, #580, #581, #603, #615, #704)
- Cache column names (#444)
- Sort the DSN parameters in DSNs generated from a config (#637)
- Allow native password authentication by default (#644)
- Use the default port if it is missing in the DSN (#668)
- Removed the `strict` mode (#676)
- Do not query `max_allowed_packet` by default (#680)
- Dropped support Go 1.6 and lower (#696)
- Updated `ConvertValue()` to match the database/sql/driver implementation (#760)
- Document the usage of `0000-00-00T00:00:00` as the time.Time zero value (#783)
- Improved the compatibility of the authentication system (#807)
New Features:
- Multi-Results support (#537)
- `rejectReadOnly` DSN option (#604)
- `context.Context` support (#608, #612, #627, #761)
- Transaction isolation level support (#619, #744)
- Read-Only transactions support (#618, #634)
- `NewConfig` function which initializes a config with default values (#679)
- Implemented the `ColumnType` interfaces (#667, #724)
- Support for custom string types in `ConvertValue` (#623)
- Implemented `NamedValueChecker`, improving support for uint64 with high bit set (#690, #709, #710)
- `caching_sha2_password` authentication plugin support (#794, #800, #801, #802)
- Implemented `driver.SessionResetter` (#779)
- `sha256_password` authentication plugin support (#808)
Bugfixes:
- Use the DSN hostname as TLS default ServerName if `tls=true` (#564, #718)
- Fixed LOAD LOCAL DATA INFILE for empty files (#590)
- Removed columns definition cache since it sometimes cached invalid data (#592)
- Don't mutate registered TLS configs (#600)
- Make RegisterTLSConfig concurrency-safe (#613)
- Handle missing auth data in the handshake packet correctly (#646)
- Do not retry queries when data was written to avoid data corruption (#302, #736)
- Cache the connection pointer for error handling before invalidating it (#678)
- Fixed imports for appengine/cloudsql (#700)
- Fix sending STMT_LONG_DATA for 0 byte data (#734)
- Set correct capacity for []bytes read from length-encoded strings (#766)
- Make RegisterDial concurrency-safe (#773)
## Version 1.3 (2016-12-01) ## Version 1.3 (2016-12-01)
Changes: Changes:

View file

@ -16,10 +16,11 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
* [Parameters](#parameters) * [Parameters](#parameters)
* [Examples](#examples) * [Examples](#examples)
* [Connection pool and timeouts](#connection-pool-and-timeouts) * [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)
* [context.Context Support](#contextcontext-support)
* [Testing / Development](#testing--development) * [Testing / Development](#testing--development)
* [License](#license) * [License](#license)
@ -39,7 +40,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
* Optional placeholder interpolation * Optional placeholder interpolation
## Requirements ## Requirements
* Go 1.5 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+)
--------------------------------------- ---------------------------------------
@ -232,10 +233,10 @@ Please keep in mind, that param values must be [url.QueryEscape](https://golang.
##### `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`
@ -258,6 +259,7 @@ Default: false
``` ```
`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string` `parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
The date or datetime like `0000-00-00 00:00:00` is converted into zero value of `time.Time`.
##### `readTimeout` ##### `readTimeout`
@ -278,7 +280,7 @@ Default: false
``` ```
`rejectreadOnly=true` causes the driver to reject read-only connections. This `rejectReadOnly=true` causes the driver to reject read-only connections. This
is for a possible race condition during an automatic failover, where the mysql is for a possible race condition during an automatic failover, where the mysql
client gets connected to a read-only replica after the failover. client gets connected to a read-only replica after the failover.
@ -293,6 +295,24 @@ 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) supposed to happen, setting this on some MySQL providers (such as AWS Aurora)
is safer for failovers. is safer for failovers.
Note that ERROR 1290 can be returned for a `read-only` server and this option will
cause a retry for that error. However the same error number is used for some
other cases. You should ensure your application will never cause an ERROR 1290
except for `read-only` mode when enabling this option.
##### `serverPubKey`
```
Type: string
Valid Values: <name>
Default: none
```
Server public keys can be registered with [`mysql.RegisterServerPubKey`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterServerPubKey), which can then be used by the assigned name in the DSN.
Public keys are used to transmit encrypted data, e.g. for authentication.
If the server's public key is known, it should be set manually to avoid expensive and potentially insecure transmissions of the public key from the server to the client each time it is required.
##### `timeout` ##### `timeout`
@ -400,6 +420,13 @@ user:password@/
### Connection pool and timeouts ### 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. 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 `_`):
@ -433,10 +460,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.
## `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.
## 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.

View file

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

420
vendor/github.com/go-sql-driver/mysql/auth.go generated vendored Normal file
View file

@ -0,0 +1,420 @@
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2018 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"sync"
)
// server pub keys registry
var (
serverPubKeyLock sync.RWMutex
serverPubKeyRegistry map[string]*rsa.PublicKey
)
// RegisterServerPubKey registers a server RSA public key which can be used to
// send data in a secure manner to the server without receiving the public key
// in a potentially insecure way from the server first.
// Registered keys can afterwards be used adding serverPubKey=<name> to the DSN.
//
// Note: The provided rsa.PublicKey instance is exclusively owned by the driver
// after registering it and may not be modified.
//
// data, err := ioutil.ReadFile("mykey.pem")
// if err != nil {
// log.Fatal(err)
// }
//
// block, _ := pem.Decode(data)
// if block == nil || block.Type != "PUBLIC KEY" {
// log.Fatal("failed to decode PEM block containing public key")
// }
//
// pub, err := x509.ParsePKIXPublicKey(block.Bytes)
// if err != nil {
// log.Fatal(err)
// }
//
// if rsaPubKey, ok := pub.(*rsa.PublicKey); ok {
// mysql.RegisterServerPubKey("mykey", rsaPubKey)
// } else {
// log.Fatal("not a RSA public key")
// }
//
func RegisterServerPubKey(name string, pubKey *rsa.PublicKey) {
serverPubKeyLock.Lock()
if serverPubKeyRegistry == nil {
serverPubKeyRegistry = make(map[string]*rsa.PublicKey)
}
serverPubKeyRegistry[name] = pubKey
serverPubKeyLock.Unlock()
}
// DeregisterServerPubKey removes the public key registered with the given name.
func DeregisterServerPubKey(name string) {
serverPubKeyLock.Lock()
if serverPubKeyRegistry != nil {
delete(serverPubKeyRegistry, name)
}
serverPubKeyLock.Unlock()
}
func getServerPubKey(name string) (pubKey *rsa.PublicKey) {
serverPubKeyLock.RLock()
if v, ok := serverPubKeyRegistry[name]; ok {
pubKey = v
}
serverPubKeyLock.RUnlock()
return
}
// Hash password using pre 4.1 (old password) method
// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
type myRnd struct {
seed1, seed2 uint32
}
const myRndMaxVal = 0x3FFFFFFF
// Pseudo random number generator
func newMyRnd(seed1, seed2 uint32) *myRnd {
return &myRnd{
seed1: seed1 % myRndMaxVal,
seed2: seed2 % myRndMaxVal,
}
}
// Tested to be equivalent to MariaDB's floating point variant
// http://play.golang.org/p/QHvhd4qved
// http://play.golang.org/p/RG0q4ElWDx
func (r *myRnd) NextByte() byte {
r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
return byte(uint64(r.seed1) * 31 / myRndMaxVal)
}
// Generate binary hash from byte string using insecure pre 4.1 method
func pwHash(password []byte) (result [2]uint32) {
var add uint32 = 7
var tmp uint32
result[0] = 1345345333
result[1] = 0x12345671
for _, c := range password {
// skip spaces and tabs in password
if c == ' ' || c == '\t' {
continue
}
tmp = uint32(c)
result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
result[1] += (result[1] << 8) ^ result[0]
add += tmp
}
// Remove sign bit (1<<31)-1)
result[0] &= 0x7FFFFFFF
result[1] &= 0x7FFFFFFF
return
}
// Hash password using insecure pre 4.1 method
func scrambleOldPassword(scramble []byte, password string) []byte {
if len(password) == 0 {
return nil
}
scramble = scramble[:8]
hashPw := pwHash([]byte(password))
hashSc := pwHash(scramble)
r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
var out [8]byte
for i := range out {
out[i] = r.NextByte() + 64
}
mask := r.NextByte()
for i := range out {
out[i] ^= mask
}
return out[:]
}
// Hash password using 4.1+ method (SHA1)
func scramblePassword(scramble []byte, password string) []byte {
if len(password) == 0 {
return nil
}
// stage1Hash = SHA1(password)
crypt := sha1.New()
crypt.Write([]byte(password))
stage1 := crypt.Sum(nil)
// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
// inner Hash
crypt.Reset()
crypt.Write(stage1)
hash := crypt.Sum(nil)
// outer Hash
crypt.Reset()
crypt.Write(scramble)
crypt.Write(hash)
scramble = crypt.Sum(nil)
// token = scrambleHash XOR stage1Hash
for i := range scramble {
scramble[i] ^= stage1[i]
}
return scramble
}
// Hash password using MySQL 8+ method (SHA256)
func scrambleSHA256Password(scramble []byte, password string) []byte {
if len(password) == 0 {
return nil
}
// XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble))
crypt := sha256.New()
crypt.Write([]byte(password))
message1 := crypt.Sum(nil)
crypt.Reset()
crypt.Write(message1)
message1Hash := crypt.Sum(nil)
crypt.Reset()
crypt.Write(message1Hash)
crypt.Write(scramble)
message2 := crypt.Sum(nil)
for i := range message1 {
message1[i] ^= message2[i]
}
return message1
}
func encryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte, error) {
plain := make([]byte, len(password)+1)
copy(plain, password)
for i := range plain {
j := i % len(seed)
plain[i] ^= seed[j]
}
sha1 := sha1.New()
return rsa.EncryptOAEP(sha1, rand.Reader, pub, plain, nil)
}
func (mc *mysqlConn) sendEncryptedPassword(seed []byte, pub *rsa.PublicKey) error {
enc, err := encryptPassword(mc.cfg.Passwd, seed, pub)
if err != nil {
return err
}
return mc.writeAuthSwitchPacket(enc)
}
func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
switch plugin {
case "caching_sha2_password":
authResp := scrambleSHA256Password(authData, mc.cfg.Passwd)
return authResp, nil
case "mysql_old_password":
if !mc.cfg.AllowOldPasswords {
return nil, ErrOldPassword
}
// Note: there are edge cases where this should work but doesn't;
// this is currently "wontfix":
// https://github.com/go-sql-driver/mysql/issues/184
authResp := append(scrambleOldPassword(authData[:8], mc.cfg.Passwd), 0)
return authResp, nil
case "mysql_clear_password":
if !mc.cfg.AllowCleartextPasswords {
return nil, ErrCleartextPassword
}
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
return append([]byte(mc.cfg.Passwd), 0), nil
case "mysql_native_password":
if !mc.cfg.AllowNativePasswords {
return nil, ErrNativePassword
}
// https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
// Native password authentication only need and will need 20-byte challenge.
authResp := scramblePassword(authData[:20], mc.cfg.Passwd)
return authResp, nil
case "sha256_password":
if len(mc.cfg.Passwd) == 0 {
return []byte{0}, nil
}
if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
// write cleartext auth packet
return append([]byte(mc.cfg.Passwd), 0), nil
}
pubKey := mc.cfg.pubKey
if pubKey == nil {
// request public key from server
return []byte{1}, nil
}
// encrypted password
enc, err := encryptPassword(mc.cfg.Passwd, authData, pubKey)
return enc, err
default:
errLog.Print("unknown auth plugin:", plugin)
return nil, ErrUnknownPlugin
}
}
func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
// Read Result Packet
authData, newPlugin, err := mc.readAuthResult()
if err != nil {
return err
}
// handle auth plugin switch, if requested
if newPlugin != "" {
// If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
// sent and we have to keep using the cipher sent in the init packet.
if authData == nil {
authData = oldAuthData
} else {
// copy data from read buffer to owned slice
copy(oldAuthData, authData)
}
plugin = newPlugin
authResp, err := mc.auth(authData, plugin)
if err != nil {
return err
}
if err = mc.writeAuthSwitchPacket(authResp); err != nil {
return err
}
// Read Result Packet
authData, newPlugin, err = mc.readAuthResult()
if err != nil {
return err
}
// Do not allow to change the auth plugin more than once
if newPlugin != "" {
return ErrMalformPkt
}
}
switch plugin {
// https://insidemysql.com/preparing-your-community-connector-for-mysql-8-part-2-sha256/
case "caching_sha2_password":
switch len(authData) {
case 0:
return nil // auth successful
case 1:
switch authData[0] {
case cachingSha2PasswordFastAuthSuccess:
if err = mc.readResultOK(); err == nil {
return nil // auth successful
}
case cachingSha2PasswordPerformFullAuthentication:
if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
// write cleartext auth packet
err = mc.writeAuthSwitchPacket(append([]byte(mc.cfg.Passwd), 0))
if err != nil {
return err
}
} else {
pubKey := mc.cfg.pubKey
if pubKey == nil {
// request public key from server
data := mc.buf.takeSmallBuffer(4 + 1)
data[4] = cachingSha2PasswordRequestPublicKey
mc.writePacket(data)
// parse public key
data, err := mc.readPacket()
if err != nil {
return err
}
block, _ := pem.Decode(data[1:])
pkix, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return err
}
pubKey = pkix.(*rsa.PublicKey)
}
// send encrypted password
err = mc.sendEncryptedPassword(oldAuthData, pubKey)
if err != nil {
return err
}
}
return mc.readResultOK()
default:
return ErrMalformPkt
}
default:
return ErrMalformPkt
}
case "sha256_password":
switch len(authData) {
case 0:
return nil // auth successful
default:
block, _ := pem.Decode(authData)
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return err
}
// send encrypted password
err = mc.sendEncryptedPassword(oldAuthData, pub.(*rsa.PublicKey))
if err != nil {
return err
}
return mc.readResultOK()
}
default:
return nil // auth successful
}
return err
}

View file

@ -130,18 +130,18 @@ func (b *buffer) takeBuffer(length int) []byte {
// smaller than defaultBufSize // smaller than defaultBufSize
// Only one buffer (total) can be used at a time. // Only one buffer (total) can be used at a time.
func (b *buffer) takeSmallBuffer(length int) []byte { func (b *buffer) takeSmallBuffer(length int) []byte {
if b.length == 0 { if b.length > 0 {
return b.buf[:length] return nil
} }
return nil return b.buf[:length]
} }
// takeCompleteBuffer returns the complete existing buffer. // takeCompleteBuffer returns the complete existing buffer.
// This can be used if the necessary buffer size is unknown. // This can be used if the necessary buffer size is unknown.
// Only one buffer (total) can be used at a time. // Only one buffer (total) can be used at a time.
func (b *buffer) takeCompleteBuffer() []byte { func (b *buffer) takeCompleteBuffer() []byte {
if b.length == 0 { if b.length > 0 {
return b.buf return nil
} }
return nil return b.buf
} }

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

@ -403,6 +403,7 @@ func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error)
return nil, err return nil, err
} }
} }
// Columns // Columns
rows.rs.columns, err = mc.readColumns(resLen) rows.rs.columns, err = mc.readColumns(resLen)
return rows, err return rows, err

View file

@ -17,25 +17,22 @@ import (
) )
// Ping implements driver.Pinger interface // Ping implements driver.Pinger interface
func (mc *mysqlConn) Ping(ctx context.Context) error { func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
if mc.closed.IsSet() { if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn) errLog.Print(ErrInvalidConn)
return driver.ErrBadConn return driver.ErrBadConn
} }
if err := mc.watchCancel(ctx); err != nil { if err = mc.watchCancel(ctx); err != nil {
return err return
} }
defer mc.finish() defer mc.finish()
if err := mc.writeCommandPacket(comPing); err != nil { if err = mc.writeCommandPacket(comPing); err != nil {
return err return
}
if _, err := mc.readResultOK(); err != nil {
return err
} }
return nil return mc.readResultOK()
} }
// BeginTx implements driver.ConnBeginTx interface // BeginTx implements driver.ConnBeginTx interface
@ -152,22 +149,21 @@ func (mc *mysqlConn) watchCancel(ctx context.Context) error {
mc.cleanup() mc.cleanup()
return nil return nil
} }
// When ctx is already cancelled, don't watch it.
if err := ctx.Err(); err != nil {
return err
}
// When ctx is not cancellable, don't watch it.
if ctx.Done() == nil { if ctx.Done() == nil {
return nil return nil
} }
// When watcher is not alive, can't watch it.
mc.watching = true
select {
default:
case <-ctx.Done():
return ctx.Err()
}
if mc.watcher == nil { if mc.watcher == nil {
return nil return nil
} }
mc.watching = true
mc.watcher <- ctx mc.watcher <- ctx
return nil return nil
} }
@ -195,3 +191,17 @@ func (mc *mysqlConn) startWatcher() {
} }
}() }()
} }
func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
nv.Value, err = converter{}.ConvertValue(nv.Value)
return
}
// ResetSession implements driver.SessionResetter.
// (From Go 1.10)
func (mc *mysqlConn) ResetSession(ctx context.Context) error {
if mc.closed.IsSet() {
return driver.ErrBadConn
}
return nil
}

View file

@ -9,7 +9,9 @@
package mysql package mysql
const ( const (
minProtocolVersion byte = 10 defaultAuthPlugin = "mysql_native_password"
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"
) )
@ -18,10 +20,11 @@ const (
// http://dev.mysql.com/doc/internals/en/client-server-protocol.html // http://dev.mysql.com/doc/internals/en/client-server-protocol.html
const ( const (
iOK byte = 0x00 iOK byte = 0x00
iLocalInFile byte = 0xfb iAuthMoreData byte = 0x01
iEOF byte = 0xfe iLocalInFile byte = 0xfb
iERR byte = 0xff iEOF byte = 0xfe
iERR byte = 0xff
) )
// https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags // https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
@ -87,8 +90,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 +112,7 @@ const (
fieldTypeBit fieldTypeBit
) )
const ( const (
fieldTypeJSON byte = iota + 0xf5 fieldTypeJSON fieldType = iota + 0xf5
fieldTypeNewDecimal fieldTypeNewDecimal
fieldTypeEnum fieldTypeEnum
fieldTypeSet fieldTypeSet
@ -161,3 +166,9 @@ const (
statusInTransReadonly statusInTransReadonly
statusSessionStateChanged statusSessionStateChanged
) )
const (
cachingSha2PasswordRequestPublicKey = 2
cachingSha2PasswordFastAuthSuccess = 3
cachingSha2PasswordPerformFullAuthentication = 4
)

View file

@ -20,6 +20,7 @@ import (
"database/sql" "database/sql"
"database/sql/driver" "database/sql/driver"
"net" "net"
"sync"
) )
// watcher interface is used for context support (From Go 1.8) // watcher interface is used for context support (From Go 1.8)
@ -35,12 +36,17 @@ type MySQLDriver struct{}
// Custom dial functions must be registered with RegisterDial // Custom dial functions must be registered with RegisterDial
type DialFunc func(addr string) (net.Conn, error) type DialFunc func(addr string) (net.Conn, error)
var dials map[string]DialFunc var (
dialsLock sync.RWMutex
dials map[string]DialFunc
)
// RegisterDial registers a custom dial function. It can then be used by the // RegisterDial registers a custom dial function. It can then be used by the
// network address mynet(addr), where mynet is the registered new network. // network address mynet(addr), where mynet is the registered new network.
// addr is passed as a parameter to the dial function. // addr is passed as a parameter to the dial function.
func RegisterDial(net string, dial DialFunc) { func RegisterDial(net string, dial DialFunc) {
dialsLock.Lock()
defer dialsLock.Unlock()
if dials == nil { if dials == nil {
dials = make(map[string]DialFunc) dials = make(map[string]DialFunc)
} }
@ -66,7 +72,10 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
mc.parseTime = mc.cfg.ParseTime mc.parseTime = mc.cfg.ParseTime
// Connect to Server // Connect to Server
if dial, ok := dials[mc.cfg.Net]; ok { dialsLock.RLock()
dial, ok := dials[mc.cfg.Net]
dialsLock.RUnlock()
if ok {
mc.netConn, err = dial(mc.cfg.Addr) mc.netConn, err = dial(mc.cfg.Addr)
} else { } else {
nd := net.Dialer{Timeout: mc.cfg.Timeout} nd := net.Dialer{Timeout: mc.cfg.Timeout}
@ -98,20 +107,34 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
mc.writeTimeout = mc.cfg.WriteTimeout mc.writeTimeout = mc.cfg.WriteTimeout
// Reading Handshake Initialization Packet // Reading Handshake Initialization Packet
cipher, err := mc.readInitPacket() authData, plugin, err := mc.readHandshakePacket()
if err != nil { if err != nil {
mc.cleanup() mc.cleanup()
return nil, err return nil, err
} }
if plugin == "" {
plugin = defaultAuthPlugin
}
// Send Client Authentication Packet // Send Client Authentication Packet
if err = mc.writeAuthPacket(cipher); err != nil { authResp, err := mc.auth(authData, plugin)
if err != nil {
// try the default auth plugin, if using the requested plugin failed
errLog.Print("could not use requested auth plugin '"+plugin+"': ", err.Error())
plugin = defaultAuthPlugin
authResp, err = mc.auth(authData, plugin)
if err != nil {
mc.cleanup()
return nil, err
}
}
if err = mc.writeHandshakeResponsePacket(authResp, plugin); err != nil {
mc.cleanup() mc.cleanup()
return nil, err return nil, err
} }
// Handle response to auth packet, switch methods if possible // Handle response to auth packet, switch methods if possible
if err = handleAuthResult(mc, cipher); err != nil { if err = mc.handleAuthResult(authData, plugin); err != nil {
// Authentication failed and MySQL has already closed the connection // Authentication failed and MySQL has already closed the connection
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html). // (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
// Do not send COM_QUIT, just cleanup and return the error. // Do not send COM_QUIT, just cleanup and return the error.
@ -144,50 +167,6 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
return mc, nil return mc, nil
} }
func handleAuthResult(mc *mysqlConn, oldCipher []byte) error {
// Read Result Packet
cipher, err := mc.readResultOK()
if err == nil {
return nil // auth successful
}
if mc.cfg == nil {
return err // auth failed and retry not possible
}
// Retry auth if configured to do so.
if mc.cfg.AllowOldPasswords && err == ErrOldPassword {
// Retry with old authentication method. Note: there are edge cases
// where this should work but doesn't; this is currently "wontfix":
// https://github.com/go-sql-driver/mysql/issues/184
// If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
// sent and we have to keep using the cipher sent in the init packet.
if cipher == nil {
cipher = oldCipher
}
if err = mc.writeOldAuthPacket(cipher); err != nil {
return err
}
_, err = mc.readResultOK()
} else if mc.cfg.AllowCleartextPasswords && err == ErrCleartextPassword {
// Retry with clear text password for
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
if err = mc.writeClearAuthPacket(); err != nil {
return err
}
_, err = mc.readResultOK()
} else if mc.cfg.AllowNativePasswords && err == ErrNativePassword {
if err = mc.writeNativeAuthPacket(cipher); err != nil {
return err
}
_, err = mc.readResultOK()
}
return err
}
func init() { func init() {
sql.Register("mysql", &MySQLDriver{}) sql.Register("mysql", &MySQLDriver{})
} }

View file

@ -10,6 +10,7 @@ package mysql
import ( import (
"bytes" "bytes"
"crypto/rsa"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
@ -41,6 +42,8 @@ type Config struct {
Collation string // Connection collation Collation string // Connection collation
Loc *time.Location // Location for time.Time values Loc *time.Location // Location for time.Time values
MaxAllowedPacket int // Max packet size allowed MaxAllowedPacket int // Max packet size allowed
ServerPubKey string // Server public key name
pubKey *rsa.PublicKey // Server public key
TLSConfig string // TLS configuration name TLSConfig string // TLS configuration name
tls *tls.Config // TLS configuration tls *tls.Config // TLS configuration
Timeout time.Duration // Dial timeout Timeout time.Duration // Dial timeout
@ -64,6 +67,7 @@ func NewConfig() *Config {
return &Config{ return &Config{
Collation: defaultCollation, Collation: defaultCollation,
Loc: time.UTC, Loc: time.UTC,
MaxAllowedPacket: defaultMaxAllowedPacket,
AllowNativePasswords: true, AllowNativePasswords: true,
} }
} }
@ -93,6 +97,15 @@ func (cfg *Config) normalize() error {
cfg.Addr = ensureHavePort(cfg.Addr) 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 return nil
} }
@ -244,6 +257,16 @@ func (cfg *Config) FormatDSN() string {
} }
} }
if len(cfg.ServerPubKey) > 0 {
if hasParam {
buf.WriteString("&serverPubKey=")
} else {
hasParam = true
buf.WriteString("?serverPubKey=")
}
buf.WriteString(url.QueryEscape(cfg.ServerPubKey))
}
if cfg.Timeout > 0 { if cfg.Timeout > 0 {
if hasParam { if hasParam {
buf.WriteString("&timeout=") buf.WriteString("&timeout=")
@ -274,7 +297,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 {
@ -397,7 +420,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
@ -503,6 +525,20 @@ func parseDSNParams(cfg *Config, params string) (err error) {
return errors.New("invalid bool value: " + value) return errors.New("invalid bool value: " + value)
} }
// Server public key
case "serverPubKey":
name, err := url.QueryUnescape(value)
if err != nil {
return fmt.Errorf("invalid value for server pub key name: %v", err)
}
if pubKey := getServerPubKey(name); pubKey != nil {
cfg.ServerPubKey = name
cfg.pubKey = pubKey
} else {
return errors.New("invalid value / unknown server pub key name: " + name)
}
// Strict mode // Strict mode
case "strict": case "strict":
panic("strict mode has been removed. See https://github.com/go-sql-driver/mysql/wiki/strict-mode") panic("strict mode has been removed. See https://github.com/go-sql-driver/mysql/wiki/strict-mode")
@ -521,10 +557,6 @@ func parseDSNParams(cfg *Config, params string) (err error) {
if boolValue { if boolValue {
cfg.TLSConfig = "true" cfg.TLSConfig = "true"
cfg.tls = &tls.Config{} cfg.tls = &tls.Config{}
host, _, err := net.SplitHostPort(cfg.Addr)
if err == nil {
cfg.tls.ServerName = host
}
} else { } else {
cfg.TLSConfig = "false" cfg.TLSConfig = "false"
} }
@ -538,13 +570,6 @@ func parseDSNParams(cfg *Config, params string) (err error) {
} }
if tlsConfig := getTLSConfigClone(name); tlsConfig != nil { 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 {

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

@ -174,8 +174,7 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
// read OK packet // read OK packet
if err == nil { if err == nil {
_, err = mc.readResultOK() return mc.readResultOK()
return err
} }
mc.readPacket() mc.readPacket()

View file

@ -149,24 +149,29 @@ func (mc *mysqlConn) writePacket(data []byte) error {
} }
/****************************************************************************** /******************************************************************************
* Initialisation Process * * Initialization Process *
******************************************************************************/ ******************************************************************************/
// Handshake Initialization Packet // Handshake Initialization Packet
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake
func (mc *mysqlConn) readInitPacket() ([]byte, error) { func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err error) {
data, err := mc.readPacket() data, err = mc.readPacket()
if err != nil { if err != nil {
return nil, err // for init we can rewrite this to ErrBadConn for sql.Driver to retry, since
// in connection initialization we don't risk retrying non-idempotent actions.
if err == ErrInvalidConn {
return nil, "", driver.ErrBadConn
}
return
} }
if data[0] == iERR { if data[0] == iERR {
return nil, mc.handleErrorPacket(data) return nil, "", mc.handleErrorPacket(data)
} }
// protocol version [1 byte] // protocol version [1 byte]
if data[0] < minProtocolVersion { if data[0] < minProtocolVersion {
return nil, fmt.Errorf( return nil, "", fmt.Errorf(
"unsupported protocol version %d. Version %d or higher is required", "unsupported protocol version %d. Version %d or higher is required",
data[0], data[0],
minProtocolVersion, minProtocolVersion,
@ -178,7 +183,7 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
pos := 1 + bytes.IndexByte(data[1:], 0x00) + 1 + 4 pos := 1 + bytes.IndexByte(data[1:], 0x00) + 1 + 4
// first part of the password cipher [8 bytes] // first part of the password cipher [8 bytes]
cipher := data[pos : pos+8] authData := data[pos : pos+8]
// (filler) always 0x00 [1 byte] // (filler) always 0x00 [1 byte]
pos += 8 + 1 pos += 8 + 1
@ -186,10 +191,10 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
// capability flags (lower 2 bytes) [2 bytes] // capability flags (lower 2 bytes) [2 bytes]
mc.flags = clientFlag(binary.LittleEndian.Uint16(data[pos : pos+2])) mc.flags = clientFlag(binary.LittleEndian.Uint16(data[pos : pos+2]))
if mc.flags&clientProtocol41 == 0 { if mc.flags&clientProtocol41 == 0 {
return nil, ErrOldProtocol return nil, "", ErrOldProtocol
} }
if mc.flags&clientSSL == 0 && mc.cfg.tls != nil { if mc.flags&clientSSL == 0 && mc.cfg.tls != nil {
return nil, ErrNoTLS return nil, "", ErrNoTLS
} }
pos += 2 pos += 2
@ -213,32 +218,32 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
// //
// The official Python library uses the fixed length 12 // The official Python library uses the fixed length 12
// which seems to work but technically could have a hidden bug. // which seems to work but technically could have a hidden bug.
cipher = append(cipher, data[pos:pos+12]...) authData = append(authData, data[pos:pos+12]...)
pos += 13
// TODO: Verify string termination
// EOF if version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2) // EOF if version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2)
// \NUL otherwise // \NUL otherwise
// if end := bytes.IndexByte(data[pos:], 0x00); end != -1 {
//if data[len(data)-1] == 0 { plugin = string(data[pos : pos+end])
// return } else {
//} plugin = string(data[pos:])
//return ErrMalformPkt }
// make a memory safe copy of the cipher slice // make a memory safe copy of the cipher slice
var b [20]byte var b [20]byte
copy(b[:], cipher) copy(b[:], authData)
return b[:], nil return b[:], plugin, nil
} }
// make a memory safe copy of the cipher slice // make a memory safe copy of the cipher slice
var b [8]byte var b [8]byte
copy(b[:], cipher) copy(b[:], authData)
return b[:], nil return b[:], plugin, nil
} }
// Client Authentication Packet // Client Authentication Packet
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse
func (mc *mysqlConn) writeAuthPacket(cipher []byte) error { func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string) error {
// Adjust client flags based on server support // Adjust client flags based on server support
clientFlags := clientProtocol41 | clientFlags := clientProtocol41 |
clientSecureConn | clientSecureConn |
@ -262,10 +267,17 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
clientFlags |= clientMultiStatements clientFlags |= clientMultiStatements
} }
// User Password // encode length of the auth plugin data
scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.Passwd)) var authRespLEIBuf [9]byte
authRespLen := len(authResp)
authRespLEI := appendLengthEncodedInteger(authRespLEIBuf[:0], uint64(authRespLen))
if len(authRespLEI) > 1 {
// if the length can not be written in 1 byte, it must be written as a
// length encoded integer
clientFlags |= clientPluginAuthLenEncClientData
}
pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + 1 + len(scrambleBuff) + 21 + 1 pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + len(authRespLEI) + len(authResp) + 21 + 1
// To specify a db name // To specify a db name
if n := len(mc.cfg.DBName); n > 0 { if n := len(mc.cfg.DBName); n > 0 {
@ -276,7 +288,7 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
// Calculate packet length and get buffer with that size // Calculate packet length and get buffer with that size
data := mc.buf.takeSmallBuffer(pktLen + 4) data := mc.buf.takeSmallBuffer(pktLen + 4)
if data == nil { if data == nil {
// can not take the buffer. Something must be wrong with the connection // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(ErrBusyBuffer)
return errBadConnNoWrite return errBadConnNoWrite
} }
@ -333,9 +345,9 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
data[pos] = 0x00 data[pos] = 0x00
pos++ pos++
// ScrambleBuffer [length encoded integer] // Auth Data [length encoded integer]
data[pos] = byte(len(scrambleBuff)) pos += copy(data[pos:], authRespLEI)
pos += 1 + copy(data[pos+1:], scrambleBuff) pos += copy(data[pos:], authResp)
// Databasename [null terminated string] // Databasename [null terminated string]
if len(mc.cfg.DBName) > 0 { if len(mc.cfg.DBName) > 0 {
@ -344,76 +356,26 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
pos++ pos++
} }
// Assume native client during response pos += copy(data[pos:], plugin)
pos += copy(data[pos:], "mysql_native_password")
data[pos] = 0x00 data[pos] = 0x00
pos++
// Send Auth packet // Send Auth packet
return mc.writePacket(data) return mc.writePacket(data[:pos])
} }
// Client old authentication packet
// 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) writeAuthSwitchPacket(authData []byte) error {
// User password pktLen := 4 + len(authData)
// https://dev.mysql.com/doc/internals/en/old-password-authentication.html data := mc.buf.takeSmallBuffer(pktLen)
// 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
pktLen := len(scrambleBuff) + 1
data := mc.buf.takeSmallBuffer(4 + pktLen)
if data == nil { if data == nil {
// can not take the buffer. Something must be wrong with the connection // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(ErrBusyBuffer)
return errBadConnNoWrite return errBadConnNoWrite
} }
// Add the scrambled password [null terminated string] // Add the auth data [EOF]
copy(data[4:], scrambleBuff) copy(data[4:], authData)
data[4+pktLen-1] = 0x00
return mc.writePacket(data)
}
// Client clear text authentication packet
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
func (mc *mysqlConn) writeClearAuthPacket() error {
// Calculate the packet length and add a tailing 0
pktLen := len(mc.cfg.Passwd) + 1
data := mc.buf.takeSmallBuffer(4 + pktLen)
if data == nil {
// can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer)
return errBadConnNoWrite
}
// Add the clear password [null terminated string]
copy(data[4:], mc.cfg.Passwd)
data[4+pktLen-1] = 0x00
return mc.writePacket(data)
}
// Native password authentication method
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
func (mc *mysqlConn) writeNativeAuthPacket(cipher []byte) error {
// 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
pktLen := len(scrambleBuff)
data := mc.buf.takeSmallBuffer(4 + pktLen)
if data == nil {
// can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer)
return errBadConnNoWrite
}
// Add the scramble
copy(data[4:], scrambleBuff)
return mc.writePacket(data) return mc.writePacket(data)
} }
@ -427,7 +389,7 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error {
data := mc.buf.takeSmallBuffer(4 + 1) data := mc.buf.takeSmallBuffer(4 + 1)
if data == nil { if data == nil {
// can not take the buffer. Something must be wrong with the connection // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(ErrBusyBuffer)
return errBadConnNoWrite return errBadConnNoWrite
} }
@ -446,7 +408,7 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
pktLen := 1 + len(arg) pktLen := 1 + len(arg)
data := mc.buf.takeBuffer(pktLen + 4) data := mc.buf.takeBuffer(pktLen + 4)
if data == nil { if data == nil {
// can not take the buffer. Something must be wrong with the connection // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(ErrBusyBuffer)
return errBadConnNoWrite return errBadConnNoWrite
} }
@ -467,7 +429,7 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
data := mc.buf.takeSmallBuffer(4 + 1 + 4) data := mc.buf.takeSmallBuffer(4 + 1 + 4)
if data == nil { if data == nil {
// can not take the buffer. Something must be wrong with the connection // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(ErrBusyBuffer)
return errBadConnNoWrite return errBadConnNoWrite
} }
@ -489,45 +451,50 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
* Result Packets * * Result Packets *
******************************************************************************/ ******************************************************************************/
// Returns error if Packet is not an 'Result OK'-Packet func (mc *mysqlConn) readAuthResult() ([]byte, string, error) {
func (mc *mysqlConn) readResultOK() ([]byte, error) {
data, err := mc.readPacket() data, err := mc.readPacket()
if err == nil { if err != nil {
// packet indicator return nil, "", err
switch data[0] {
case iOK:
return nil, mc.handleOkPacket(data)
case iEOF:
if len(data) > 1 {
pluginEndIndex := bytes.IndexByte(data, 0x00)
plugin := string(data[1:pluginEndIndex])
cipher := data[pluginEndIndex+1:]
switch plugin {
case "mysql_old_password":
// using old_passwords
return cipher, ErrOldPassword
case "mysql_clear_password":
// using clear text password
return cipher, ErrCleartextPassword
case "mysql_native_password":
// using mysql default authentication method
return cipher, ErrNativePassword
default:
return cipher, ErrUnknownPlugin
}
}
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
return nil, ErrOldPassword
default: // Error otherwise
return nil, mc.handleErrorPacket(data)
}
} }
return nil, err
// packet indicator
switch data[0] {
case iOK:
return nil, "", mc.handleOkPacket(data)
case iAuthMoreData:
return data[1:], "", err
case iEOF:
if len(data) == 1 {
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
return nil, "mysql_old_password", nil
}
pluginEndIndex := bytes.IndexByte(data, 0x00)
if pluginEndIndex < 0 {
return nil, "", ErrMalformPkt
}
plugin := string(data[1:pluginEndIndex])
authData := data[pluginEndIndex+1:]
return authData, plugin, nil
default: // Error otherwise
return nil, "", mc.handleErrorPacket(data)
}
}
// Returns error if Packet is not an 'Result OK'-Packet
func (mc *mysqlConn) readResultOK() error {
data, err := mc.readPacket()
if err != nil {
return err
}
if data[0] == iOK {
return mc.handleOkPacket(data)
}
return mc.handleErrorPacket(data)
} }
// Result Set Header Packet // Result Set Header Packet
@ -571,7 +538,8 @@ func (mc *mysqlConn) handleErrorPacket(data []byte) error {
errno := binary.LittleEndian.Uint16(data[1:3]) errno := binary.LittleEndian.Uint16(data[1:3])
// 1792: ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION // 1792: ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION
if errno == 1792 && mc.cfg.RejectReadOnly { // 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 // Oops; we are connected to a read-only connection, and won't be able
// to issue any write statements. Since RejectReadOnly is configured, // to issue any write statements. Since RejectReadOnly is configured,
// we throw away this connection hoping this one would have write // we throw away this connection hoping this one would have write
@ -696,14 +664,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]
@ -853,7 +828,7 @@ func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error {
// 2 bytes paramID // 2 bytes paramID
const dataOffset = 1 + 4 + 2 const dataOffset = 1 + 4 + 2
// Can not use the write buffer since // Cannot use the write buffer since
// a) the buffer is too small // a) the buffer is too small
// b) it is in use // b) it is in use
data := make([]byte, 4+1+4+2+len(arg)) data := make([]byte, 4+1+4+2+len(arg))
@ -908,6 +883,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
@ -919,7 +900,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
data = mc.buf.takeCompleteBuffer() data = mc.buf.takeCompleteBuffer()
} }
if data == nil { if data == nil {
// can not take the buffer. Something must be wrong with the connection // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(ErrBusyBuffer)
return errBadConnNoWrite return errBadConnNoWrite
} }
@ -980,7 +961,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
} }
@ -988,7 +969,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 {
@ -1004,7 +985,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 {
@ -1020,7 +1001,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 {
@ -1032,10 +1013,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)),
) )
@ -1050,14 +1031,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)),
) )
@ -1069,7 +1050,7 @@ 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 a [64]byte var a [64]byte
@ -1087,7 +1068,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
paramValues = append(paramValues, b...) paramValues = append(paramValues, b...)
default: default:
return fmt.Errorf("can not convert type: %T", arg) return fmt.Errorf("cannot convert type: %T", arg)
} }
} }
@ -1143,10 +1124,11 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
} }
return io.EOF return io.EOF
} }
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]
@ -1264,7 +1246,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
rows.rs.columns[i].decimals, rows.rs.columns[i].decimals,
) )
} }
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true) dest[i], err = formatBinaryTime(data[pos:pos+int(num)], dstlen)
case rows.mc.parseTime: case rows.mc.parseTime:
dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc) dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc)
default: default:
@ -1284,7 +1266,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
) )
} }
} }
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, false) dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen)
} }
if err == nil { if err == nil {

View file

@ -11,16 +11,10 @@ package mysql
import ( import (
"database/sql/driver" "database/sql/driver"
"io" "io"
"math"
"reflect"
) )
type mysqlField struct {
tableName string
name string
flags fieldFlag
fieldType byte
decimals byte
}
type resultSet struct { type resultSet struct {
columns []mysqlField columns []mysqlField
columnNames []string columnNames []string
@ -65,6 +59,44 @@ func (rows *mysqlRows) Columns() []string {
return columns return columns
} }
func (rows *mysqlRows) ColumnTypeDatabaseTypeName(i int) string {
return rows.rs.columns[i].typeDatabaseName()
}
// func (rows *mysqlRows) ColumnTypeLength(i int) (length int64, ok bool) {
// return int64(rows.rs.columns[i].length), true
// }
func (rows *mysqlRows) ColumnTypeNullable(i int) (nullable, ok bool) {
return rows.rs.columns[i].flags&flagNotNULL == 0, true
}
func (rows *mysqlRows) ColumnTypePrecisionScale(i int) (int64, int64, bool) {
column := rows.rs.columns[i]
decimals := int64(column.decimals)
switch column.fieldType {
case fieldTypeDecimal, fieldTypeNewDecimal:
if decimals > 0 {
return int64(column.length) - 2, decimals, true
}
return int64(column.length) - 1, decimals, true
case fieldTypeTimestamp, fieldTypeDateTime, fieldTypeTime:
return decimals, decimals, true
case fieldTypeFloat, fieldTypeDouble:
if decimals == 0x1f {
return math.MaxInt64, math.MaxInt64, true
}
return math.MaxInt64, decimals, true
}
return 0, 0, false
}
func (rows *mysqlRows) ColumnTypeScanType(i int) reflect.Type {
return rows.rs.columns[i].scanType()
}
func (rows *mysqlRows) Close() (err error) { func (rows *mysqlRows) Close() (err error) {
if f := rows.finish; f != nil { if f := rows.finish; f != nil {
f() f()

View file

@ -132,19 +132,36 @@ func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
type converter struct{} type converter struct{}
// ConvertValue mirrors the reference/default converter in database/sql/driver
// with _one_ exception. We support uint64 with their high bit and the default
// implementation does not. This function should be kept in sync with
// database/sql/driver defaultConverter.ConvertValue() except for that
// deliberate difference.
func (c converter) ConvertValue(v interface{}) (driver.Value, error) { func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
if driver.IsValue(v) { if driver.IsValue(v) {
return v, nil return v, nil
} }
if vr, ok := v.(driver.Valuer); ok {
sv, err := callValuerValue(vr)
if err != nil {
return nil, err
}
if !driver.IsValue(sv) {
return nil, fmt.Errorf("non-Value type %T returned from Value", sv)
}
return sv, nil
}
rv := reflect.ValueOf(v) rv := reflect.ValueOf(v)
switch rv.Kind() { switch rv.Kind() {
case reflect.Ptr: case reflect.Ptr:
// indirect pointers // indirect pointers
if rv.IsNil() { if rv.IsNil() {
return nil, nil return nil, nil
} else {
return c.ConvertValue(rv.Elem().Interface())
} }
return c.ConvertValue(rv.Elem().Interface())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return rv.Int(), nil return rv.Int(), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
@ -157,6 +174,38 @@ 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())
} }
var valuerReflectType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
// callValuerValue returns vr.Value(), with one exception:
// If vr.Value is an auto-generated method on a pointer type and the
// pointer is nil, it would panic at runtime in the panicwrap
// method. Treat it like nil instead.
//
// This is so people can implement driver.Value on value types and
// still use nil pointers to those types to mean nil/NULL, just like
// string/*string.
//
// This is an exact copy of the same-named unexported function from the
// database/sql package.
func callValuerValue(vr driver.Valuer) (v driver.Value, err error) {
if rv := reflect.ValueOf(vr); rv.Kind() == reflect.Ptr &&
rv.IsNil() &&
rv.Type().Elem().Implements(valuerReflectType) {
return nil, nil
}
return vr.Value()
}

View file

@ -9,27 +9,29 @@
package mysql package mysql
import ( import (
"crypto/sha1"
"crypto/tls" "crypto/tls"
"database/sql/driver" "database/sql/driver"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
"strconv"
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
) )
// Registry for custom tls.Configs
var ( var (
tlsConfigLock sync.RWMutex tlsConfigLock sync.RWMutex
tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs tlsConfigRegistry map[string]*tls.Config
) )
// 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. // Note: The provided tls.Config is exclusively owned by the driver after
// registering it.
// //
// rootCertPool := x509.NewCertPool() // rootCertPool := x509.NewCertPool()
// pem, err := ioutil.ReadFile("/path/ca-cert.pem") // pem, err := ioutil.ReadFile("/path/ca-cert.pem")
@ -57,11 +59,11 @@ func RegisterTLSConfig(key string, config *tls.Config) error {
} }
tlsConfigLock.Lock() tlsConfigLock.Lock()
if tlsConfigRegister == nil { if tlsConfigRegistry == nil {
tlsConfigRegister = make(map[string]*tls.Config) tlsConfigRegistry = make(map[string]*tls.Config)
} }
tlsConfigRegister[key] = config tlsConfigRegistry[key] = config
tlsConfigLock.Unlock() tlsConfigLock.Unlock()
return nil return nil
} }
@ -69,15 +71,15 @@ func RegisterTLSConfig(key string, config *tls.Config) error {
// 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() tlsConfigLock.Lock()
if tlsConfigRegister != nil { if tlsConfigRegistry != nil {
delete(tlsConfigRegister, key) delete(tlsConfigRegistry, key)
} }
tlsConfigLock.Unlock() tlsConfigLock.Unlock()
} }
func getTLSConfigClone(key string) (config *tls.Config) { func getTLSConfigClone(key string) (config *tls.Config) {
tlsConfigLock.RLock() tlsConfigLock.RLock()
if v, ok := tlsConfigRegister[key]; ok { if v, ok := tlsConfigRegistry[key]; ok {
config = cloneTLSConfig(v) config = cloneTLSConfig(v)
} }
tlsConfigLock.RUnlock() tlsConfigLock.RUnlock()
@ -98,119 +100,6 @@ func readBool(input string) (value bool, valid bool) {
return return
} }
/******************************************************************************
* Authentication *
******************************************************************************/
// Encrypt password using 4.1+ method
func scramblePassword(scramble, password []byte) []byte {
if len(password) == 0 {
return nil
}
// stage1Hash = SHA1(password)
crypt := sha1.New()
crypt.Write(password)
stage1 := crypt.Sum(nil)
// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
// inner Hash
crypt.Reset()
crypt.Write(stage1)
hash := crypt.Sum(nil)
// outer Hash
crypt.Reset()
crypt.Write(scramble)
crypt.Write(hash)
scramble = crypt.Sum(nil)
// token = scrambleHash XOR stage1Hash
for i := range scramble {
scramble[i] ^= stage1[i]
}
return scramble
}
// Encrypt password using pre 4.1 (old password) method
// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
type myRnd struct {
seed1, seed2 uint32
}
const myRndMaxVal = 0x3FFFFFFF
// Pseudo random number generator
func newMyRnd(seed1, seed2 uint32) *myRnd {
return &myRnd{
seed1: seed1 % myRndMaxVal,
seed2: seed2 % myRndMaxVal,
}
}
// Tested to be equivalent to MariaDB's floating point variant
// http://play.golang.org/p/QHvhd4qved
// http://play.golang.org/p/RG0q4ElWDx
func (r *myRnd) NextByte() byte {
r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
return byte(uint64(r.seed1) * 31 / myRndMaxVal)
}
// Generate binary hash from byte string using insecure pre 4.1 method
func pwHash(password []byte) (result [2]uint32) {
var add uint32 = 7
var tmp uint32
result[0] = 1345345333
result[1] = 0x12345671
for _, c := range password {
// skip spaces and tabs in password
if c == ' ' || c == '\t' {
continue
}
tmp = uint32(c)
result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
result[1] += (result[1] << 8) ^ result[0]
add += tmp
}
// Remove sign bit (1<<31)-1)
result[0] &= 0x7FFFFFFF
result[1] &= 0x7FFFFFFF
return
}
// Encrypt password using insecure pre 4.1 method
func scrambleOldPassword(scramble, password []byte) []byte {
if len(password) == 0 {
return nil
}
scramble = scramble[:8]
hashPw := pwHash(password)
hashSc := pwHash(scramble)
r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
var out [8]byte
for i := range out {
out[i] = r.NextByte() + 64
}
mask := r.NextByte()
for i := range out {
out[i] ^= mask
}
return out[:]
}
/****************************************************************************** /******************************************************************************
* Time related utils * * Time related utils *
******************************************************************************/ ******************************************************************************/
@ -339,87 +228,104 @@ var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) { func appendMicrosecs(dst, src []byte, decimals int) []byte {
if decimals <= 0 {
return dst
}
if len(src) == 0 {
return append(dst, ".000000"[:decimals+1]...)
}
microsecs := binary.LittleEndian.Uint32(src[:4])
p1 := byte(microsecs / 10000)
microsecs -= 10000 * uint32(p1)
p2 := byte(microsecs / 100)
microsecs -= 100 * uint32(p2)
p3 := byte(microsecs)
switch decimals {
default:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2], digits01[p2],
digits10[p3], digits01[p3],
)
case 1:
return append(dst, '.',
digits10[p1],
)
case 2:
return append(dst, '.',
digits10[p1], digits01[p1],
)
case 3:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2],
)
case 4:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2], digits01[p2],
)
case 5:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2], digits01[p2],
digits10[p3],
)
}
}
func formatBinaryDateTime(src []byte, length uint8) (driver.Value, error) {
// length expects the deterministic length of the zero value, // length expects the deterministic length of the zero value,
// negative time and 100+ hours are automatically added if needed // negative time and 100+ hours are automatically added if needed
if len(src) == 0 { if len(src) == 0 {
if justTime {
return zeroDateTime[11 : 11+length], nil
}
return zeroDateTime[:length], nil return zeroDateTime[:length], nil
} }
var dst []byte // return value var dst []byte // return value
var pt, p1, p2, p3 byte // current digit pair var p1, p2, p3 byte // current digit pair
var zOffs byte // offset of value in zeroDateTime
if justTime { switch length {
switch length { case 10, 19, 21, 22, 23, 24, 25, 26:
case default:
8, // time (can be up to 10 when negative and 100+ hours) t := "DATE"
10, 11, 12, 13, 14, 15: // time with fractional seconds if length > 10 {
default: t += "TIME"
return nil, fmt.Errorf("illegal TIME length %d", length)
} }
switch len(src) { return nil, fmt.Errorf("illegal %s length %d", t, length)
case 8, 12:
default:
return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
}
// +2 to enable negative time and 100+ hours
dst = make([]byte, 0, length+2)
if src[0] == 1 {
dst = append(dst, '-')
}
if src[1] != 0 {
hour := uint16(src[1])*24 + uint16(src[5])
pt = byte(hour / 100)
p1 = byte(hour - 100*uint16(pt))
dst = append(dst, digits01[pt])
} else {
p1 = src[5]
}
zOffs = 11
src = src[6:]
} else {
switch length {
case 10, 19, 21, 22, 23, 24, 25, 26:
default:
t := "DATE"
if length > 10 {
t += "TIME"
}
return nil, fmt.Errorf("illegal %s length %d", t, length)
}
switch len(src) {
case 4, 7, 11:
default:
t := "DATE"
if length > 10 {
t += "TIME"
}
return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
}
dst = make([]byte, 0, length)
// start with the date
year := binary.LittleEndian.Uint16(src[:2])
pt = byte(year / 100)
p1 = byte(year - 100*uint16(pt))
p2, p3 = src[2], src[3]
dst = append(dst,
digits10[pt], digits01[pt],
digits10[p1], digits01[p1], '-',
digits10[p2], digits01[p2], '-',
digits10[p3], digits01[p3],
)
if length == 10 {
return dst, nil
}
if len(src) == 4 {
return append(dst, zeroDateTime[10:length]...), nil
}
dst = append(dst, ' ')
p1 = src[4] // hour
src = src[5:]
} }
switch len(src) {
case 4, 7, 11:
default:
t := "DATE"
if length > 10 {
t += "TIME"
}
return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
}
dst = make([]byte, 0, length)
// start with the date
year := binary.LittleEndian.Uint16(src[:2])
pt := year / 100
p1 = byte(year - 100*uint16(pt))
p2, p3 = src[2], src[3]
dst = append(dst,
digits10[pt], digits01[pt],
digits10[p1], digits01[p1], '-',
digits10[p2], digits01[p2], '-',
digits10[p3], digits01[p3],
)
if length == 10 {
return dst, nil
}
if len(src) == 4 {
return append(dst, zeroDateTime[10:length]...), nil
}
dst = append(dst, ' ')
p1 = src[4] // hour
src = src[5:]
// p1 is 2-digit hour, src is after hour // p1 is 2-digit hour, src is after hour
p2, p3 = src[0], src[1] p2, p3 = src[0], src[1]
dst = append(dst, dst = append(dst,
@ -427,51 +333,49 @@ func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value
digits10[p2], digits01[p2], ':', digits10[p2], digits01[p2], ':',
digits10[p3], digits01[p3], digits10[p3], digits01[p3],
) )
if length <= byte(len(dst)) { return appendMicrosecs(dst, src[2:], int(length)-20), nil
return dst, nil }
}
src = src[2:] func formatBinaryTime(src []byte, length uint8) (driver.Value, error) {
// length expects the deterministic length of the zero value,
// negative time and 100+ hours are automatically added if needed
if len(src) == 0 { if len(src) == 0 {
return append(dst, zeroDateTime[19:zOffs+length]...), nil return zeroDateTime[11 : 11+length], nil
} }
microsecs := binary.LittleEndian.Uint32(src[:4]) var dst []byte // return value
p1 = byte(microsecs / 10000)
microsecs -= 10000 * uint32(p1) switch length {
p2 = byte(microsecs / 100) case
microsecs -= 100 * uint32(p2) 8, // time (can be up to 10 when negative and 100+ hours)
p3 = byte(microsecs) 10, 11, 12, 13, 14, 15: // time with fractional seconds
switch decimals := zOffs + length - 20; decimals {
default: default:
return append(dst, '.', return nil, fmt.Errorf("illegal TIME length %d", length)
digits10[p1], digits01[p1],
digits10[p2], digits01[p2],
digits10[p3], digits01[p3],
), nil
case 1:
return append(dst, '.',
digits10[p1],
), nil
case 2:
return append(dst, '.',
digits10[p1], digits01[p1],
), nil
case 3:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2],
), nil
case 4:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2], digits01[p2],
), nil
case 5:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2], digits01[p2],
digits10[p3],
), nil
} }
switch len(src) {
case 8, 12:
default:
return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
}
// +2 to enable negative time and 100+ hours
dst = make([]byte, 0, length+2)
if src[0] == 1 {
dst = append(dst, '-')
}
days := binary.LittleEndian.Uint32(src[1:5])
hours := int64(days)*24 + int64(src[5])
if hours >= 100 {
dst = strconv.AppendInt(dst, hours, 10)
} else {
dst = append(dst, digits10[hours], digits01[hours])
}
min, sec := src[6], src[7]
dst = append(dst, ':',
digits10[min], digits01[min], ':',
digits10[sec], digits01[sec],
)
return appendMicrosecs(dst, src[8:], int(length)-9), nil
} }
/****************************************************************************** /******************************************************************************
@ -537,7 +441,7 @@ func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
// Check data length // Check data length
if len(b) >= n { if len(b) >= n {
return b[n-int(num) : n], false, n, nil return b[n-int(num) : n : n], false, n, nil
} }
return nil, false, n, io.EOF return nil, false, n, io.EOF
} }
@ -566,8 +470,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
@ -800,7 +704,7 @@ func (ab *atomicBool) TrySet(value bool) bool {
return atomic.SwapUint32(&ab.value, 0) > 0 return atomic.SwapUint32(&ab.value, 0) > 0
} }
// atomicBool is a wrapper for atomically accessed error values // atomicError is a wrapper for atomically accessed error values
type atomicError struct { type atomicError struct {
_noCopy noCopy _noCopy noCopy
value atomic.Value value atomic.Value

View file

@ -15,6 +15,7 @@ import (
"database/sql" "database/sql"
"database/sql/driver" "database/sql/driver"
"errors" "errors"
"fmt"
) )
func cloneTLSConfig(c *tls.Config) *tls.Config { func cloneTLSConfig(c *tls.Config) *tls.Config {
@ -44,6 +45,6 @@ func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
case sql.LevelSerializable: case sql.LevelSerializable:
return "SERIALIZABLE", nil return "SERIALIZABLE", nil
default: default:
return "", errors.New("mysql: unsupported isolation level: " + string(level)) return "", fmt.Errorf("mysql: unsupported isolation level: %v", level)
} }
} }

View file

@ -1,18 +0,0 @@
// 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
package mysql
import "crypto/tls"
func cloneTLSConfig(c *tls.Config) *tls.Config {
clone := *c
return &clone
}

View file

@ -12,18 +12,18 @@ env:
matrix: matrix:
- GOTAGS= - GOTAGS=
- GOTAGS=libsqlite3 - GOTAGS=libsqlite3
- GOTAGS="sqlite_allow_uri_authority sqlite_app_armor sqlite_foreign_keys sqlite_fts5 sqlite_icu sqlite_introspect sqlite_json sqlite_secure_delete sqlite_see sqlite_stat4 sqlite_trace sqlite_userauth sqlite_vacuum_incr sqlite_vtable" - GOTAGS="sqlite_allow_uri_authority sqlite_app_armor sqlite_foreign_keys sqlite_fts5 sqlite_icu sqlite_introspect sqlite_json sqlite_secure_delete sqlite_see sqlite_stat4 sqlite_trace sqlite_userauth sqlite_vacuum_incr sqlite_vtable sqlite_unlock_notify"
- GOTAGS=sqlite_vacuum_full - GOTAGS=sqlite_vacuum_full
go: go:
- 1.9.x - 1.9.x
- 1.10.x - 1.10.x
- 1.11.x
before_install: before_install:
- | - |
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
brew update brew update
brew upgrade icu4c
fi fi
- | - |
go get github.com/smartystreets/goconvey go get github.com/smartystreets/goconvey

View file

@ -67,6 +67,7 @@ This is also known as a DSN string. (Data Source Name).
Options are append after the filename of the SQLite database. Options are append after the filename of the SQLite database.
The database filename and options are seperated by an `?` (Question Mark). The database filename and options are seperated by an `?` (Question Mark).
Options should be URL-encoded (see [url.QueryEscape](https://golang.org/pkg/net/url/#QueryEscape)).
This also applies when using an in-memory database instead of a file. This also applies when using an in-memory database instead of a file.
@ -198,7 +199,7 @@ Additional information:
# Google Cloud Platform # Google Cloud Platform
Building on GCP is not possible because `Google Cloud Platform does not allow `gcc` to be executed. Building on GCP is not possible because Google Cloud Platform does not allow `gcc` to be executed.
Please work only with compiled final binaries. Please work only with compiled final binaries.
@ -290,7 +291,7 @@ For example the TDM-GCC Toolchain can be found [here](ttps://sourceforge.net/pro
When receiving a compile time error referencing recompile with `-FPIC` then you When receiving a compile time error referencing recompile with `-FPIC` then you
are probably using a hardend system. are probably using a hardend system.
You can copile the library on a hardend system with the following command. You can compile the library on a hardend system with the following command.
```bash ```bash
go build -ldflags '-extldflags=-fno-PIC' go build -ldflags '-extldflags=-fno-PIC'
@ -473,7 +474,7 @@ For an example see [shaxbee/go-spatialite](https://github.com/shaxbee/go-spatial
For more information see [#289](https://github.com/mattn/go-sqlite3/issues/289) For more information see [#289](https://github.com/mattn/go-sqlite3/issues/289)
- Trying to execure a `.` (dot) command throws an error. - Trying to execute a `.` (dot) command throws an error.
Error: `Error: near ".": syntax error` Error: `Error: near ".": syntax error`
Dot command are part of SQLite3 CLI not of this library. Dot command are part of SQLite3 CLI not of this library.

View file

@ -77,6 +77,12 @@ func updateHookTrampoline(handle uintptr, op int, db *C.char, table *C.char, row
callback(op, C.GoString(db), C.GoString(table), rowid) callback(op, C.GoString(db), C.GoString(table), rowid)
} }
//export authorizerTrampoline
func authorizerTrampoline(handle uintptr, op int, arg1 *C.char, arg2 *C.char, arg3 *C.char) int {
callback := lookupHandle(handle).(func(int, string, string, string) int)
return callback(op, C.GoString(arg1), C.GoString(arg2), C.GoString(arg3))
}
// Use handles to avoid passing Go pointers to C. // Use handles to avoid passing Go pointers to C.
type handleVal struct { type handleVal struct {

File diff suppressed because it is too large Load diff

View file

@ -124,9 +124,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()]. ** [sqlite_version()] and [sqlite_source_id()].
*/ */
#define SQLITE_VERSION "3.24.0" #define SQLITE_VERSION "3.25.2"
#define SQLITE_VERSION_NUMBER 3024000 #define SQLITE_VERSION_NUMBER 3025002
#define SQLITE_SOURCE_ID "2018-06-04 19:24:41 c7ee0833225bfd8c5ec2f9bf62b97c4e04d03bd9566366d5221ac8fb199a87ca" #define SQLITE_SOURCE_ID "2018-09-25 19:08:10 fb90e7189ae6d62e77ba3a308ca5d683f90bbe633cf681865365b8e92792d1c7"
/* /*
** CAPI3REF: Run-Time Library Version Numbers ** CAPI3REF: Run-Time Library Version Numbers
@ -473,6 +473,7 @@ SQLITE_API int sqlite3_exec(
*/ */
#define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8)) #define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8))
#define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8)) #define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8))
#define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8))
#define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) #define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8))
#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) #define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8))
#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) #define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8))
@ -512,6 +513,7 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2<<8)) #define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2<<8))
#define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3<<8)) #define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3<<8))
#define SQLITE_CANTOPEN_CONVPATH (SQLITE_CANTOPEN | (4<<8)) #define SQLITE_CANTOPEN_CONVPATH (SQLITE_CANTOPEN | (4<<8))
#define SQLITE_CANTOPEN_DIRTYWAL (SQLITE_CANTOPEN | (5<<8)) /* Not Used */
#define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8)) #define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8))
#define SQLITE_CORRUPT_SEQUENCE (SQLITE_CORRUPT | (2<<8)) #define SQLITE_CORRUPT_SEQUENCE (SQLITE_CORRUPT | (2<<8))
#define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8)) #define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8))
@ -887,7 +889,8 @@ struct sqlite3_io_methods {
** <li>[[SQLITE_FCNTL_PERSIST_WAL]] ** <li>[[SQLITE_FCNTL_PERSIST_WAL]]
** ^The [SQLITE_FCNTL_PERSIST_WAL] opcode is used to set or query the ** ^The [SQLITE_FCNTL_PERSIST_WAL] opcode is used to set or query the
** persistent [WAL | Write Ahead Log] setting. By default, the auxiliary ** persistent [WAL | Write Ahead Log] setting. By default, the auxiliary
** write ahead log and shared memory files used for transaction control ** write ahead log ([WAL file]) and shared memory
** files used for transaction control
** are automatically deleted when the latest connection to the database ** are automatically deleted when the latest connection to the database
** closes. Setting persistent WAL mode causes those files to persist after ** closes. Setting persistent WAL mode causes those files to persist after
** close. Persisting the files is useful when other processes that do not ** close. Persisting the files is useful when other processes that do not
@ -1073,6 +1076,26 @@ struct sqlite3_io_methods {
** a file lock using the xLock or xShmLock methods of the VFS to wait ** a file lock using the xLock or xShmLock methods of the VFS to wait
** for up to M milliseconds before failing, where M is the single ** for up to M milliseconds before failing, where M is the single
** unsigned integer parameter. ** unsigned integer parameter.
**
** <li>[[SQLITE_FCNTL_DATA_VERSION]]
** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to
** a database file. The argument is a pointer to a 32-bit unsigned integer.
** The "data version" for the pager is written into the pointer. The
** "data version" changes whenever any change occurs to the corresponding
** database file, either through SQL statements on the same database
** connection or through transactions committed by separate database
** connections possibly in other processes. The [sqlite3_total_changes()]
** interface can be used to find if any database on the connection has changed,
** but that interface responds to changes on TEMP as well as MAIN and does
** not provide a mechanism to detect changes to MAIN only. Also, the
** [sqlite3_total_changes()] interface responds to internal changes only and
** omits changes made by other database connections. The
** [PRAGMA data_version] command provide a mechanism to detect changes to
** a single attached database that occur due to other database connections,
** but omits changes implemented by the database connection on which it is
** called. This file control is the only mechanism to detect changes that
** happen either internally or externally and that are associated with
** a particular attached database.
** </ul> ** </ul>
*/ */
#define SQLITE_FCNTL_LOCKSTATE 1 #define SQLITE_FCNTL_LOCKSTATE 1
@ -1108,6 +1131,7 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32 #define SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32
#define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33 #define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33
#define SQLITE_FCNTL_LOCK_TIMEOUT 34 #define SQLITE_FCNTL_LOCK_TIMEOUT 34
#define SQLITE_FCNTL_DATA_VERSION 35
/* deprecated names */ /* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@ -2122,6 +2146,12 @@ struct sqlite3_mem_methods {
** with no schema and no content. The following process works even for ** with no schema and no content. The following process works even for
** a badly corrupted database file: ** a badly corrupted database file:
** <ol> ** <ol>
** <li> If the database connection is newly opened, make sure it has read the
** database schema by preparing then discarding some query against the
** database, or calling sqlite3_table_column_metadata(), ignoring any
** errors. This step is only necessary if the application desires to keep
** the database in WAL mode after the reset if it was in WAL mode before
** the reset.
** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); ** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0);
** <li> [sqlite3_exec](db, "[VACUUM]", 0, 0, 0); ** <li> [sqlite3_exec](db, "[VACUUM]", 0, 0, 0);
** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); ** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0);
@ -2270,12 +2300,17 @@ SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3*,sqlite3_int64);
** program, the value returned reflects the number of rows modified by the ** program, the value returned reflects the number of rows modified by the
** previous INSERT, UPDATE or DELETE statement within the same trigger. ** previous INSERT, UPDATE or DELETE statement within the same trigger.
** **
** See also the [sqlite3_total_changes()] interface, the
** [count_changes pragma], and the [changes() SQL function].
**
** If a separate thread makes changes on the same database connection ** If a separate thread makes changes on the same database connection
** while [sqlite3_changes()] is running then the value returned ** while [sqlite3_changes()] is running then the value returned
** is unpredictable and not meaningful. ** is unpredictable and not meaningful.
**
** See also:
** <ul>
** <li> the [sqlite3_total_changes()] interface
** <li> the [count_changes pragma]
** <li> the [changes() SQL function]
** <li> the [data_version pragma]
** </ul>
*/ */
SQLITE_API int sqlite3_changes(sqlite3*); SQLITE_API int sqlite3_changes(sqlite3*);
@ -2293,13 +2328,26 @@ SQLITE_API int sqlite3_changes(sqlite3*);
** count, but those made as part of REPLACE constraint resolution are ** count, but those made as part of REPLACE constraint resolution are
** not. ^Changes to a view that are intercepted by INSTEAD OF triggers ** not. ^Changes to a view that are intercepted by INSTEAD OF triggers
** are not counted. ** are not counted.
**
** See also the [sqlite3_changes()] interface, the
** [count_changes pragma], and the [total_changes() SQL function].
** **
** This the [sqlite3_total_changes(D)] interface only reports the number
** of rows that changed due to SQL statement run against database
** connection D. Any changes by other database connections are ignored.
** To detect changes against a database file from other database
** connections use the [PRAGMA data_version] command or the
** [SQLITE_FCNTL_DATA_VERSION] [file control].
**
** If a separate thread makes changes on the same database connection ** If a separate thread makes changes on the same database connection
** while [sqlite3_total_changes()] is running then the value ** while [sqlite3_total_changes()] is running then the value
** returned is unpredictable and not meaningful. ** returned is unpredictable and not meaningful.
**
** See also:
** <ul>
** <li> the [sqlite3_changes()] interface
** <li> the [count_changes pragma]
** <li> the [changes() SQL function]
** <li> the [data_version pragma]
** <li> the [SQLITE_FCNTL_DATA_VERSION] [file control]
** </ul>
*/ */
SQLITE_API int sqlite3_total_changes(sqlite3*); SQLITE_API int sqlite3_total_changes(sqlite3*);
@ -3355,13 +3403,24 @@ SQLITE_API sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int
** [database connection] D failed, then the sqlite3_errcode(D) interface ** [database connection] D failed, then the sqlite3_errcode(D) interface
** returns the numeric [result code] or [extended result code] for that ** returns the numeric [result code] or [extended result code] for that
** API call. ** API call.
** If the most recent API call was successful,
** then the return value from sqlite3_errcode() is undefined.
** ^The sqlite3_extended_errcode() ** ^The sqlite3_extended_errcode()
** interface is the same except that it always returns the ** interface is the same except that it always returns the
** [extended result code] even when extended result codes are ** [extended result code] even when extended result codes are
** disabled. ** disabled.
** **
** The values returned by sqlite3_errcode() and/or
** sqlite3_extended_errcode() might change with each API call.
** Except, there are some interfaces that are guaranteed to never
** change the value of the error code. The error-code preserving
** interfaces are:
**
** <ul>
** <li> sqlite3_errcode()
** <li> sqlite3_extended_errcode()
** <li> sqlite3_errmsg()
** <li> sqlite3_errmsg16()
** </ul>
**
** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language ** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language
** text that describes the error, as either UTF-8 or UTF-16 respectively. ** text that describes the error, as either UTF-8 or UTF-16 respectively.
** ^(Memory to hold the error message string is managed internally. ** ^(Memory to hold the error message string is managed internally.
@ -4515,11 +4574,25 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt);
** from [sqlite3_column_blob()], [sqlite3_column_text()], etc. into ** from [sqlite3_column_blob()], [sqlite3_column_text()], etc. into
** [sqlite3_free()]. ** [sqlite3_free()].
** **
** ^(If a memory allocation error occurs during the evaluation of any ** As long as the input parameters are correct, these routines will only
** of these routines, a default value is returned. The default value ** fail if an out-of-memory error occurs during a format conversion.
** is either the integer 0, the floating point number 0.0, or a NULL ** Only the following subset of interfaces are subject to out-of-memory
** pointer. Subsequent calls to [sqlite3_errcode()] will return ** errors:
** [SQLITE_NOMEM].)^ **
** <ul>
** <li> sqlite3_column_blob()
** <li> sqlite3_column_text()
** <li> sqlite3_column_text16()
** <li> sqlite3_column_bytes()
** <li> sqlite3_column_bytes16()
** </ul>
**
** If an out-of-memory error occurs, then the return value from these
** routines is the same as if the column had contained an SQL NULL value.
** Valid SQL NULL returns can be distinguished from out-of-memory errors
** by invoking the [sqlite3_errcode()] immediately after the suspect
** return value is obtained and before any
** other SQLite interface is called on the same [database connection].
*/ */
SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol); SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol);
@ -4596,11 +4669,13 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** **
** ^These functions (collectively known as "function creation routines") ** ^These functions (collectively known as "function creation routines")
** are used to add SQL functions or aggregates or to redefine the behavior ** are used to add SQL functions or aggregates or to redefine the behavior
** of existing SQL functions or aggregates. The only differences between ** of existing SQL functions or aggregates. The only differences between
** these routines are the text encoding expected for ** the three "sqlite3_create_function*" routines are the text encoding
** the second parameter (the name of the function being created) ** expected for the second parameter (the name of the function being
** and the presence or absence of a destructor callback for ** created) and the presence or absence of a destructor callback for
** the application data pointer. ** the application data pointer. Function sqlite3_create_window_function()
** is similar, but allows the user to supply the extra callback functions
** needed by [aggregate window functions].
** **
** ^The first parameter is the [database connection] to which the SQL ** ^The first parameter is the [database connection] to which the SQL
** function is to be added. ^If an application uses more than one database ** function is to be added. ^If an application uses more than one database
@ -4646,7 +4721,8 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** ^(The fifth parameter is an arbitrary pointer. The implementation of the ** ^(The fifth parameter is an arbitrary pointer. The implementation of the
** function can gain access to this pointer using [sqlite3_user_data()].)^ ** function can gain access to this pointer using [sqlite3_user_data()].)^
** **
** ^The sixth, seventh and eighth parameters, xFunc, xStep and xFinal, are ** ^The sixth, seventh and eighth parameters passed to the three
** "sqlite3_create_function*" functions, xFunc, xStep and xFinal, are
** pointers to C-language functions that implement the SQL function or ** pointers to C-language functions that implement the SQL function or
** aggregate. ^A scalar SQL function requires an implementation of the xFunc ** aggregate. ^A scalar SQL function requires an implementation of the xFunc
** callback only; NULL pointers must be passed as the xStep and xFinal ** callback only; NULL pointers must be passed as the xStep and xFinal
@ -4655,15 +4731,24 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** SQL function or aggregate, pass NULL pointers for all three function ** SQL function or aggregate, pass NULL pointers for all three function
** callbacks. ** callbacks.
** **
** ^(If the ninth parameter to sqlite3_create_function_v2() is not NULL, ** ^The sixth, seventh, eighth and ninth parameters (xStep, xFinal, xValue
** then it is destructor for the application data pointer. ** and xInverse) passed to sqlite3_create_window_function are pointers to
** The destructor is invoked when the function is deleted, either by being ** C-language callbacks that implement the new function. xStep and xFinal
** overloaded or when the database connection closes.)^ ** must both be non-NULL. xValue and xInverse may either both be NULL, in
** ^The destructor is also invoked if the call to ** which case a regular aggregate function is created, or must both be
** sqlite3_create_function_v2() fails. ** non-NULL, in which case the new function may be used as either an aggregate
** ^When the destructor callback of the tenth parameter is invoked, it ** or aggregate window function. More details regarding the implementation
** is passed a single argument which is a copy of the application data ** of aggregate window functions are
** pointer which was the fifth parameter to sqlite3_create_function_v2(). ** [user-defined window functions|available here].
**
** ^(If the final parameter to sqlite3_create_function_v2() or
** sqlite3_create_window_function() is not NULL, then it is destructor for
** the application data pointer. The destructor is invoked when the function
** is deleted, either by being overloaded or when the database connection
** closes.)^ ^The destructor is also invoked if the call to
** sqlite3_create_function_v2() fails. ^When the destructor callback is
** invoked, it is passed a single argument which is a copy of the application
** data pointer which was the fifth parameter to sqlite3_create_function_v2().
** **
** ^It is permitted to register multiple implementations of the same ** ^It is permitted to register multiple implementations of the same
** functions with the same name but with either differing numbers of ** functions with the same name but with either differing numbers of
@ -4716,6 +4801,18 @@ SQLITE_API int sqlite3_create_function_v2(
void (*xFinal)(sqlite3_context*), void (*xFinal)(sqlite3_context*),
void(*xDestroy)(void*) void(*xDestroy)(void*)
); );
SQLITE_API int sqlite3_create_window_function(
sqlite3 *db,
const char *zFunctionName,
int nArg,
int eTextRep,
void *pApp,
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*),
void (*xValue)(sqlite3_context*),
void (*xInverse)(sqlite3_context*,int,sqlite3_value**),
void(*xDestroy)(void*)
);
/* /*
** CAPI3REF: Text Encodings ** CAPI3REF: Text Encodings
@ -4858,6 +4955,28 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6
** **
** These routines must be called from the same thread as ** These routines must be called from the same thread as
** the SQL function that supplied the [sqlite3_value*] parameters. ** the SQL function that supplied the [sqlite3_value*] parameters.
**
** As long as the input parameter is correct, these routines can only
** fail if an out-of-memory error occurs during a format conversion.
** Only the following subset of interfaces are subject to out-of-memory
** errors:
**
** <ul>
** <li> sqlite3_value_blob()
** <li> sqlite3_value_text()
** <li> sqlite3_value_text16()
** <li> sqlite3_value_text16le()
** <li> sqlite3_value_text16be()
** <li> sqlite3_value_bytes()
** <li> sqlite3_value_bytes16()
** </ul>
**
** If an out-of-memory error occurs, then the return value from these
** routines is the same as if the column had contained an SQL NULL value.
** Valid SQL NULL returns can be distinguished from out-of-memory errors
** by invoking the [sqlite3_errcode()] immediately after the suspect
** return value is obtained and before any
** other SQLite interface is called on the same [database connection].
*/ */
SQLITE_API const void *sqlite3_value_blob(sqlite3_value*); SQLITE_API const void *sqlite3_value_blob(sqlite3_value*);
SQLITE_API double sqlite3_value_double(sqlite3_value*); SQLITE_API double sqlite3_value_double(sqlite3_value*);
@ -6324,6 +6443,7 @@ struct sqlite3_index_info {
#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 #define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70
#define SQLITE_INDEX_CONSTRAINT_ISNULL 71 #define SQLITE_INDEX_CONSTRAINT_ISNULL 71
#define SQLITE_INDEX_CONSTRAINT_IS 72 #define SQLITE_INDEX_CONSTRAINT_IS 72
#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150
/* /*
** CAPI3REF: Register A Virtual Table Implementation ** CAPI3REF: Register A Virtual Table Implementation
@ -7000,6 +7120,7 @@ SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*);
/* /*
** CAPI3REF: Low-Level Control Of Database Files ** CAPI3REF: Low-Level Control Of Database Files
** METHOD: sqlite3 ** METHOD: sqlite3
** KEYWORDS: {file control}
** **
** ^The [sqlite3_file_control()] interface makes a direct call to the ** ^The [sqlite3_file_control()] interface makes a direct call to the
** xFileControl method for the [sqlite3_io_methods] object associated ** xFileControl method for the [sqlite3_io_methods] object associated
@ -7014,11 +7135,18 @@ SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*);
** the xFileControl method. ^The return value of the xFileControl ** the xFileControl method. ^The return value of the xFileControl
** method becomes the return value of this routine. ** method becomes the return value of this routine.
** **
** A few opcodes for [sqlite3_file_control()] are handled directly
** by the SQLite core and never invoke the
** sqlite3_io_methods.xFileControl method.
** ^The [SQLITE_FCNTL_FILE_POINTER] value for the op parameter causes ** ^The [SQLITE_FCNTL_FILE_POINTER] value for the op parameter causes
** a pointer to the underlying [sqlite3_file] object to be written into ** a pointer to the underlying [sqlite3_file] object to be written into
** the space pointed to by the 4th parameter. ^The [SQLITE_FCNTL_FILE_POINTER] ** the space pointed to by the 4th parameter. The
** case is a short-circuit path which does not actually invoke the ** [SQLITE_FCNTL_JOURNAL_POINTER] works similarly except that it returns
** underlying sqlite3_io_methods.xFileControl method. ** the [sqlite3_file] object associated with the journal file instead of
** the main database. The [SQLITE_FCNTL_VFS_POINTER] opcode returns
** a pointer to the underlying [sqlite3_vfs] object for the file.
** The [SQLITE_FCNTL_DATA_VERSION] returns the data version counter
** from the pager.
** **
** ^If the second parameter (zDbName) does not match the name of any ** ^If the second parameter (zDbName) does not match the name of any
** open database file, then SQLITE_ERROR is returned. ^This error ** open database file, then SQLITE_ERROR is returned. ^This error
@ -8837,7 +8965,6 @@ SQLITE_API int sqlite3_system_errno(sqlite3*);
/* /*
** CAPI3REF: Database Snapshot ** CAPI3REF: Database Snapshot
** KEYWORDS: {snapshot} {sqlite3_snapshot} ** KEYWORDS: {snapshot} {sqlite3_snapshot}
** EXPERIMENTAL
** **
** An instance of the snapshot object records the state of a [WAL mode] ** An instance of the snapshot object records the state of a [WAL mode]
** database for some specific point in history. ** database for some specific point in history.
@ -8854,11 +8981,6 @@ SQLITE_API int sqlite3_system_errno(sqlite3*);
** version of the database file so that it is possible to later open a new read ** version of the database file so that it is possible to later open a new read
** transaction that sees that historical version of the database rather than ** transaction that sees that historical version of the database rather than
** the most recent version. ** the most recent version.
**
** The constructor for this object is [sqlite3_snapshot_get()]. The
** [sqlite3_snapshot_open()] method causes a fresh read transaction to refer
** to an historical snapshot (if possible). The destructor for
** sqlite3_snapshot objects is [sqlite3_snapshot_free()].
*/ */
typedef struct sqlite3_snapshot { typedef struct sqlite3_snapshot {
unsigned char hidden[48]; unsigned char hidden[48];
@ -8866,7 +8988,7 @@ typedef struct sqlite3_snapshot {
/* /*
** CAPI3REF: Record A Database Snapshot ** CAPI3REF: Record A Database Snapshot
** EXPERIMENTAL ** CONSTRUCTOR: sqlite3_snapshot
** **
** ^The [sqlite3_snapshot_get(D,S,P)] interface attempts to make a ** ^The [sqlite3_snapshot_get(D,S,P)] interface attempts to make a
** new [sqlite3_snapshot] object that records the current state of ** new [sqlite3_snapshot] object that records the current state of
@ -8882,7 +9004,7 @@ typedef struct sqlite3_snapshot {
** in this case. ** in this case.
** **
** <ul> ** <ul>
** <li> The database handle must be in [autocommit mode]. ** <li> The database handle must not be in [autocommit mode].
** **
** <li> Schema S of [database connection] D must be a [WAL mode] database. ** <li> Schema S of [database connection] D must be a [WAL mode] database.
** **
@ -8905,7 +9027,7 @@ typedef struct sqlite3_snapshot {
** to avoid a memory leak. ** to avoid a memory leak.
** **
** The [sqlite3_snapshot_get()] interface is only available when the ** The [sqlite3_snapshot_get()] interface is only available when the
** SQLITE_ENABLE_SNAPSHOT compile-time option is used. ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/ */
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
sqlite3 *db, sqlite3 *db,
@ -8915,24 +9037,35 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
/* /*
** CAPI3REF: Start a read transaction on an historical snapshot ** CAPI3REF: Start a read transaction on an historical snapshot
** EXPERIMENTAL ** METHOD: sqlite3_snapshot
** **
** ^The [sqlite3_snapshot_open(D,S,P)] interface starts a ** ^The [sqlite3_snapshot_open(D,S,P)] interface either starts a new read
** read transaction for schema S of ** transaction or upgrades an existing one for schema S of
** [database connection] D such that the read transaction ** [database connection] D such that the read transaction refers to
** refers to historical [snapshot] P, rather than the most ** historical [snapshot] P, rather than the most recent change to the
** recent change to the database. ** database. ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK
** ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK on success ** on success or an appropriate [error code] if it fails.
** or an appropriate [error code] if it fails. **
** ^In order to succeed, the database connection must not be in
** [autocommit mode] when [sqlite3_snapshot_open(D,S,P)] is called. If there
** is already a read transaction open on schema S, then the database handle
** must have no active statements (SELECT statements that have been passed
** to sqlite3_step() but not sqlite3_reset() or sqlite3_finalize()).
** SQLITE_ERROR is returned if either of these conditions is violated, or
** if schema S does not exist, or if the snapshot object is invalid.
**
** ^A call to sqlite3_snapshot_open() will fail to open if the specified
** snapshot has been overwritten by a [checkpoint]. In this case
** SQLITE_ERROR_SNAPSHOT is returned.
**
** If there is already a read transaction open when this function is
** invoked, then the same read transaction remains open (on the same
** database snapshot) if SQLITE_ERROR, SQLITE_BUSY or SQLITE_ERROR_SNAPSHOT
** is returned. If another error code - for example SQLITE_PROTOCOL or an
** SQLITE_IOERR error code - is returned, then the final state of the
** read transaction is undefined. If SQLITE_OK is returned, then the
** read transaction is now open on database snapshot P.
** **
** ^In order to succeed, a call to [sqlite3_snapshot_open(D,S,P)] must be
** the first operation following the [BEGIN] that takes the schema S
** out of [autocommit mode].
** ^In other words, schema S must not currently be in
** a transaction for [sqlite3_snapshot_open(D,S,P)] to work, but the
** database connection D must be out of [autocommit mode].
** ^A [snapshot] will fail to open if it has been overwritten by a
** [checkpoint].
** ^(A call to [sqlite3_snapshot_open(D,S,P)] will fail if the ** ^(A call to [sqlite3_snapshot_open(D,S,P)] will fail if the
** database connection D does not know that the database file for ** database connection D does not know that the database file for
** schema S is in [WAL mode]. A database connection might not know ** schema S is in [WAL mode]. A database connection might not know
@ -8943,7 +9076,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
** database connection in order to make it ready to use snapshots.) ** database connection in order to make it ready to use snapshots.)
** **
** The [sqlite3_snapshot_open()] interface is only available when the ** The [sqlite3_snapshot_open()] interface is only available when the
** SQLITE_ENABLE_SNAPSHOT compile-time option is used. ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/ */
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open( SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
sqlite3 *db, sqlite3 *db,
@ -8953,20 +9086,20 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
/* /*
** CAPI3REF: Destroy a snapshot ** CAPI3REF: Destroy a snapshot
** EXPERIMENTAL ** DESTRUCTOR: sqlite3_snapshot
** **
** ^The [sqlite3_snapshot_free(P)] interface destroys [sqlite3_snapshot] P. ** ^The [sqlite3_snapshot_free(P)] interface destroys [sqlite3_snapshot] P.
** The application must eventually free every [sqlite3_snapshot] object ** The application must eventually free every [sqlite3_snapshot] object
** using this routine to avoid a memory leak. ** using this routine to avoid a memory leak.
** **
** The [sqlite3_snapshot_free()] interface is only available when the ** The [sqlite3_snapshot_free()] interface is only available when the
** SQLITE_ENABLE_SNAPSHOT compile-time option is used. ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/ */
SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
/* /*
** CAPI3REF: Compare the ages of two snapshot handles. ** CAPI3REF: Compare the ages of two snapshot handles.
** EXPERIMENTAL ** METHOD: sqlite3_snapshot
** **
** The sqlite3_snapshot_cmp(P1, P2) interface is used to compare the ages ** The sqlite3_snapshot_cmp(P1, P2) interface is used to compare the ages
** of two valid snapshot handles. ** of two valid snapshot handles.
@ -8985,6 +9118,9 @@ SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
** Otherwise, this API returns a negative value if P1 refers to an older ** Otherwise, this API returns a negative value if P1 refers to an older
** snapshot than P2, zero if the two handles refer to the same database ** snapshot than P2, zero if the two handles refer to the same database
** snapshot, and a positive value if P1 is a newer snapshot than P2. ** snapshot, and a positive value if P1 is a newer snapshot than P2.
**
** This interface is only available if SQLite is compiled with the
** [SQLITE_ENABLE_SNAPSHOT] option.
*/ */
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
sqlite3_snapshot *p1, sqlite3_snapshot *p1,
@ -8993,23 +9129,26 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
/* /*
** CAPI3REF: Recover snapshots from a wal file ** CAPI3REF: Recover snapshots from a wal file
** EXPERIMENTAL ** METHOD: sqlite3_snapshot
** **
** If all connections disconnect from a database file but do not perform ** If a [WAL file] remains on disk after all database connections close
** a checkpoint, the existing wal file is opened along with the database ** (either through the use of the [SQLITE_FCNTL_PERSIST_WAL] [file control]
** file the next time the database is opened. At this point it is only ** or because the last process to have the database opened exited without
** possible to successfully call sqlite3_snapshot_open() to open the most ** calling [sqlite3_close()]) and a new connection is subsequently opened
** recent snapshot of the database (the one at the head of the wal file), ** on that database and [WAL file], the [sqlite3_snapshot_open()] interface
** even though the wal file may contain other valid snapshots for which ** will only be able to open the last transaction added to the WAL file
** clients have sqlite3_snapshot handles. ** even though the WAL file contains other valid transactions.
** **
** This function attempts to scan the wal file associated with database zDb ** This function attempts to scan the WAL file associated with database zDb
** of database handle db and make all valid snapshots available to ** of database handle db and make all valid snapshots available to
** sqlite3_snapshot_open(). It is an error if there is already a read ** sqlite3_snapshot_open(). It is an error if there is already a read
** transaction open on the database, or if the database is not a wal mode ** transaction open on the database, or if the database is not a WAL mode
** database. ** database.
** **
** SQLITE_OK is returned if successful, or an SQLite error code otherwise. ** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
**
** This interface is only available if SQLite is compiled with the
** [SQLITE_ENABLE_SNAPSHOT] option.
*/ */
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
@ -9120,7 +9259,7 @@ SQLITE_API int sqlite3_deserialize(
** in the P argument is held in memory obtained from [sqlite3_malloc64()] ** in the P argument is held in memory obtained from [sqlite3_malloc64()]
** and that SQLite should take ownership of this memory and automatically ** and that SQLite should take ownership of this memory and automatically
** free it when it has finished using it. Without this flag, the caller ** free it when it has finished using it. Without this flag, the caller
** is resposible for freeing any dynamically allocated memory. ** is responsible for freeing any dynamically allocated memory.
** **
** The SQLITE_DESERIALIZE_RESIZEABLE flag means that SQLite is allowed to ** The SQLITE_DESERIALIZE_RESIZEABLE flag means that SQLite is allowed to
** grow the size of the database using calls to [sqlite3_realloc64()]. This ** grow the size of the database using calls to [sqlite3_realloc64()]. This
@ -11298,7 +11437,7 @@ struct Fts5ExtensionApi {
** This way, even if the tokenizer does not provide synonyms ** This way, even if the tokenizer does not provide synonyms
** when tokenizing query text (it should not - to do would be ** when tokenizing query text (it should not - to do would be
** inefficient), it doesn't matter if the user queries for ** inefficient), it doesn't matter if the user queries for
** 'first + place' or '1st + place', as there are entires in the ** 'first + place' or '1st + place', as there are entries in the
** FTS index corresponding to both forms of the first token. ** FTS index corresponding to both forms of the first token.
** </ol> ** </ol>
** **
@ -11326,7 +11465,7 @@ struct Fts5ExtensionApi {
** extra data to the FTS index or require FTS5 to query for multiple terms, ** extra data to the FTS index or require FTS5 to query for multiple terms,
** so it is efficient in terms of disk space and query speed. However, it ** so it is efficient in terms of disk space and query speed. However, it
** does not support prefix queries very well. If, as suggested above, the ** does not support prefix queries very well. If, as suggested above, the
** token "first" is subsituted for "1st" by the tokenizer, then the query: ** token "first" is substituted for "1st" by the tokenizer, then the query:
** **
** <codeblock> ** <codeblock>
** ... MATCH '1s*'</codeblock> ** ... MATCH '1s*'</codeblock>

View file

@ -78,8 +78,38 @@ _sqlite3_exec(sqlite3* db, const char* pcmd, long long* rowid, long long* change
return rv; return rv;
} }
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
extern int _sqlite3_step_blocking(sqlite3_stmt *stmt);
extern int _sqlite3_step_row_blocking(sqlite3_stmt* stmt, long long* rowid, long long* changes);
extern int _sqlite3_prepare_v2_blocking(sqlite3 *db, const char *zSql, int nBytes, sqlite3_stmt **ppStmt, const char **pzTail);
static int static int
_sqlite3_step(sqlite3_stmt* stmt, long long* rowid, long long* changes) _sqlite3_step_internal(sqlite3_stmt *stmt)
{
return _sqlite3_step_blocking(stmt);
}
static int
_sqlite3_step_row_internal(sqlite3_stmt* stmt, long long* rowid, long long* changes)
{
return _sqlite3_step_row_blocking(stmt, rowid, changes);
}
static int
_sqlite3_prepare_v2_internal(sqlite3 *db, const char *zSql, int nBytes, sqlite3_stmt **ppStmt, const char **pzTail)
{
return _sqlite3_prepare_v2_blocking(db, zSql, nBytes, ppStmt, pzTail);
}
#else
static int
_sqlite3_step_internal(sqlite3_stmt *stmt)
{
return sqlite3_step(stmt);
}
static int
_sqlite3_step_row_internal(sqlite3_stmt* stmt, long long* rowid, long long* changes)
{ {
int rv = sqlite3_step(stmt); int rv = sqlite3_step(stmt);
sqlite3* db = sqlite3_db_handle(stmt); sqlite3* db = sqlite3_db_handle(stmt);
@ -88,6 +118,13 @@ _sqlite3_step(sqlite3_stmt* stmt, long long* rowid, long long* changes)
return rv; return rv;
} }
static int
_sqlite3_prepare_v2_internal(sqlite3 *db, const char *zSql, int nBytes, sqlite3_stmt **ppStmt, const char **pzTail)
{
return sqlite3_prepare_v2(db, zSql, nBytes, ppStmt, pzTail);
}
#endif
void _sqlite3_result_text(sqlite3_context* ctx, const char* s) { void _sqlite3_result_text(sqlite3_context* ctx, const char* s) {
sqlite3_result_text(ctx, s, -1, &free); sqlite3_result_text(ctx, s, -1, &free);
} }
@ -119,6 +156,8 @@ int commitHookTrampoline(void*);
void rollbackHookTrampoline(void*); void rollbackHookTrampoline(void*);
void updateHookTrampoline(void*, int, char*, char*, sqlite3_int64); void updateHookTrampoline(void*, int, char*, char*, sqlite3_int64);
int authorizerTrampoline(void*, int, char*, char*, char*, char*);
#ifdef SQLITE_LIMIT_WORKER_THREADS #ifdef SQLITE_LIMIT_WORKER_THREADS
# define _SQLITE_HAS_LIMIT # define _SQLITE_HAS_LIMIT
# define SQLITE_LIMIT_LENGTH 0 # define SQLITE_LIMIT_LENGTH 0
@ -200,18 +239,57 @@ func Version() (libVersion string, libVersionNumber int, sourceID string) {
} }
const ( const (
// used by authorizer and pre_update_hook
SQLITE_DELETE = C.SQLITE_DELETE SQLITE_DELETE = C.SQLITE_DELETE
SQLITE_INSERT = C.SQLITE_INSERT SQLITE_INSERT = C.SQLITE_INSERT
SQLITE_UPDATE = C.SQLITE_UPDATE SQLITE_UPDATE = C.SQLITE_UPDATE
// used by authorzier - as return value
SQLITE_OK = C.SQLITE_OK
SQLITE_IGNORE = C.SQLITE_IGNORE
SQLITE_DENY = C.SQLITE_DENY
// different actions query tries to do - passed as argument to authorizer
SQLITE_CREATE_INDEX = C.SQLITE_CREATE_INDEX
SQLITE_CREATE_TABLE = C.SQLITE_CREATE_TABLE
SQLITE_CREATE_TEMP_INDEX = C.SQLITE_CREATE_TEMP_INDEX
SQLITE_CREATE_TEMP_TABLE = C.SQLITE_CREATE_TEMP_TABLE
SQLITE_CREATE_TEMP_TRIGGER = C.SQLITE_CREATE_TEMP_TRIGGER
SQLITE_CREATE_TEMP_VIEW = C.SQLITE_CREATE_TEMP_VIEW
SQLITE_CREATE_TRIGGER = C.SQLITE_CREATE_TRIGGER
SQLITE_CREATE_VIEW = C.SQLITE_CREATE_VIEW
SQLITE_CREATE_VTABLE = C.SQLITE_CREATE_VTABLE
SQLITE_DROP_INDEX = C.SQLITE_DROP_INDEX
SQLITE_DROP_TABLE = C.SQLITE_DROP_TABLE
SQLITE_DROP_TEMP_INDEX = C.SQLITE_DROP_TEMP_INDEX
SQLITE_DROP_TEMP_TABLE = C.SQLITE_DROP_TEMP_TABLE
SQLITE_DROP_TEMP_TRIGGER = C.SQLITE_DROP_TEMP_TRIGGER
SQLITE_DROP_TEMP_VIEW = C.SQLITE_DROP_TEMP_VIEW
SQLITE_DROP_TRIGGER = C.SQLITE_DROP_TRIGGER
SQLITE_DROP_VIEW = C.SQLITE_DROP_VIEW
SQLITE_DROP_VTABLE = C.SQLITE_DROP_VTABLE
SQLITE_PRAGMA = C.SQLITE_PRAGMA
SQLITE_READ = C.SQLITE_READ
SQLITE_SELECT = C.SQLITE_SELECT
SQLITE_TRANSACTION = C.SQLITE_TRANSACTION
SQLITE_ATTACH = C.SQLITE_ATTACH
SQLITE_DETACH = C.SQLITE_DETACH
SQLITE_ALTER_TABLE = C.SQLITE_ALTER_TABLE
SQLITE_REINDEX = C.SQLITE_REINDEX
SQLITE_ANALYZE = C.SQLITE_ANALYZE
SQLITE_FUNCTION = C.SQLITE_FUNCTION
SQLITE_SAVEPOINT = C.SQLITE_SAVEPOINT
SQLITE_COPY = C.SQLITE_COPY
/*SQLITE_RECURSIVE = C.SQLITE_RECURSIVE*/
) )
// SQLiteDriver implement sql.Driver. // SQLiteDriver implements driver.Driver.
type SQLiteDriver struct { type SQLiteDriver struct {
Extensions []string Extensions []string
ConnectHook func(*SQLiteConn) error ConnectHook func(*SQLiteConn) error
} }
// SQLiteConn implement sql.Conn. // SQLiteConn implements driver.Conn.
type SQLiteConn struct { type SQLiteConn struct {
mu sync.Mutex mu sync.Mutex
db *C.sqlite3 db *C.sqlite3
@ -221,12 +299,12 @@ type SQLiteConn struct {
aggregators []*aggInfo aggregators []*aggInfo
} }
// SQLiteTx implemen sql.Tx. // SQLiteTx implements driver.Tx.
type SQLiteTx struct { type SQLiteTx struct {
c *SQLiteConn c *SQLiteConn
} }
// SQLiteStmt implement sql.Stmt. // SQLiteStmt implements driver.Stmt.
type SQLiteStmt struct { type SQLiteStmt struct {
mu sync.Mutex mu sync.Mutex
c *SQLiteConn c *SQLiteConn
@ -236,13 +314,13 @@ type SQLiteStmt struct {
cls bool cls bool
} }
// SQLiteResult implement sql.Result. // SQLiteResult implements sql.Result.
type SQLiteResult struct { type SQLiteResult struct {
id int64 id int64
changes int64 changes int64
} }
// SQLiteRows implement sql.Rows. // SQLiteRows implements driver.Rows.
type SQLiteRows struct { type SQLiteRows struct {
s *SQLiteStmt s *SQLiteStmt
nc int nc int
@ -440,6 +518,20 @@ func (c *SQLiteConn) RegisterUpdateHook(callback func(int, string, string, int64
} }
} }
// RegisterAuthorizer sets the authorizer for connection.
//
// The parameters to the callback are the operation (one of the constants
// SQLITE_INSERT, SQLITE_DELETE, or SQLITE_UPDATE), and 1 to 3 arguments,
// depending on operation. More details see:
// https://www.sqlite.org/c3ref/c_alter_table.html
func (c *SQLiteConn) RegisterAuthorizer(callback func(int, string, string, string) int) {
if callback == nil {
C.sqlite3_set_authorizer(c.db, nil, nil)
} else {
C.sqlite3_set_authorizer(c.db, (*[0]byte)(C.authorizerTrampoline), unsafe.Pointer(newHandle(c, callback)))
}
}
// RegisterFunc makes a Go function available as a SQLite function. // RegisterFunc makes a Go function available as a SQLite function.
// //
// The Go function can have arguments of the following types: any // The Go function can have arguments of the following types: any
@ -1582,7 +1674,7 @@ func (c *SQLiteConn) prepare(ctx context.Context, query string) (driver.Stmt, er
defer C.free(unsafe.Pointer(pquery)) defer C.free(unsafe.Pointer(pquery))
var s *C.sqlite3_stmt var s *C.sqlite3_stmt
var tail *C.char var tail *C.char
rv := C.sqlite3_prepare_v2(c.db, pquery, -1, &s, &tail) rv := C._sqlite3_prepare_v2_internal(c.db, pquery, -1, &s, &tail)
if rv != C.SQLITE_OK { if rv != C.SQLITE_OK {
return nil, c.lastError() return nil, c.lastError()
} }
@ -1816,7 +1908,7 @@ func (s *SQLiteStmt) exec(ctx context.Context, args []namedValue) (driver.Result
} }
var rowid, changes C.longlong var rowid, changes C.longlong
rv := C._sqlite3_step(s.s, &rowid, &changes) rv := C._sqlite3_step_row_internal(s.s, &rowid, &changes)
if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE { if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE {
err := s.c.lastError() err := s.c.lastError()
C.sqlite3_reset(s.s) C.sqlite3_reset(s.s)
@ -1883,12 +1975,12 @@ func (rc *SQLiteRows) DeclTypes() []string {
// Next move cursor to next. // Next move cursor to next.
func (rc *SQLiteRows) Next(dest []driver.Value) error { func (rc *SQLiteRows) Next(dest []driver.Value) error {
rc.s.mu.Lock()
defer rc.s.mu.Unlock()
if rc.s.closed { if rc.s.closed {
return io.EOF return io.EOF
} }
rc.s.mu.Lock() rv := C._sqlite3_step_internal(rc.s.s)
defer rc.s.mu.Unlock()
rv := C.sqlite3_step(rc.s.s)
if rv == C.SQLITE_DONE { if rv == C.SQLITE_DONE {
return io.EOF return io.EOF
} }

View file

@ -83,13 +83,13 @@ func CryptEncoderSSHA256(salt string) func(pass []byte, hash interface{}) []byte
} }
} }
// CryptEncoderSHA384 encodes a password with SHA256 // CryptEncoderSHA384 encodes a password with SHA384
func CryptEncoderSHA384(pass []byte, hash interface{}) []byte { func CryptEncoderSHA384(pass []byte, hash interface{}) []byte {
h := sha512.Sum384(pass) h := sha512.Sum384(pass)
return h[:] return h[:]
} }
// CryptEncoderSSHA384 encodes a password with SHA256 // CryptEncoderSSHA384 encodes a password with SHA384
// with the configured salt // with the configured salt
func CryptEncoderSSHA384(salt string) func(pass []byte, hash interface{}) []byte { func CryptEncoderSSHA384(salt string) func(pass []byte, hash interface{}) []byte {
return func(pass []byte, hash interface{}) []byte { return func(pass []byte, hash interface{}) []byte {
@ -100,13 +100,13 @@ func CryptEncoderSSHA384(salt string) func(pass []byte, hash interface{}) []byte
} }
} }
// CryptEncoderSHA512 encodes a password with SHA256 // CryptEncoderSHA512 encodes a password with SHA512
func CryptEncoderSHA512(pass []byte, hash interface{}) []byte { func CryptEncoderSHA512(pass []byte, hash interface{}) []byte {
h := sha512.Sum512(pass) h := sha512.Sum512(pass)
return h[:] return h[:]
} }
// CryptEncoderSSHA512 encodes a password with SHA256 // CryptEncoderSSHA512 encodes a password with SHA512
// with the configured salt // with the configured salt
func CryptEncoderSSHA512(salt string) func(pass []byte, hash interface{}) []byte { func CryptEncoderSSHA512(salt string) func(pass []byte, hash interface{}) []byte {
return func(pass []byte, hash interface{}) []byte { return func(pass []byte, hash interface{}) []byte {

View file

@ -0,0 +1,85 @@
// Copyright (C) 2018 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
#include <stdio.h>
#include <sqlite3-binding.h>
extern int unlock_notify_wait(sqlite3 *db);
int
_sqlite3_step_blocking(sqlite3_stmt *stmt)
{
int rv;
sqlite3* db;
db = sqlite3_db_handle(stmt);
for (;;) {
rv = sqlite3_step(stmt);
if (rv != SQLITE_LOCKED) {
break;
}
if (sqlite3_extended_errcode(db) != SQLITE_LOCKED_SHAREDCACHE) {
break;
}
rv = unlock_notify_wait(db);
if (rv != SQLITE_OK) {
break;
}
sqlite3_reset(stmt);
}
return rv;
}
int
_sqlite3_step_row_blocking(sqlite3_stmt* stmt, long long* rowid, long long* changes)
{
int rv;
sqlite3* db;
db = sqlite3_db_handle(stmt);
for (;;) {
rv = sqlite3_step(stmt);
if (rv!=SQLITE_LOCKED) {
break;
}
if (sqlite3_extended_errcode(db) != SQLITE_LOCKED_SHAREDCACHE) {
break;
}
rv = unlock_notify_wait(db);
if (rv != SQLITE_OK) {
break;
}
sqlite3_reset(stmt);
}
*rowid = (long long) sqlite3_last_insert_rowid(db);
*changes = (long long) sqlite3_changes(db);
return rv;
}
int
_sqlite3_prepare_v2_blocking(sqlite3 *db, const char *zSql, int nBytes, sqlite3_stmt **ppStmt, const char **pzTail)
{
int rv;
for (;;) {
rv = sqlite3_prepare_v2(db, zSql, nBytes, ppStmt, pzTail);
if (rv!=SQLITE_LOCKED) {
break;
}
if (sqlite3_extended_errcode(db) != SQLITE_LOCKED_SHAREDCACHE) {
break;
}
rv = unlock_notify_wait(db);
if (rv != SQLITE_OK) {
break;
}
}
return rv;
}
#endif

View file

@ -0,0 +1,93 @@
// Copyright (C) 2018 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// +build cgo
// +build sqlite_unlock_notify
package sqlite3
/*
#cgo CFLAGS: -DSQLITE_ENABLE_UNLOCK_NOTIFY
#include <stdlib.h>
#include <sqlite3-binding.h>
extern void unlock_notify_callback(void *arg, int argc);
*/
import "C"
import (
"fmt"
"math"
"sync"
"unsafe"
)
type unlock_notify_table struct {
sync.Mutex
seqnum uint
table map[uint]chan struct{}
}
var unt unlock_notify_table = unlock_notify_table{table: make(map[uint]chan struct{})}
func (t *unlock_notify_table) add(c chan struct{}) uint {
t.Lock()
defer t.Unlock()
h := t.seqnum
t.table[h] = c
t.seqnum++
return h
}
func (t *unlock_notify_table) remove(h uint) {
t.Lock()
defer t.Unlock()
delete(t.table, h)
}
func (t *unlock_notify_table) get(h uint) chan struct{} {
t.Lock()
defer t.Unlock()
c, ok := t.table[h]
if !ok {
panic(fmt.Sprintf("Non-existent key for unlcok-notify channel: %d", h))
}
return c
}
//export unlock_notify_callback
func unlock_notify_callback(argv unsafe.Pointer, argc C.int) {
for i := 0; i < int(argc); i++ {
parg := ((*(*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.uint)(nil))]*[1]uint)(argv))[i])
arg := *parg
h := arg[0]
c := unt.get(h)
c <- struct{}{}
}
}
//export unlock_notify_wait
func unlock_notify_wait(db *C.sqlite3) C.int {
// It has to be a bufferred channel to not block in sqlite_unlock_notify
// as sqlite_unlock_notify could invoke the callback before it returns.
c := make(chan struct{}, 1)
defer close(c)
h := unt.add(c)
defer unt.remove(h)
pargv := C.malloc(C.sizeof_uint)
defer C.free(pargv)
argv := (*[1]uint)(pargv)
argv[0] = h
if rv := C.sqlite3_unlock_notify(db, (*[0]byte)(C.unlock_notify_callback), unsafe.Pointer(pargv)); rv != C.SQLITE_OK {
return rv
}
<-c
return C.SQLITE_OK
}

View file

@ -10,5 +10,8 @@ package sqlite3
/* /*
#cgo CFLAGS: -I. #cgo CFLAGS: -I.
#cgo linux LDFLAGS: -ldl #cgo linux LDFLAGS: -ldl
#cgo linux,ppc LDFLAGS: -lpthread
#cgo linux,ppc64 LDFLAGS: -lpthread
#cgo linux,ppc64le LDFLAGS: -lpthread
*/ */
import "C" import "C"

View file

@ -311,6 +311,12 @@ struct sqlite3_api_routines {
int (*str_errcode)(sqlite3_str*); int (*str_errcode)(sqlite3_str*);
int (*str_length)(sqlite3_str*); int (*str_length)(sqlite3_str*);
char *(*str_value)(sqlite3_str*); char *(*str_value)(sqlite3_str*);
int (*create_window_function)(sqlite3*,const char*,int,int,void*,
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*),
void (*xValue)(sqlite3_context*),
void (*xInv)(sqlite3_context*,int,sqlite3_value**),
void(*xDestroy)(void*));
}; };
/* /*
@ -596,6 +602,8 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_str_errcode sqlite3_api->str_errcode #define sqlite3_str_errcode sqlite3_api->str_errcode
#define sqlite3_str_length sqlite3_api->str_length #define sqlite3_str_length sqlite3_api->str_length
#define sqlite3_str_value sqlite3_api->str_value #define sqlite3_str_value sqlite3_api->str_value
/* Version 3.25.0 and later */
#define sqlite3_create_window_function sqlite3_api->create_window_function
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)

202
vendor/google.golang.org/appengine/LICENSE generated vendored Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,62 @@
// Copyright 2013 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
Package cloudsql exposes access to Google Cloud SQL databases.
This package does not work in App Engine "flexible environment".
This package is intended for MySQL drivers to make App Engine-specific
connections. Applications should use this package through database/sql:
Select a pure Go MySQL driver that supports this package, and use sql.Open
with protocol "cloudsql" and an address of the Cloud SQL instance.
A Go MySQL driver that has been tested to work well with Cloud SQL
is the go-sql-driver:
import "database/sql"
import _ "github.com/go-sql-driver/mysql"
db, err := sql.Open("mysql", "user@cloudsql(project-id:instance-name)/dbname")
Another driver that works well with Cloud SQL is the mymysql driver:
import "database/sql"
import _ "github.com/ziutek/mymysql/godrv"
db, err := sql.Open("mymysql", "cloudsql:instance-name*dbname/user/password")
Using either of these drivers, you can perform a standard SQL query.
This example assumes there is a table named 'users' with
columns 'first_name' and 'last_name':
rows, err := db.Query("SELECT first_name, last_name FROM users")
if err != nil {
log.Errorf(ctx, "db.Query: %v", err)
}
defer rows.Close()
for rows.Next() {
var firstName string
var lastName string
if err := rows.Scan(&firstName, &lastName); err != nil {
log.Errorf(ctx, "rows.Scan: %v", err)
continue
}
log.Infof(ctx, "First: %v - Last: %v", firstName, lastName)
}
if err := rows.Err(); err != nil {
log.Errorf(ctx, "Row error: %v", err)
}
*/
package cloudsql
import (
"net"
)
// Dial connects to the named Cloud SQL instance.
func Dial(instance string) (net.Conn, error) {
return connect(instance)
}

View file

@ -0,0 +1,17 @@
// Copyright 2013 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// +build appengine
package cloudsql
import (
"net"
"appengine/cloudsql"
)
func connect(instance string) (net.Conn, error) {
return cloudsql.Dial(instance)
}

View file

@ -0,0 +1,16 @@
// Copyright 2013 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// +build !appengine
package cloudsql
import (
"errors"
"net"
)
func connect(instance string) (net.Conn, error) {
return nil, errors.New(`cloudsql: not supported in App Engine "flexible environment"`)
}

View file

@ -1,8 +1,8 @@
language: go language: go
go: go:
- '1.9'
- '1.10' - '1.10'
- '1.11'
services: services:
- postgresql - postgresql

View file

@ -229,6 +229,15 @@ errors with the previous approach. It is as simple as using:
&testfixtures.PostgreSQL{UseAlterConstraint: true} &testfixtures.PostgreSQL{UseAlterConstraint: true}
``` ```
#### Skipping reset of sequences
You can skip the reset of PostgreSQL sequences if you're debugging a problem
with it, but most of the time you shouldn't do it:
```go
&testfixtures.PostgreSQL{SkipResetSequences: true}
```
### MySQL / MariaDB ### MySQL / MariaDB
Just make sure the connection string have Just make sure the connection string have
@ -302,6 +311,27 @@ if err != nil {
> This was thought to run in small sample databases. It will likely break > This was thought to run in small sample databases. It will likely break
if run in a production/big database. if run in a production/big database.
## Gotchas
### Parallel testing
This library doesn't yet support running tests in parallel! Running tests
in parallel can result in random data being present in the database, which
will likely cause tests to randomly/intermittently fail.
This is specially tricky since it's not immediately clear that `go test ./...`
run tests for each package in parallel. If more than one package use this
library, you can face this issue. Please, use `go test -p 1 ./...` or run tests
for each package in separated commands to fix this issue.
See [#40](https://github.com/go-testfixtures/testfixtures/issues/40)
and [golang/go#11521](https://github.com/golang/go/issues/11521) for more information.
We're also planning to implement transactional tests to allow running tests
in parallel (see [#24](https://github.com/go-testfixtures/testfixtures/issues/24)).
Running each test package in a separated database would also allow you to do that.
Open issues for other ideas :slightly_smiling_face:.
## Contributing ## Contributing
Tests were written to ensure everything work as expected. You can run the tests Tests were written to ensure everything work as expected. You can run the tests

View file

@ -1,4 +1,4 @@
# github.com/go-task/task # https://taskfile.org
version: '2' version: '2'
@ -6,7 +6,7 @@ tasks:
dl-deps: dl-deps:
desc: Download cli deps desc: Download cli deps
cmds: cmds:
- go get -u github.com/golang/lint/golint - go get -u golang.org/x/lint/golint
lint: lint:
desc: Runs golint desc: Runs golint

View file

@ -27,7 +27,7 @@ environment:
MINGW_PATH: C:\MinGW MINGW_PATH: C:\MinGW
GOPATH: C:\GOPATH GOPATH: C:\GOPATH
GOVERSION: 1.10.3 GOVERSION: 1.11.2
install: install:
- SET PATH=%POSTGRES_PATH%\bin;%MYSQL_PATH%\bin;%MINGW_PATH%\bin;%PATH% - SET PATH=%POSTGRES_PATH%\bin;%MYSQL_PATH%\bin;%MINGW_PATH%\bin;%PATH%

15
vendor/gopkg.in/testfixtures.v2/go.mod generated vendored Normal file
View file

@ -0,0 +1,15 @@
module gopkg.in/testfixtures.v2
require (
cloud.google.com/go v0.33.1 // indirect
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f
github.com/go-sql-driver/mysql v1.4.1
github.com/google/go-cmp v0.2.0 // indirect
github.com/joho/godotenv v1.3.0
github.com/lib/pq v1.0.0
github.com/mattn/go-oci8 v0.0.0-20181115070430-6eefff3c767c
github.com/mattn/go-sqlite3 v1.10.0
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 // indirect
google.golang.org/appengine v1.3.0 // indirect
gopkg.in/yaml.v2 v2.2.1
)

27
vendor/gopkg.in/testfixtures.v2/go.sum generated vendored Normal file
View file

@ -0,0 +1,27 @@
cloud.google.com/go v0.33.1 h1:fmJQWZ1w9PGkHR1YL/P7HloDvqlmKQ4Vpb7PC2e+aCk=
cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f h1:WH0w/R4Yoey+04HhFxqZ6VX6I0d7RMyw5aXQ9UTvQPs=
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-oci8 v0.0.0-20181115070430-6eefff3c767c h1:RkC3vqmJwowDCqtL7d8cFEMNdoGHBcqoR4jKO9/mWuA=
github.com/mattn/go-oci8 v0.0.0-20181115070430-6eefff3c767c/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 h1:kkXA53yGe04D0adEYJwEVQjeBppL01Exg+fnMjfUraU=
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -70,13 +70,6 @@ func (h *MySQL) tableNames(q queryable) ([]string, error) {
} }
func (h *MySQL) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) { func (h *MySQL) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) {
// re-enable after load
defer func() {
if _, err2 := db.Exec("SET FOREIGN_KEY_CHECKS = 1"); err2 != nil && err == nil {
err = err2
}
}()
tx, err := db.Begin() tx, err := db.Begin()
if err != nil { if err != nil {
return err return err
@ -87,9 +80,14 @@ func (h *MySQL) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (er
return err return err
} }
if err = loadFn(tx); err != nil { err = loadFn(tx)
_, err2 := tx.Exec("SET FOREIGN_KEY_CHECKS = 1")
if err != nil {
return err return err
} }
if err2 != nil {
return err2
}
return tx.Commit() return tx.Commit()
} }

View file

@ -16,6 +16,10 @@ type PostgreSQL struct {
// which requires SUPERUSER privileges. // which requires SUPERUSER privileges.
UseAlterConstraint bool UseAlterConstraint bool
// SkipResetSequences prevents the reset of the databases
// sequences after load fixtures time
SkipResetSequences bool
tables []string tables []string
sequences []string sequences []string
nonDeferrableConstraints []pgConstraint nonDeferrableConstraints []pgConstraint
@ -216,11 +220,13 @@ func (h *PostgreSQL) makeConstraintsDeferrable(db *sql.DB, loadFn loadFunction)
func (h *PostgreSQL) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) { func (h *PostgreSQL) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) {
// ensure sequences being reset after load // ensure sequences being reset after load
defer func() { if !h.SkipResetSequences {
if err2 := h.resetSequences(db); err2 != nil && err == nil { defer func() {
err = err2 if err2 := h.resetSequences(db); err2 != nil && err == nil {
} err = err2
}() }
}()
}
if h.UseAlterConstraint { if h.UseAlterConstraint {
return h.makeConstraintsDeferrable(db, loadFn) return h.makeConstraintsDeferrable(db, loadFn)

View file

@ -89,19 +89,30 @@ func newContext(db *sql.DB, helper Helper, fixtures []*fixtureFile) (*Context, e
return c, nil return c, nil
} }
// DetectTestDatabase returns nil if databaseName matches regexp
// if err := fixtures.DetectTestDatabase(); err != nil {
// log.Fatal(err)
// }
func (c *Context) DetectTestDatabase() error {
dbName, err := c.helper.databaseName(c.db)
if err != nil {
return err
}
if !dbnameRegexp.MatchString(dbName) {
return ErrNotTestDatabase
}
return nil
}
// Load wipes and after load all fixtures in the database. // Load wipes and after load all fixtures in the database.
// if err := fixtures.Load(); err != nil { // if err := fixtures.Load(); err != nil {
// log.Fatal(err) // log.Fatal(err)
// } // }
func (c *Context) Load() error { func (c *Context) Load() error {
if !skipDatabaseNameCheck { if !skipDatabaseNameCheck {
dbName, err := c.helper.databaseName(c.db) if err := c.DetectTestDatabase(); err != nil {
if err != nil {
return err return err
} }
if !dbnameRegexp.MatchString(dbName) {
return ErrNotTestDatabase
}
} }
err := c.helper.disableReferentialIntegrity(c.db, func(tx *sql.Tx) error { err := c.helper.disableReferentialIntegrity(c.db, func(tx *sql.Tx) error {
@ -257,7 +268,8 @@ func fixturesFromFolder(folderName string) ([]*fixtureFile, error) {
} }
for _, fileinfo := range fileinfos { for _, fileinfo := range fileinfos {
if !fileinfo.IsDir() && filepath.Ext(fileinfo.Name()) == ".yml" { fileExt := filepath.Ext(fileinfo.Name())
if !fileinfo.IsDir() && (fileExt == ".yml" || fileExt == ".yaml") {
fixture := &fixtureFile{ fixture := &fixtureFile{
path: path.Join(folderName, fileinfo.Name()), path: path.Join(folderName, fileinfo.Name()),
fileName: fileinfo.Name(), fileName: fileinfo.Name(),

28
vendor/gopkg.in/yaml.v2/encode.go generated vendored
View file

@ -13,6 +13,19 @@ import (
"unicode/utf8" "unicode/utf8"
) )
// jsonNumber is the interface of the encoding/json.Number datatype.
// Repeating the interface here avoids a dependency on encoding/json, and also
// supports other libraries like jsoniter, which use a similar datatype with
// the same interface. Detecting this interface is useful when dealing with
// structures containing json.Number, which is a string under the hood. The
// encoder should prefer the use of Int64(), Float64() and string(), in that
// order, when encoding this type.
type jsonNumber interface {
Float64() (float64, error)
Int64() (int64, error)
String() string
}
type encoder struct { type encoder struct {
emitter yaml_emitter_t emitter yaml_emitter_t
event yaml_event_t event yaml_event_t
@ -89,6 +102,21 @@ func (e *encoder) marshal(tag string, in reflect.Value) {
} }
iface := in.Interface() iface := in.Interface()
switch m := iface.(type) { switch m := iface.(type) {
case jsonNumber:
integer, err := m.Int64()
if err == nil {
// In this case the json.Number is a valid int64
in = reflect.ValueOf(integer)
break
}
float, err := m.Float64()
if err == nil {
// In this case the json.Number is a valid float64
in = reflect.ValueOf(float)
break
}
// fallback case - no number could be obtained
in = reflect.ValueOf(m.String())
case time.Time, *time.Time: case time.Time, *time.Time:
// Although time.Time implements TextMarshaler, // Although time.Time implements TextMarshaler,
// we don't want to treat it as a string for YAML // we don't want to treat it as a string for YAML

14
vendor/modules.txt vendored
View file

@ -45,7 +45,7 @@ github.com/go-redis/redis/internal/pool
github.com/go-redis/redis/internal/proto github.com/go-redis/redis/internal/proto
github.com/go-redis/redis/internal/singleflight github.com/go-redis/redis/internal/singleflight
github.com/go-redis/redis/internal/util github.com/go-redis/redis/internal/util
# github.com/go-sql-driver/mysql v0.0.0-20171007150158-ee359f95877b # github.com/go-sql-driver/mysql v1.4.1
github.com/go-sql-driver/mysql github.com/go-sql-driver/mysql
# github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25 # github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25
github.com/go-xorm/builder github.com/go-xorm/builder
@ -92,7 +92,7 @@ github.com/mailru/easyjson/buffer
github.com/mattn/go-colorable github.com/mattn/go-colorable
# github.com/mattn/go-isatty v0.0.4 # github.com/mattn/go-isatty v0.0.4
github.com/mattn/go-isatty github.com/mattn/go-isatty
# github.com/mattn/go-sqlite3 v1.9.0 # github.com/mattn/go-sqlite3 v1.10.0
github.com/mattn/go-sqlite3 github.com/mattn/go-sqlite3
# github.com/matttproud/golang_protobuf_extensions v1.0.1 # github.com/matttproud/golang_protobuf_extensions v1.0.1
github.com/matttproud/golang_protobuf_extensions/pbutil github.com/matttproud/golang_protobuf_extensions/pbutil
@ -149,7 +149,7 @@ github.com/urfave/cli
github.com/valyala/bytebufferpool github.com/valyala/bytebufferpool
# github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 # github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4
github.com/valyala/fasttemplate github.com/valyala/fasttemplate
# golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 # golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
golang.org/x/crypto/bcrypt golang.org/x/crypto/bcrypt
golang.org/x/crypto/acme/autocert golang.org/x/crypto/acme/autocert
golang.org/x/crypto/blowfish golang.org/x/crypto/blowfish
@ -157,7 +157,7 @@ golang.org/x/crypto/acme
# golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 # golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3
golang.org/x/lint/golint golang.org/x/lint/golint
golang.org/x/lint golang.org/x/lint
# golang.org/x/net v0.0.0-20181201002055-351d144fa1fc # golang.org/x/net v0.0.0-20181217023233-e147a9138326
golang.org/x/net/webdav golang.org/x/net/webdav
golang.org/x/net/context golang.org/x/net/context
golang.org/x/net/webdav/internal/xml golang.org/x/net/webdav/internal/xml
@ -177,11 +177,13 @@ golang.org/x/tools/go/gcexportdata
golang.org/x/tools/go/buildutil golang.org/x/tools/go/buildutil
golang.org/x/tools/go/internal/cgo golang.org/x/tools/go/internal/cgo
golang.org/x/tools/go/internal/gcimporter golang.org/x/tools/go/internal/gcimporter
# google.golang.org/appengine v1.3.0
google.golang.org/appengine/cloudsql
# gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc # gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc
gopkg.in/alexcesaro/quotedprintable.v3 gopkg.in/alexcesaro/quotedprintable.v3
# gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df # gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/gomail.v2 gopkg.in/gomail.v2
# gopkg.in/testfixtures.v2 v2.4.5 # gopkg.in/testfixtures.v2 v2.5.3
gopkg.in/testfixtures.v2 gopkg.in/testfixtures.v2
# gopkg.in/yaml.v2 v2.2.1 # gopkg.in/yaml.v2 v2.2.2
gopkg.in/yaml.v2 gopkg.in/yaml.v2