Commit f468e10f authored by Brad Davidson's avatar Brad Davidson Committed by Brad Davidson

bump kine to v0.6.5

parent b526e98d
...@@ -29,6 +29,7 @@ replace ( ...@@ -29,6 +29,7 @@ replace (
github.com/opencontainers/runc => github.com/opencontainers/runc v1.0.2 github.com/opencontainers/runc => github.com/opencontainers/runc v1.0.2
github.com/opencontainers/runtime-spec => github.com/opencontainers/runtime-spec v1.0.3-0.20210316141917-a8c4a9ee0f6b github.com/opencontainers/runtime-spec => github.com/opencontainers/runtime-spec v1.0.3-0.20210316141917-a8c4a9ee0f6b
github.com/rancher/k3s/pkg/data => ./pkg/data github.com/rancher/k3s/pkg/data => ./pkg/data
github.com/rancher/wrangler => github.com/rancher/wrangler v0.6.2
go.etcd.io/etcd => github.com/k3s-io/etcd v0.5.0-alpha.5.0.20201208200253-50621aee4aea go.etcd.io/etcd => github.com/k3s-io/etcd v0.5.0-alpha.5.0.20201208200253-50621aee4aea
golang.org/x/crypto => golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 golang.org/x/crypto => golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/net => golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 golang.org/x/net => golang.org/x/net v0.0.0-20210224082022-3d97a244fca7
...@@ -80,7 +81,7 @@ require ( ...@@ -80,7 +81,7 @@ require (
github.com/erikdubbelboer/gspt v0.0.0-20190125194910-e68493906b83 github.com/erikdubbelboer/gspt v0.0.0-20190125194910-e68493906b83
github.com/flannel-io/flannel v0.15.1 github.com/flannel-io/flannel v0.15.1
github.com/go-bindata/go-bindata v3.1.2+incompatible github.com/go-bindata/go-bindata v3.1.2+incompatible
github.com/go-sql-driver/mysql v1.4.1 github.com/go-sql-driver/mysql v1.6.0
github.com/golangplus/testing v1.0.0 // indirect github.com/golangplus/testing v1.0.0 // indirect
github.com/google/cadvisor v0.39.0 github.com/google/cadvisor v0.39.0
github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2 github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2
...@@ -88,11 +89,11 @@ require ( ...@@ -88,11 +89,11 @@ require (
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/k3s-io/helm-controller v0.10.8 github.com/k3s-io/helm-controller v0.10.8
github.com/k3s-io/kine v0.6.2 github.com/k3s-io/kine v0.6.5
github.com/klauspost/compress v1.12.2 github.com/klauspost/compress v1.13.6
github.com/kubernetes-sigs/cri-tools v0.0.0-00010101000000-000000000000 github.com/kubernetes-sigs/cri-tools v0.0.0-00010101000000-000000000000
github.com/lib/pq v1.8.0 github.com/lib/pq v1.10.2
github.com/mattn/go-sqlite3 v1.14.4 github.com/mattn/go-sqlite3 v1.14.8
github.com/minio/minio-go/v7 v7.0.7 github.com/minio/minio-go/v7 v7.0.7
github.com/moby/sys/symlink v0.1.0 // indirect github.com/moby/sys/symlink v0.1.0 // indirect
github.com/natefinch/lumberjack v2.0.0+incompatible github.com/natefinch/lumberjack v2.0.0+incompatible
...@@ -105,7 +106,7 @@ require ( ...@@ -105,7 +106,7 @@ require (
github.com/rancher/dynamiclistener v0.2.3-k3s1 github.com/rancher/dynamiclistener v0.2.3-k3s1
github.com/rancher/remotedialer v0.2.0 github.com/rancher/remotedialer v0.2.0
github.com/rancher/wharfie v0.3.4 github.com/rancher/wharfie v0.3.4
github.com/rancher/wrangler v0.6.2 github.com/rancher/wrangler v0.8.3
github.com/rancher/wrangler-api v0.6.0 github.com/rancher/wrangler-api v0.6.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/rootless-containers/rootlesskit v0.14.0 github.com/rootless-containers/rootlesskit v0.14.0
......
sudo: false
language: go
go:
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- master
before_install:
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
before_script:
- echo -e "[server]\ninnodb_log_file_size=256MB\ninnodb_buffer_pool_size=512MB\nmax_allowed_packet=16MB" | sudo tee -a /etc/mysql/my.cnf
- sudo service mysql restart
- .travis/wait_mysql.sh
- mysql -e 'create database gotest;'
matrix:
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
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: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
mysql:5.7 --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=MARIA55
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 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
mariadb:5.5 --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=MARIA10_1
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 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
mariadb:10.1 --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
script:
- go test -v -covermode=count -coverprofile=coverage.out
- go vet ./...
- .travis/gofmt.sh
after_script:
- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci
...@@ -13,11 +13,15 @@ ...@@ -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>
Alex Snast <alexsn at fb.com>
Alexey Palazhchenko <alexey.palazhchenko at gmail.com> Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
Andrew Reid <andrew.reid at tixtrack.com> Andrew Reid <andrew.reid at tixtrack.com>
Animesh Ray <mail.rayanimesh at gmail.com>
Arne Hormann <arnehormann at gmail.com> Arne Hormann <arnehormann at gmail.com>
Ariel Mashraki <ariel at mashraki.co.il>
Asta Xie <xiemengjun at gmail.com> Asta Xie <xiemengjun at gmail.com>
Bulat Gaifullin <gaifullinbf at gmail.com> Bulat Gaifullin <gaifullinbf at gmail.com>
Caine Jette <jette at alum.mit.edu>
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> Craig Wilson <craiggwilson at gmail.com>
...@@ -27,6 +31,7 @@ Daniël van Eeden <git at myname.nl> ...@@ -27,6 +31,7 @@ Daniël van Eeden <git at myname.nl>
Dave Protasowski <dprotaso at gmail.com> Dave Protasowski <dprotaso at gmail.com>
DisposaBoy <disposaboy at dby.me> DisposaBoy <disposaboy at dby.me>
Egor Smolyakov <egorsmkv at gmail.com> Egor Smolyakov <egorsmkv at gmail.com>
Erwan Martin <hello at erwan.io>
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>
...@@ -34,12 +39,16 @@ Hajime Nakagami <nakagami at gmail.com> ...@@ -34,12 +39,16 @@ 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>
Huyiguang <hyg at webterren.com>
ICHINOSE Shogo <shogo82148 at gmail.com> ICHINOSE Shogo <shogo82148 at gmail.com>
Ilia Cimpoes <ichimpoesh at gmail.com>
INADA Naoki <songofacandy at gmail.com> 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> Jeffrey Charles <jeffreycharles at gmail.com>
Jerome Meyer <jxmeyer at gmail.com>
Jiajia Zhong <zhong2plus 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>
...@@ -47,6 +56,7 @@ Julien Schmidt <go-sql-driver at julienschmidt.com> ...@@ -47,6 +56,7 @@ Julien Schmidt <go-sql-driver at julienschmidt.com>
Justin Li <jli at j-li.net> 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>
Kei Kamikawa <x00.x7f.x86 at gmail.com>
Kevin Malachowski <kevin at chowski.com> Kevin Malachowski <kevin at chowski.com>
Kieron Woodhouse <kieron.woodhouse at infosum.com> Kieron Woodhouse <kieron.woodhouse at infosum.com>
Lennart Rudolph <lrudolph at hmc.edu> Lennart Rudolph <lrudolph at hmc.edu>
...@@ -58,6 +68,7 @@ Lucas Liu <extrafliu at gmail.com> ...@@ -58,6 +68,7 @@ Lucas Liu <extrafliu at gmail.com>
Luke Scott <luke at webconnex.com> Luke Scott <luke at webconnex.com>
Maciej Zimnoch <maciej.zimnoch at codilime.com> Maciej Zimnoch <maciej.zimnoch at codilime.com>
Michael Woolnough <michael.woolnough at gmail.com> Michael Woolnough <michael.woolnough at gmail.com>
Nathanial Murphy <nathanial.murphy at gmail.com>
Nicola Peduzzi <thenikso at gmail.com> Nicola Peduzzi <thenikso at gmail.com>
Olivier Mengué <dolmen at cpan.org> Olivier Mengué <dolmen at cpan.org>
oscarzhao <oscarzhaosl at gmail.com> oscarzhao <oscarzhaosl at gmail.com>
...@@ -68,22 +79,39 @@ Reed Allman <rdallman10 at gmail.com> ...@@ -68,22 +79,39 @@ Reed Allman <rdallman10 at gmail.com>
Richard Wilkes <wilkes at me.com> Richard Wilkes <wilkes at me.com>
Robert Russell <robert at rrbrussell.com> Robert Russell <robert at rrbrussell.com>
Runrioter Wung <runrioter at gmail.com> Runrioter Wung <runrioter at gmail.com>
Sho Iizuka <sho.i518 at gmail.com>
Sho Ikeda <suicaicoca at gmail.com>
Shuode Li <elemount at qq.com> Shuode Li <elemount at qq.com>
Simon J Mudd <sjmudd at pobox.com>
Soroush Pour <me at soroushjp.com> Soroush Pour <me at soroushjp.com>
Stan Putrya <root.vagner at gmail.com> Stan Putrya <root.vagner at gmail.com>
Stanley Gunawan <gunawan.stanley at gmail.com> Stanley Gunawan <gunawan.stanley at gmail.com>
Steven Hartland <steven.hartland at multiplay.co.uk>
Tan Jinhua <312841925 at qq.com>
Thomas Wodarek <wodarekwebpage at gmail.com>
Tim Ruffles <timruffles at gmail.com>
Tom Jenkinson <tom at tjenkinson.me>
Vladimir Kovpak <cn007b at gmail.com>
Vladyslav Zhelezniak <zhvladi at gmail.com>
Xiangyu Hu <xiangyu.hu at outlook.com> Xiangyu Hu <xiangyu.hu at outlook.com>
Xiaobing Jiang <s7v7nislands at gmail.com> Xiaobing Jiang <s7v7nislands at gmail.com>
Xiuming Chen <cc at cxm.cc> Xiuming Chen <cc at cxm.cc>
Xuehong Chan <chanxuehong at gmail.com>
Zhenye Xie <xiezhenye at gmail.com> Zhenye Xie <xiezhenye at gmail.com>
Zhixin Wen <john.wenzhixin at gmail.com>
# Organizations # Organizations
Barracuda Networks, Inc. Barracuda Networks, Inc.
Counting Ltd. Counting Ltd.
DigitalOcean Inc.
Facebook Inc.
GitHub Inc.
Google Inc. Google Inc.
InfoSum Ltd. InfoSum Ltd.
Keybase Inc. Keybase Inc.
Multiplay Ltd.
Percona LLC Percona LLC
Pivotal Inc. Pivotal Inc.
Stripe Inc. Stripe Inc.
Zendesk Inc.
## Version 1.6 (2021-04-01)
Changes:
- Migrate the CI service from travis-ci to GitHub Actions (#1176, #1183, #1190)
- `NullTime` is deprecated (#960, #1144)
- Reduce allocations when building SET command (#1111)
- Performance improvement for time formatting (#1118)
- Performance improvement for time parsing (#1098, #1113)
New Features:
- Implement `driver.Validator` interface (#1106, #1174)
- Support returning `uint64` from `Valuer` in `ConvertValue` (#1143)
- Add `json.RawMessage` for converter and prepared statement (#1059)
- Interpolate `json.RawMessage` as `string` (#1058)
- Implements `CheckNamedValue` (#1090)
Bugfixes:
- Stop rounding times (#1121, #1172)
- Put zero filler into the SSL handshake packet (#1066)
- Fix checking cancelled connections back into the connection pool (#1095)
- Fix remove last 0 byte for mysql_old_password when password is empty (#1133)
## Version 1.5 (2020-01-07)
Changes:
- Dropped support Go 1.9 and lower (#823, #829, #886, #1016, #1017)
- Improve buffer handling (#890)
- Document potentially insecure TLS configs (#901)
- Use a double-buffering scheme to prevent data races (#943)
- Pass uint64 values without converting them to string (#838, #955)
- Update collations and make utf8mb4 default (#877, #1054)
- Make NullTime compatible with sql.NullTime in Go 1.13+ (#995)
- Removed CloudSQL support (#993, #1007)
- Add Go Module support (#1003)
New Features:
- Implement support of optional TLS (#900)
- Check connection liveness (#934, #964, #997, #1048, #1051, #1052)
- Implement Connector Interface (#941, #958, #1020, #1035)
Bugfixes:
- Mark connections as bad on error during ping (#875)
- Mark connections as bad on error during dial (#867)
- Fix connection leak caused by rapid context cancellation (#1024)
- Mark connections as bad on error during Conn.Prepare (#1030)
## Version 1.4.1 (2018-11-14) ## Version 1.4.1 (2018-11-14)
Bugfixes: Bugfixes:
......
# Contributing Guidelines
## Reporting Issues
Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed).
## Contributing Code
By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file.
Don't forget to add yourself to the AUTHORS file.
### Code Review
Everyone is invited to review and comment on pull requests.
If it looks fine to you, comment with "LGTM" (Looks good to me).
If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes.
Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM".
## Development Ideas
If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/go-sql-driver/mysql/wiki/Development-Ideas) Wiki page.
...@@ -15,6 +15,7 @@ import ( ...@@ -15,6 +15,7 @@ import (
"crypto/sha256" "crypto/sha256"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"fmt"
"sync" "sync"
) )
...@@ -136,10 +137,6 @@ func pwHash(password []byte) (result [2]uint32) { ...@@ -136,10 +137,6 @@ func pwHash(password []byte) (result [2]uint32) {
// Hash password using insecure pre 4.1 method // Hash password using insecure pre 4.1 method
func scrambleOldPassword(scramble []byte, password string) []byte { func scrambleOldPassword(scramble []byte, password string) []byte {
if len(password) == 0 {
return nil
}
scramble = scramble[:8] scramble = scramble[:8]
hashPw := pwHash([]byte(password)) hashPw := pwHash([]byte(password))
...@@ -247,6 +244,9 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) { ...@@ -247,6 +244,9 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
if !mc.cfg.AllowOldPasswords { if !mc.cfg.AllowOldPasswords {
return nil, ErrOldPassword return nil, ErrOldPassword
} }
if len(mc.cfg.Passwd) == 0 {
return nil, nil
}
// Note: there are edge cases where this should work but doesn't; // Note: there are edge cases where this should work but doesn't;
// this is currently "wontfix": // this is currently "wontfix":
// https://github.com/go-sql-driver/mysql/issues/184 // https://github.com/go-sql-driver/mysql/issues/184
...@@ -360,17 +360,22 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error { ...@@ -360,17 +360,22 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
pubKey := mc.cfg.pubKey pubKey := mc.cfg.pubKey
if pubKey == nil { if pubKey == nil {
// request public key from server // request public key from server
data := mc.buf.takeSmallBuffer(4 + 1) data, err := mc.buf.takeSmallBuffer(4 + 1)
if err != nil {
return err
}
data[4] = cachingSha2PasswordRequestPublicKey data[4] = cachingSha2PasswordRequestPublicKey
mc.writePacket(data) mc.writePacket(data)
// parse public key // parse public key
data, err := mc.readPacket() if data, err = mc.readPacket(); err != nil {
if err != nil {
return err return err
} }
block, _ := pem.Decode(data[1:]) block, rest := pem.Decode(data[1:])
if block == nil {
return fmt.Errorf("No Pem data found, data: %s", rest)
}
pkix, err := x509.ParsePKIXPublicKey(block.Bytes) pkix, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil { if err != nil {
return err return err
......
...@@ -15,47 +15,69 @@ import ( ...@@ -15,47 +15,69 @@ import (
) )
const defaultBufSize = 4096 const defaultBufSize = 4096
const maxCachedBufSize = 256 * 1024
// A buffer which is used for both reading and writing. // A buffer which is used for both reading and writing.
// This is possible since communication on each connection is synchronous. // This is possible since communication on each connection is synchronous.
// In other words, we can't write and read simultaneously on the same connection. // In other words, we can't write and read simultaneously on the same connection.
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish // The buffer is similar to bufio.Reader / Writer but zero-copy-ish
// Also highly optimized for this particular use case. // Also highly optimized for this particular use case.
// This buffer is backed by two byte slices in a double-buffering scheme
type buffer struct { type buffer struct {
buf []byte buf []byte // buf is a byte buffer who's length and capacity are equal.
nc net.Conn nc net.Conn
idx int idx int
length int length int
timeout time.Duration timeout time.Duration
dbuf [2][]byte // dbuf is an array with the two byte slices that back this buffer
flipcnt uint // flipccnt is the current buffer counter for double-buffering
} }
// newBuffer allocates and returns a new buffer.
func newBuffer(nc net.Conn) buffer { func newBuffer(nc net.Conn) buffer {
var b [defaultBufSize]byte fg := make([]byte, defaultBufSize)
return buffer{ return buffer{
buf: b[:], buf: fg,
nc: nc, nc: nc,
dbuf: [2][]byte{fg, nil},
} }
} }
// flip replaces the active buffer with the background buffer
// this is a delayed flip that simply increases the buffer counter;
// the actual flip will be performed the next time we call `buffer.fill`
func (b *buffer) flip() {
b.flipcnt += 1
}
// fill reads into the buffer until at least _need_ bytes are in it // fill reads into the buffer until at least _need_ bytes are in it
func (b *buffer) fill(need int) error { func (b *buffer) fill(need int) error {
n := b.length n := b.length
// fill data into its double-buffering target: if we've called
// flip on this buffer, we'll be copying to the background buffer,
// and then filling it with network data; otherwise we'll just move
// the contents of the current buffer to the front before filling it
dest := b.dbuf[b.flipcnt&1]
// grow buffer if necessary to fit the whole packet.
if need > len(dest) {
// Round up to the next multiple of the default size
dest = make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
// move existing data to the beginning // if the allocated buffer is not too large, move it to backing storage
if n > 0 && b.idx > 0 { // to prevent extra allocations on applications that perform large reads
copy(b.buf[0:n], b.buf[b.idx:]) if len(dest) <= maxCachedBufSize {
b.dbuf[b.flipcnt&1] = dest
}
} }
// grow buffer if necessary // if we're filling the fg buffer, move the existing data to the start of it.
// TODO: let the buffer shrink again at some point // if we're filling the bg buffer, copy over the data
// Maybe keep the org buf slice and swap back? if n > 0 {
if need > len(b.buf) { copy(dest[:n], b.buf[b.idx:])
// Round up to the next multiple of the default size
newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
copy(newBuf, b.buf)
b.buf = newBuf
} }
b.buf = dest
b.idx = 0 b.idx = 0
for { for {
...@@ -105,43 +127,56 @@ func (b *buffer) readNext(need int) ([]byte, error) { ...@@ -105,43 +127,56 @@ func (b *buffer) readNext(need int) ([]byte, error) {
return b.buf[offset:b.idx], nil return b.buf[offset:b.idx], nil
} }
// returns a buffer with the requested size. // takeBuffer returns a buffer with the requested size.
// If possible, a slice from the existing buffer is returned. // If possible, a slice from the existing buffer is returned.
// Otherwise a bigger buffer is made. // Otherwise a bigger buffer is made.
// Only one buffer (total) can be used at a time. // Only one buffer (total) can be used at a time.
func (b *buffer) takeBuffer(length int) []byte { func (b *buffer) takeBuffer(length int) ([]byte, error) {
if b.length > 0 { if b.length > 0 {
return nil return nil, ErrBusyBuffer
} }
// test (cheap) general case first // test (cheap) general case first
if length <= defaultBufSize || length <= cap(b.buf) { if length <= cap(b.buf) {
return b.buf[:length] return b.buf[:length], nil
} }
if length < maxPacketSize { if length < maxPacketSize {
b.buf = make([]byte, length) b.buf = make([]byte, length)
return b.buf return b.buf, nil
} }
return make([]byte, length)
// buffer is larger than we want to store.
return make([]byte, length), nil
} }
// shortcut which can be used if the requested buffer is guaranteed to be // takeSmallBuffer is shortcut which can be used if length is
// smaller than defaultBufSize // known to be 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, error) {
if b.length > 0 { if b.length > 0 {
return nil return nil, ErrBusyBuffer
} }
return b.buf[:length] return b.buf[:length], nil
} }
// 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.
// cap and len of the returned buffer will be equal.
// 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, error) {
if b.length > 0 {
return nil, ErrBusyBuffer
}
return b.buf, nil
}
// store stores buf, an updated buffer, if its suitable to do so.
func (b *buffer) store(buf []byte) error {
if b.length > 0 { if b.length > 0 {
return nil return ErrBusyBuffer
} else if cap(buf) <= maxPacketSize && cap(buf) > cap(b.buf) {
b.buf = buf[:cap(buf)]
} }
return b.buf return nil
} }
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2019 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 linux darwin dragonfly freebsd netbsd openbsd solaris illumos
package mysql
import (
"errors"
"io"
"net"
"syscall"
)
var errUnexpectedRead = errors.New("unexpected read from socket")
func connCheck(conn net.Conn) error {
var sysErr error
sysConn, ok := conn.(syscall.Conn)
if !ok {
return nil
}
rawConn, err := sysConn.SyscallConn()
if err != nil {
return err
}
err = rawConn.Read(func(fd uintptr) bool {
var buf [1]byte
n, err := syscall.Read(int(fd), buf[:])
switch {
case n == 0 && err == nil:
sysErr = io.EOF
case n > 0:
sysErr = errUnexpectedRead
case err == syscall.EAGAIN || err == syscall.EWOULDBLOCK:
sysErr = nil
default:
sysErr = err
}
return true
})
if err != nil {
return err
}
return sysErr
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package // Go MySQL Driver - A MySQL-Driver for Go's database/sql package
// //
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. // Copyright 2019 The Go-MySQL-Driver Authors. All rights reserved.
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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, // License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
// +build appengine // +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!illumos
package mysql package mysql
import ( import "net"
"google.golang.org/appengine/cloudsql"
)
func init() { func connCheck(conn net.Conn) error {
RegisterDial("cloudsql", cloudsql.Dial) return nil
} }
...@@ -9,7 +9,10 @@ ...@@ -9,7 +9,10 @@
package mysql package mysql
import ( import (
"context"
"database/sql"
"database/sql/driver" "database/sql/driver"
"encoding/json"
"io" "io"
"net" "net"
"strconv" "strconv"
...@@ -17,19 +20,10 @@ import ( ...@@ -17,19 +20,10 @@ import (
"time" "time"
) )
// a copy of context.Context for Go 1.7 and earlier
type mysqlContext interface {
Done() <-chan struct{}
Err() error
// defined in context.Context, but not used in this driver:
// Deadline() (deadline time.Time, ok bool)
// Value(key interface{}) interface{}
}
type mysqlConn struct { type mysqlConn struct {
buf buffer buf buffer
netConn net.Conn netConn net.Conn
rawConn net.Conn // underlying connection when netConn is TLS connection.
affectedRows uint64 affectedRows uint64
insertId uint64 insertId uint64
cfg *Config cfg *Config
...@@ -40,10 +34,11 @@ type mysqlConn struct { ...@@ -40,10 +34,11 @@ type mysqlConn struct {
status statusFlag status statusFlag
sequence uint8 sequence uint8
parseTime bool parseTime bool
reset bool // set when the Go SQL package calls ResetSession
// for context support (Go 1.8+) // for context support (Go 1.8+)
watching bool watching bool
watcher chan<- mysqlContext watcher chan<- context.Context
closech chan struct{} closech chan struct{}
finished chan<- struct{} finished chan<- struct{}
canceled atomicError // set non-nil if conn is canceled canceled atomicError // set non-nil if conn is canceled
...@@ -52,9 +47,10 @@ type mysqlConn struct { ...@@ -52,9 +47,10 @@ type mysqlConn struct {
// Handles parameters set in DSN after the connection is established // Handles parameters set in DSN after the connection is established
func (mc *mysqlConn) handleParams() (err error) { func (mc *mysqlConn) handleParams() (err error) {
var cmdSet strings.Builder
for param, val := range mc.cfg.Params { for param, val := range mc.cfg.Params {
switch param { switch param {
// Charset // Charset: character_set_connection, character_set_client, character_set_results
case "charset": case "charset":
charsets := strings.Split(val, ",") charsets := strings.Split(val, ",")
for i := range charsets { for i := range charsets {
...@@ -68,12 +64,25 @@ func (mc *mysqlConn) handleParams() (err error) { ...@@ -68,12 +64,25 @@ func (mc *mysqlConn) handleParams() (err error) {
return return
} }
// System Vars // Other system vars accumulated in a single SET command
default: default:
err = mc.exec("SET " + param + "=" + val + "") if cmdSet.Len() == 0 {
if err != nil { // Heuristic: 29 chars for each other key=value to reduce reallocations
return cmdSet.Grow(4 + len(param) + 1 + len(val) + 30*(len(mc.cfg.Params)-1))
cmdSet.WriteString("SET ")
} else {
cmdSet.WriteByte(',')
} }
cmdSet.WriteString(param)
cmdSet.WriteByte('=')
cmdSet.WriteString(val)
}
}
if cmdSet.Len() > 0 {
err = mc.exec(cmdSet.String())
if err != nil {
return
} }
} }
...@@ -160,7 +169,9 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) { ...@@ -160,7 +169,9 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
// Send command // Send command
err := mc.writeCommandPacketStr(comStmtPrepare, query) err := mc.writeCommandPacketStr(comStmtPrepare, query)
if err != nil { if err != nil {
return nil, mc.markBadConn(err) // STMT_PREPARE is safe to retry. So we can return ErrBadConn here.
errLog.Print(err)
return nil, driver.ErrBadConn
} }
stmt := &mysqlStmt{ stmt := &mysqlStmt{
...@@ -190,10 +201,10 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin ...@@ -190,10 +201,10 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
return "", driver.ErrSkip return "", driver.ErrSkip
} }
buf := mc.buf.takeCompleteBuffer() buf, err := mc.buf.takeCompleteBuffer()
if buf == nil { if err != nil {
// can not take the buffer. Something must be wrong with the connection // can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(err)
return "", ErrInvalidConn return "", ErrInvalidConn
} }
buf = buf[:0] buf = buf[:0]
...@@ -219,6 +230,9 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin ...@@ -219,6 +230,9 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
switch v := arg.(type) { switch v := arg.(type) {
case int64: case int64:
buf = strconv.AppendInt(buf, v, 10) buf = strconv.AppendInt(buf, v, 10)
case uint64:
// Handle uint64 explicitly because our custom ConvertValue emits unsigned values
buf = strconv.AppendUint(buf, v, 10)
case float64: case float64:
buf = strconv.AppendFloat(buf, v, 'g', -1, 64) buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
case bool: case bool:
...@@ -231,47 +245,21 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin ...@@ -231,47 +245,21 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
if v.IsZero() { if v.IsZero() {
buf = append(buf, "'0000-00-00'"...) buf = append(buf, "'0000-00-00'"...)
} else { } else {
v := v.In(mc.cfg.Loc) buf = append(buf, '\'')
v = v.Add(time.Nanosecond * 500) // To round under microsecond buf, err = appendDateTime(buf, v.In(mc.cfg.Loc))
year := v.Year() if err != nil {
year100 := year / 100 return "", err
year1 := year % 100
month := v.Month()
day := v.Day()
hour := v.Hour()
minute := v.Minute()
second := v.Second()
micro := v.Nanosecond() / 1000
buf = append(buf, []byte{
'\'',
digits10[year100], digits01[year100],
digits10[year1], digits01[year1],
'-',
digits10[month], digits01[month],
'-',
digits10[day], digits01[day],
' ',
digits10[hour], digits01[hour],
':',
digits10[minute], digits01[minute],
':',
digits10[second], digits01[second],
}...)
if micro != 0 {
micro10000 := micro / 10000
micro100 := micro / 100 % 100
micro1 := micro % 100
buf = append(buf, []byte{
'.',
digits10[micro10000], digits01[micro10000],
digits10[micro100], digits01[micro100],
digits10[micro1], digits01[micro1],
}...)
} }
buf = append(buf, '\'') buf = append(buf, '\'')
} }
case json.RawMessage:
buf = append(buf, '\'')
if mc.status&statusNoBackslashEscapes == 0 {
buf = escapeBytesBackslash(buf, v)
} else {
buf = escapeBytesQuotes(buf, v)
}
buf = append(buf, '\'')
case []byte: case []byte:
if v == nil { if v == nil {
buf = append(buf, "NULL"...) buf = append(buf, "NULL"...)
...@@ -459,3 +447,204 @@ func (mc *mysqlConn) finish() { ...@@ -459,3 +447,204 @@ func (mc *mysqlConn) finish() {
case <-mc.closech: case <-mc.closech:
} }
} }
// Ping implements driver.Pinger interface
func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return driver.ErrBadConn
}
if err = mc.watchCancel(ctx); err != nil {
return
}
defer mc.finish()
if err = mc.writeCommandPacket(comPing); err != nil {
return mc.markBadConn(err)
}
return mc.readResultOK()
}
// BeginTx implements driver.ConnBeginTx interface
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
if mc.closed.IsSet() {
return nil, driver.ErrBadConn
}
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
defer mc.finish()
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
level, err := mapIsolationLevel(opts.Isolation)
if err != nil {
return nil, err
}
err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
if err != nil {
return nil, err
}
}
return mc.begin(opts.ReadOnly)
}
func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
rows, err := mc.query(query, dargs)
if err != nil {
mc.finish()
return nil, err
}
rows.finish = mc.finish
return rows, err
}
func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
defer mc.finish()
return mc.Exec(query, dargs)
}
func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
stmt, err := mc.Prepare(query)
mc.finish()
if err != nil {
return nil, err
}
select {
default:
case <-ctx.Done():
stmt.Close()
return nil, ctx.Err()
}
return stmt, nil
}
func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := stmt.mc.watchCancel(ctx); err != nil {
return nil, err
}
rows, err := stmt.query(dargs)
if err != nil {
stmt.mc.finish()
return nil, err
}
rows.finish = stmt.mc.finish
return rows, err
}
func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := stmt.mc.watchCancel(ctx); err != nil {
return nil, err
}
defer stmt.mc.finish()
return stmt.Exec(dargs)
}
func (mc *mysqlConn) watchCancel(ctx context.Context) error {
if mc.watching {
// Reach here if canceled,
// so the connection is already invalid
mc.cleanup()
return nil
}
// 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 {
return nil
}
// When watcher is not alive, can't watch it.
if mc.watcher == nil {
return nil
}
mc.watching = true
mc.watcher <- ctx
return nil
}
func (mc *mysqlConn) startWatcher() {
watcher := make(chan context.Context, 1)
mc.watcher = watcher
finished := make(chan struct{})
mc.finished = finished
go func() {
for {
var ctx context.Context
select {
case ctx = <-watcher:
case <-mc.closech:
return
}
select {
case <-ctx.Done():
mc.cancel(ctx.Err())
case <-finished:
case <-mc.closech:
return
}
}
}()
}
func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
nv.Value, err = converter{}.ConvertValue(nv.Value)
return
}
// ResetSession implements driver.SessionResetter.
// (From Go 1.10)
func (mc *mysqlConn) ResetSession(ctx context.Context) error {
if mc.closed.IsSet() {
return driver.ErrBadConn
}
mc.reset = true
return nil
}
// IsValid implements driver.Validator interface
// (From Go 1.15)
func (mc *mysqlConn) IsValid() bool {
return !mc.closed.IsSet()
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build go1.8
package mysql
import (
"context"
"database/sql"
"database/sql/driver"
)
// Ping implements driver.Pinger interface
func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return driver.ErrBadConn
}
if err = mc.watchCancel(ctx); err != nil {
return
}
defer mc.finish()
if err = mc.writeCommandPacket(comPing); err != nil {
return
}
return mc.readResultOK()
}
// BeginTx implements driver.ConnBeginTx interface
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
defer mc.finish()
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
level, err := mapIsolationLevel(opts.Isolation)
if err != nil {
return nil, err
}
err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
if err != nil {
return nil, err
}
}
return mc.begin(opts.ReadOnly)
}
func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
rows, err := mc.query(query, dargs)
if err != nil {
mc.finish()
return nil, err
}
rows.finish = mc.finish
return rows, err
}
func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
defer mc.finish()
return mc.Exec(query, dargs)
}
func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
stmt, err := mc.Prepare(query)
mc.finish()
if err != nil {
return nil, err
}
select {
default:
case <-ctx.Done():
stmt.Close()
return nil, ctx.Err()
}
return stmt, nil
}
func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := stmt.mc.watchCancel(ctx); err != nil {
return nil, err
}
rows, err := stmt.query(dargs)
if err != nil {
stmt.mc.finish()
return nil, err
}
rows.finish = stmt.mc.finish
return rows, err
}
func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := stmt.mc.watchCancel(ctx); err != nil {
return nil, err
}
defer stmt.mc.finish()
return stmt.Exec(dargs)
}
func (mc *mysqlConn) watchCancel(ctx context.Context) error {
if mc.watching {
// Reach here if canceled,
// so the connection is already invalid
mc.cleanup()
return nil
}
// 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 {
return nil
}
// When watcher is not alive, can't watch it.
if mc.watcher == nil {
return nil
}
mc.watching = true
mc.watcher <- ctx
return nil
}
func (mc *mysqlConn) startWatcher() {
watcher := make(chan mysqlContext, 1)
mc.watcher = watcher
finished := make(chan struct{})
mc.finished = finished
go func() {
for {
var ctx mysqlContext
select {
case ctx = <-watcher:
case <-mc.closech:
return
}
select {
case <-ctx.Done():
mc.cancel(ctx.Err())
case <-finished:
case <-mc.closech:
return
}
}
}()
}
func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
nv.Value, err = converter{}.ConvertValue(nv.Value)
return
}
// 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
}
// 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 (
"context"
"database/sql/driver"
"net"
)
type connector struct {
cfg *Config // immutable private copy.
}
// Connect implements driver.Connector interface.
// Connect returns a connection to the database.
func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
var err error
// New mysqlConn
mc := &mysqlConn{
maxAllowedPacket: maxPacketSize,
maxWriteSize: maxPacketSize - 1,
closech: make(chan struct{}),
cfg: c.cfg,
}
mc.parseTime = mc.cfg.ParseTime
// Connect to Server
dialsLock.RLock()
dial, ok := dials[mc.cfg.Net]
dialsLock.RUnlock()
if ok {
dctx := ctx
if mc.cfg.Timeout > 0 {
var cancel context.CancelFunc
dctx, cancel = context.WithTimeout(ctx, c.cfg.Timeout)
defer cancel()
}
mc.netConn, err = dial(dctx, mc.cfg.Addr)
} else {
nd := net.Dialer{Timeout: mc.cfg.Timeout}
mc.netConn, err = nd.DialContext(ctx, mc.cfg.Net, mc.cfg.Addr)
}
if err != nil {
return nil, err
}
// Enable TCP Keepalives on TCP connections
if tc, ok := mc.netConn.(*net.TCPConn); ok {
if err := tc.SetKeepAlive(true); err != nil {
// Don't send COM_QUIT before handshake.
mc.netConn.Close()
mc.netConn = nil
return nil, err
}
}
// Call startWatcher for context support (From Go 1.8)
mc.startWatcher()
if err := mc.watchCancel(ctx); err != nil {
mc.cleanup()
return nil, err
}
defer mc.finish()
mc.buf = newBuffer(mc.netConn)
// Set I/O timeouts
mc.buf.timeout = mc.cfg.ReadTimeout
mc.writeTimeout = mc.cfg.WriteTimeout
// Reading Handshake Initialization Packet
authData, plugin, err := mc.readHandshakePacket()
if err != nil {
mc.cleanup()
return nil, err
}
if plugin == "" {
plugin = defaultAuthPlugin
}
// Send Client Authentication Packet
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()
return nil, err
}
// Handle response to auth packet, switch methods if possible
if err = mc.handleAuthResult(authData, plugin); err != nil {
// Authentication failed and MySQL has already closed the connection
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
// Do not send COM_QUIT, just cleanup and return the error.
mc.cleanup()
return nil, err
}
if mc.cfg.MaxAllowedPacket > 0 {
mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket
} else {
// Get max allowed packet size
maxap, err := mc.getSystemVar("max_allowed_packet")
if err != nil {
mc.Close()
return nil, err
}
mc.maxAllowedPacket = stringToInt(maxap) - 1
}
if mc.maxAllowedPacket < maxPacketSize {
mc.maxWriteSize = mc.maxAllowedPacket
}
// Handle DSN Params
err = mc.handleParams()
if err != nil {
mc.Close()
return nil, err
}
return mc, nil
}
// Driver implements driver.Connector interface.
// Driver returns &MySQLDriver{}.
func (c *connector) Driver() driver.Driver {
return &MySQLDriver{}
}
...@@ -17,156 +17,91 @@ ...@@ -17,156 +17,91 @@
package mysql package mysql
import ( import (
"context"
"database/sql" "database/sql"
"database/sql/driver" "database/sql/driver"
"net" "net"
"sync" "sync"
) )
// watcher interface is used for context support (From Go 1.8)
type watcher interface {
startWatcher()
}
// MySQLDriver is exported to make the driver directly accessible. // MySQLDriver is exported to make the driver directly accessible.
// In general the driver is used via the database/sql package. // In general the driver is used via the database/sql package.
type MySQLDriver struct{} type MySQLDriver struct{}
// DialFunc is a function which can be used to establish the network connection. // DialFunc is a function which can be used to establish the network connection.
// Custom dial functions must be registered with RegisterDial // Custom dial functions must be registered with RegisterDial
//
// Deprecated: users should register a DialContextFunc instead
type DialFunc func(addr string) (net.Conn, error) type DialFunc func(addr string) (net.Conn, error)
// DialContextFunc is a function which can be used to establish the network connection.
// Custom dial functions must be registered with RegisterDialContext
type DialContextFunc func(ctx context.Context, addr string) (net.Conn, error)
var ( var (
dialsLock sync.RWMutex dialsLock sync.RWMutex
dials map[string]DialFunc dials map[string]DialContextFunc
) )
// RegisterDial registers a custom dial function. It can then be used by the // RegisterDialContext 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. // The current context for the connection and its address is passed to the dial function.
func RegisterDial(net string, dial DialFunc) { func RegisterDialContext(net string, dial DialContextFunc) {
dialsLock.Lock() dialsLock.Lock()
defer dialsLock.Unlock() defer dialsLock.Unlock()
if dials == nil { if dials == nil {
dials = make(map[string]DialFunc) dials = make(map[string]DialContextFunc)
} }
dials[net] = dial dials[net] = dial
} }
// RegisterDial registers a custom dial function. It can then be used by the
// network address mynet(addr), where mynet is the registered new network.
// addr is passed as a parameter to the dial function.
//
// Deprecated: users should call RegisterDialContext instead
func RegisterDial(network string, dial DialFunc) {
RegisterDialContext(network, func(_ context.Context, addr string) (net.Conn, error) {
return dial(addr)
})
}
// Open new Connection. // Open new Connection.
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how // See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
// the DSN string is formated // the DSN string is formatted
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
var err error cfg, err := ParseDSN(dsn)
// New mysqlConn
mc := &mysqlConn{
maxAllowedPacket: maxPacketSize,
maxWriteSize: maxPacketSize - 1,
closech: make(chan struct{}),
}
mc.cfg, err = ParseDSN(dsn)
if err != nil {
return nil, err
}
mc.parseTime = mc.cfg.ParseTime
// Connect to Server
dialsLock.RLock()
dial, ok := dials[mc.cfg.Net]
dialsLock.RUnlock()
if ok {
mc.netConn, err = dial(mc.cfg.Addr)
} else {
nd := net.Dialer{Timeout: mc.cfg.Timeout}
mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr)
}
if err != nil {
return nil, err
}
// Enable TCP Keepalives on TCP connections
if tc, ok := mc.netConn.(*net.TCPConn); ok {
if err := tc.SetKeepAlive(true); err != nil {
// Don't send COM_QUIT before handshake.
mc.netConn.Close()
mc.netConn = nil
return nil, err
}
}
// Call startWatcher for context support (From Go 1.8)
if s, ok := interface{}(mc).(watcher); ok {
s.startWatcher()
}
mc.buf = newBuffer(mc.netConn)
// Set I/O timeouts
mc.buf.timeout = mc.cfg.ReadTimeout
mc.writeTimeout = mc.cfg.WriteTimeout
// Reading Handshake Initialization Packet
authData, plugin, err := mc.readHandshakePacket()
if err != nil { if err != nil {
mc.cleanup()
return nil, err return nil, err
} }
if plugin == "" { c := &connector{
plugin = defaultAuthPlugin cfg: cfg,
} }
return c.Connect(context.Background())
}
// Send Client Authentication Packet func init() {
authResp, err := mc.auth(authData, plugin) sql.Register("mysql", &MySQLDriver{})
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()
return nil, err
}
// Handle response to auth packet, switch methods if possible // NewConnector returns new driver.Connector.
if err = mc.handleAuthResult(authData, plugin); err != nil { func NewConnector(cfg *Config) (driver.Connector, error) {
// Authentication failed and MySQL has already closed the connection cfg = cfg.Clone()
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html). // normalize the contents of cfg so calls to NewConnector have the same
// Do not send COM_QUIT, just cleanup and return the error. // behavior as MySQLDriver.OpenConnector
mc.cleanup() if err := cfg.normalize(); err != nil {
return nil, err return nil, err
} }
return &connector{cfg: cfg}, nil
}
if mc.cfg.MaxAllowedPacket > 0 { // OpenConnector implements driver.DriverContext.
mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket func (d MySQLDriver) OpenConnector(dsn string) (driver.Connector, error) {
} else { cfg, err := ParseDSN(dsn)
// Get max allowed packet size
maxap, err := mc.getSystemVar("max_allowed_packet")
if err != nil {
mc.Close()
return nil, err
}
mc.maxAllowedPacket = stringToInt(maxap) - 1
}
if mc.maxAllowedPacket < maxPacketSize {
mc.maxWriteSize = mc.maxAllowedPacket
}
// Handle DSN Params
err = mc.handleParams()
if err != nil { if err != nil {
mc.Close()
return nil, err return nil, err
} }
return &connector{
return mc, nil cfg: cfg,
} }, nil
func init() {
sql.Register("mysql", &MySQLDriver{})
} }
...@@ -106,7 +106,7 @@ var ( ...@@ -106,7 +106,7 @@ var (
scanTypeInt64 = reflect.TypeOf(int64(0)) scanTypeInt64 = reflect.TypeOf(int64(0))
scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{}) scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
scanTypeNullInt = reflect.TypeOf(sql.NullInt64{}) scanTypeNullInt = reflect.TypeOf(sql.NullInt64{})
scanTypeNullTime = reflect.TypeOf(NullTime{}) scanTypeNullTime = reflect.TypeOf(nullTime{})
scanTypeUint8 = reflect.TypeOf(uint8(0)) scanTypeUint8 = reflect.TypeOf(uint8(0))
scanTypeUint16 = reflect.TypeOf(uint16(0)) scanTypeUint16 = reflect.TypeOf(uint16(0))
scanTypeUint32 = reflect.TypeOf(uint32(0)) scanTypeUint32 = reflect.TypeOf(uint32(0))
......
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package.
//
// Copyright 2020 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 gofuzz
package mysql
import (
"database/sql"
)
func Fuzz(data []byte) int {
db, err := sql.Open("mysql", string(data))
if err != nil {
return 0
}
db.Close()
return 1
}
module github.com/go-sql-driver/mysql
go 1.10
...@@ -23,7 +23,7 @@ var ( ...@@ -23,7 +23,7 @@ var (
readerRegisterLock sync.RWMutex readerRegisterLock sync.RWMutex
) )
// RegisterLocalFile adds the given file to the file whitelist, // RegisterLocalFile adds the given file to the file allowlist,
// so that it can be used by "LOAD DATA LOCAL INFILE <filepath>". // so that it can be used by "LOAD DATA LOCAL INFILE <filepath>".
// Alternatively you can allow the use of all local files with // Alternatively you can allow the use of all local files with
// the DSN parameter 'allowAllFiles=true' // the DSN parameter 'allowAllFiles=true'
...@@ -45,7 +45,7 @@ func RegisterLocalFile(filePath string) { ...@@ -45,7 +45,7 @@ func RegisterLocalFile(filePath string) {
fileRegisterLock.Unlock() fileRegisterLock.Unlock()
} }
// DeregisterLocalFile removes the given filepath from the whitelist. // DeregisterLocalFile removes the given filepath from the allowlist.
func DeregisterLocalFile(filePath string) { func DeregisterLocalFile(filePath string) {
fileRegisterLock.Lock() fileRegisterLock.Lock()
delete(fileRegister, strings.Trim(filePath, `"`)) delete(fileRegister, strings.Trim(filePath, `"`))
......
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"database/sql/driver"
"fmt"
"time"
)
// Scan implements the Scanner interface.
// The value type must be time.Time or string / []byte (formatted time-string),
// otherwise Scan fails.
func (nt *NullTime) Scan(value interface{}) (err error) {
if value == nil {
nt.Time, nt.Valid = time.Time{}, false
return
}
switch v := value.(type) {
case time.Time:
nt.Time, nt.Valid = v, true
return
case []byte:
nt.Time, err = parseDateTime(v, time.UTC)
nt.Valid = (err == nil)
return
case string:
nt.Time, err = parseDateTime([]byte(v), time.UTC)
nt.Valid = (err == nil)
return
}
nt.Valid = false
return fmt.Errorf("Can't convert %T to time.Time", value)
}
// Value implements the driver Valuer interface.
func (nt NullTime) Value() (driver.Value, error) {
if !nt.Valid {
return nil, nil
}
return nt.Time, nil
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build go1.13
package mysql
import (
"database/sql"
)
// NullTime represents a time.Time that may be NULL.
// NullTime implements the Scanner interface so
// it can be used as a scan destination:
//
// var nt NullTime
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
// ...
// if nt.Valid {
// // use nt.Time
// } else {
// // NULL value
// }
//
// This NullTime implementation is not driver-specific
//
// Deprecated: NullTime doesn't honor the loc DSN parameter.
// NullTime.Scan interprets a time as UTC, not the loc DSN parameter.
// Use sql.NullTime instead.
type NullTime sql.NullTime
// for internal use.
// the mysql package uses sql.NullTime if it is available.
// if not, the package uses mysql.NullTime.
type nullTime = sql.NullTime // sql.NullTime is available
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build !go1.13
package mysql
import (
"time"
)
// NullTime represents a time.Time that may be NULL.
// NullTime implements the Scanner interface so
// it can be used as a scan destination:
//
// var nt NullTime
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
// ...
// if nt.Valid {
// // use nt.Time
// } else {
// // NULL value
// }
//
// This NullTime implementation is not driver-specific
type NullTime struct {
Time time.Time
Valid bool // Valid is true if Time is not NULL
}
// for internal use.
// the mysql package uses sql.NullTime if it is available.
// if not, the package uses mysql.NullTime.
type nullTime = NullTime // sql.NullTime is not available
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"crypto/tls" "crypto/tls"
"database/sql/driver" "database/sql/driver"
"encoding/binary" "encoding/binary"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
...@@ -51,7 +52,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) { ...@@ -51,7 +52,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
mc.sequence++ mc.sequence++
// packets with length 0 terminate a previous packet which is a // packets with length 0 terminate a previous packet which is a
// multiple of (2^24)1 bytes long // multiple of (2^24)-1 bytes long
if pktLen == 0 { if pktLen == 0 {
// there was no previous packet // there was no previous packet
if prevData == nil { if prevData == nil {
...@@ -96,6 +97,35 @@ func (mc *mysqlConn) writePacket(data []byte) error { ...@@ -96,6 +97,35 @@ func (mc *mysqlConn) writePacket(data []byte) error {
return ErrPktTooLarge return ErrPktTooLarge
} }
// Perform a stale connection check. We only perform this check for
// the first query on a connection that has been checked out of the
// connection pool: a fresh connection from the pool is more likely
// to be stale, and it has not performed any previous writes that
// could cause data corruption, so it's safe to return ErrBadConn
// if the check fails.
if mc.reset {
mc.reset = false
conn := mc.netConn
if mc.rawConn != nil {
conn = mc.rawConn
}
var err error
// If this connection has a ReadTimeout which we've been setting on
// reads, reset it to its default value before we attempt a non-blocking
// read, otherwise the scheduler will just time us out before we can read
if mc.cfg.ReadTimeout != 0 {
err = conn.SetReadDeadline(time.Time{})
}
if err == nil && mc.cfg.CheckConnLiveness {
err = connCheck(conn)
}
if err != nil {
errLog.Print("closing bad idle connection: ", err)
mc.Close()
return driver.ErrBadConn
}
}
for { for {
var size int var size int
if pktLen >= maxPacketSize { if pktLen >= maxPacketSize {
...@@ -194,7 +224,11 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro ...@@ -194,7 +224,11 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro
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 if mc.cfg.TLSConfig == "preferred" {
mc.cfg.tls = nil
} else {
return nil, "", ErrNoTLS
}
} }
pos += 2 pos += 2
...@@ -286,10 +320,10 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string ...@@ -286,10 +320,10 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
} }
// 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, err := mc.buf.takeSmallBuffer(pktLen + 4)
if data == nil { if err != nil {
// cannot 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(err)
return errBadConnNoWrite return errBadConnNoWrite
} }
...@@ -315,6 +349,12 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string ...@@ -315,6 +349,12 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
return errors.New("unknown collation") return errors.New("unknown collation")
} }
// Filler [23 bytes] (all 0x00)
pos := 13
for ; pos < 13+23; pos++ {
data[pos] = 0
}
// SSL Connection Request Packet // SSL Connection Request Packet
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest
if mc.cfg.tls != nil { if mc.cfg.tls != nil {
...@@ -328,16 +368,11 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string ...@@ -328,16 +368,11 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
if err := tlsConn.Handshake(); err != nil { if err := tlsConn.Handshake(); err != nil {
return err return err
} }
mc.rawConn = mc.netConn
mc.netConn = tlsConn mc.netConn = tlsConn
mc.buf.nc = tlsConn mc.buf.nc = tlsConn
} }
// Filler [23 bytes] (all 0x00)
pos := 13
for ; pos < 13+23; pos++ {
data[pos] = 0
}
// User [null terminated string] // User [null terminated string]
if len(mc.cfg.User) > 0 { if len(mc.cfg.User) > 0 {
pos += copy(data[pos:], mc.cfg.User) pos += copy(data[pos:], mc.cfg.User)
...@@ -367,10 +402,10 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string ...@@ -367,10 +402,10 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
// 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) writeAuthSwitchPacket(authData []byte) error { func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error {
pktLen := 4 + len(authData) pktLen := 4 + len(authData)
data := mc.buf.takeSmallBuffer(pktLen) data, err := mc.buf.takeSmallBuffer(pktLen)
if data == nil { if err != nil {
// cannot 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(err)
return errBadConnNoWrite return errBadConnNoWrite
} }
...@@ -387,10 +422,10 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error { ...@@ -387,10 +422,10 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error {
// Reset Packet Sequence // Reset Packet Sequence
mc.sequence = 0 mc.sequence = 0
data := mc.buf.takeSmallBuffer(4 + 1) data, err := mc.buf.takeSmallBuffer(4 + 1)
if data == nil { if err != nil {
// cannot 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(err)
return errBadConnNoWrite return errBadConnNoWrite
} }
...@@ -406,10 +441,10 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error { ...@@ -406,10 +441,10 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
mc.sequence = 0 mc.sequence = 0
pktLen := 1 + len(arg) pktLen := 1 + len(arg)
data := mc.buf.takeBuffer(pktLen + 4) data, err := mc.buf.takeBuffer(pktLen + 4)
if data == nil { if err != nil {
// cannot 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(err)
return errBadConnNoWrite return errBadConnNoWrite
} }
...@@ -427,10 +462,10 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error { ...@@ -427,10 +462,10 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
// Reset Packet Sequence // Reset Packet Sequence
mc.sequence = 0 mc.sequence = 0
data := mc.buf.takeSmallBuffer(4 + 1 + 4) data, err := mc.buf.takeSmallBuffer(4 + 1 + 4)
if data == nil { if err != nil {
// cannot 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(err)
return errBadConnNoWrite return errBadConnNoWrite
} }
...@@ -743,7 +778,7 @@ func (rows *textRows) readRow(dest []driver.Value) error { ...@@ -743,7 +778,7 @@ func (rows *textRows) readRow(dest []driver.Value) error {
case fieldTypeTimestamp, fieldTypeDateTime, case fieldTypeTimestamp, fieldTypeDateTime,
fieldTypeDate, fieldTypeNewDate: fieldTypeDate, fieldTypeNewDate:
dest[i], err = parseDateTime( dest[i], err = parseDateTime(
string(dest[i].([]byte)), dest[i].([]byte),
mc.cfg.Loc, mc.cfg.Loc,
) )
if err == nil { if err == nil {
...@@ -883,7 +918,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { ...@@ -883,7 +918,7 @@ 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. // Determine threshold dynamically to avoid packet size shortage.
longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1) longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1)
if longDataSize < 64 { if longDataSize < 64 {
longDataSize = 64 longDataSize = 64
...@@ -893,15 +928,17 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { ...@@ -893,15 +928,17 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
mc.sequence = 0 mc.sequence = 0
var data []byte var data []byte
var err error
if len(args) == 0 { if len(args) == 0 {
data = mc.buf.takeBuffer(minPktLen) data, err = mc.buf.takeBuffer(minPktLen)
} else { } else {
data = mc.buf.takeCompleteBuffer() data, err = mc.buf.takeCompleteBuffer()
// In this case the len(data) == cap(data) which is used to optimise the flow below.
} }
if data == nil { if err != nil {
// cannot 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(err)
return errBadConnNoWrite return errBadConnNoWrite
} }
...@@ -927,7 +964,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { ...@@ -927,7 +964,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
pos := minPktLen pos := minPktLen
var nullMask []byte var nullMask []byte
if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= len(data) { if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= cap(data) {
// buffer has to be extended but we don't know by how much so // buffer has to be extended but we don't know by how much so
// we depend on append after all data with known sizes fit. // we depend on append after all data with known sizes fit.
// We stop at that because we deal with a lot of columns here // We stop at that because we deal with a lot of columns here
...@@ -936,10 +973,11 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { ...@@ -936,10 +973,11 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
copy(tmp[:pos], data[:pos]) copy(tmp[:pos], data[:pos])
data = tmp data = tmp
nullMask = data[pos : pos+maskLen] nullMask = data[pos : pos+maskLen]
// No need to clean nullMask as make ensures that.
pos += maskLen pos += maskLen
} else { } else {
nullMask = data[pos : pos+maskLen] nullMask = data[pos : pos+maskLen]
for i := 0; i < maskLen; i++ { for i := range nullMask {
nullMask[i] = 0 nullMask[i] = 0
} }
pos += maskLen pos += maskLen
...@@ -966,6 +1004,9 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { ...@@ -966,6 +1004,9 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
continue continue
} }
if v, ok := arg.(json.RawMessage); ok {
arg = []byte(v)
}
// cache types and values // cache types and values
switch v := arg.(type) { switch v := arg.(type) {
case int64: case int64:
...@@ -984,6 +1025,22 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { ...@@ -984,6 +1025,22 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
) )
} }
case uint64:
paramTypes[i+i] = byte(fieldTypeLongLong)
paramTypes[i+i+1] = 0x80 // type is unsigned
if cap(paramValues)-len(paramValues)-8 >= 0 {
paramValues = paramValues[:len(paramValues)+8]
binary.LittleEndian.PutUint64(
paramValues[len(paramValues)-8:],
uint64(v),
)
} else {
paramValues = append(paramValues,
uint64ToBytes(uint64(v))...,
)
}
case float64: case float64:
paramTypes[i+i] = byte(fieldTypeDouble) paramTypes[i+i] = byte(fieldTypeDouble)
paramTypes[i+i+1] = 0x00 paramTypes[i+i+1] = 0x00
...@@ -1059,7 +1116,10 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { ...@@ -1059,7 +1116,10 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
if v.IsZero() { if v.IsZero() {
b = append(b, "0000-00-00"...) b = append(b, "0000-00-00"...)
} else { } else {
b = v.In(mc.cfg.Loc).AppendFormat(b, timeFormat) b, err = appendDateTime(b, v.In(mc.cfg.Loc))
if err != nil {
return err
}
} }
paramValues = appendLengthEncodedInteger(paramValues, paramValues = appendLengthEncodedInteger(paramValues,
...@@ -1076,7 +1136,10 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { ...@@ -1076,7 +1136,10 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
// In that case we must build the data packet with the new values buffer // In that case we must build the data packet with the new values buffer
if valuesCap != cap(paramValues) { if valuesCap != cap(paramValues) {
data = append(data[:pos], paramValues...) data = append(data[:pos], paramValues...)
mc.buf.buf = data if err = mc.buf.store(data); err != nil {
errLog.Print(err)
return errBadConnNoWrite
}
} }
pos += len(paramValues) pos += len(paramValues)
......
...@@ -111,6 +111,13 @@ func (rows *mysqlRows) Close() (err error) { ...@@ -111,6 +111,13 @@ func (rows *mysqlRows) Close() (err error) {
return err return err
} }
// flip the buffer for this connection if we need to drain it.
// note that for a successful query (i.e. one where rows.next()
// has been called until it returns false), `rows.mc` will be nil
// by the time the user calls `(*Rows).Close`, so we won't reach this
// see: https://github.com/golang/go/commit/651ddbdb5056ded455f47f9c494c67b389622a47
mc.buf.flip()
// Remove unread packets from stream // Remove unread packets from stream
if !rows.rs.done { if !rows.rs.done {
err = mc.readUntilEOF() err = mc.readUntilEOF()
......
...@@ -10,10 +10,10 @@ package mysql ...@@ -10,10 +10,10 @@ package mysql
import ( import (
"database/sql/driver" "database/sql/driver"
"encoding/json"
"fmt" "fmt"
"io" "io"
"reflect" "reflect"
"strconv"
) )
type mysqlStmt struct { type mysqlStmt struct {
...@@ -44,6 +44,11 @@ func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter { ...@@ -44,6 +44,11 @@ func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
return converter{} return converter{}
} }
func (stmt *mysqlStmt) CheckNamedValue(nv *driver.NamedValue) (err error) {
nv.Value, err = converter{}.ConvertValue(nv.Value)
return
}
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) { func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
if stmt.mc.closed.IsSet() { if stmt.mc.closed.IsSet() {
errLog.Print(ErrInvalidConn) errLog.Print(ErrInvalidConn)
...@@ -130,6 +135,8 @@ func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) { ...@@ -130,6 +135,8 @@ func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
return rows, err return rows, err
} }
var jsonType = reflect.TypeOf(json.RawMessage{})
type converter struct{} type converter struct{}
// ConvertValue mirrors the reference/default converter in database/sql/driver // ConvertValue mirrors the reference/default converter in database/sql/driver
...@@ -147,12 +154,17 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) { ...@@ -147,12 +154,17 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !driver.IsValue(sv) { if driver.IsValue(sv) {
return nil, fmt.Errorf("non-Value type %T returned from Value", sv) return sv, nil
}
// A value returend from the Valuer interface can be "a type handled by
// a database driver's NamedValueChecker interface" so we should accept
// uint64 here as well.
if u, ok := sv.(uint64); ok {
return u, nil
} }
return sv, nil return nil, fmt.Errorf("non-Value type %T returned from Value", sv)
} }
rv := reflect.ValueOf(v) rv := reflect.ValueOf(v)
switch rv.Kind() { switch rv.Kind() {
case reflect.Ptr: case reflect.Ptr:
...@@ -164,24 +176,21 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) { ...@@ -164,24 +176,21 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
} }
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, reflect.Uint64:
return int64(rv.Uint()), nil return rv.Uint(), nil
case reflect.Uint64:
u64 := rv.Uint()
if u64 >= 1<<63 {
return strconv.FormatUint(u64, 10), nil
}
return int64(u64), nil
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
return rv.Float(), nil return rv.Float(), nil
case reflect.Bool: case reflect.Bool:
return rv.Bool(), nil return rv.Bool(), nil
case reflect.Slice: case reflect.Slice:
ek := rv.Type().Elem().Kind() switch t := rv.Type(); {
if ek == reflect.Uint8 { case t == jsonType:
return v, nil
case t.Elem().Kind() == reflect.Uint8:
return rv.Bytes(), nil return rv.Bytes(), nil
default:
return nil, fmt.Errorf("unsupported type %T, a slice of %s", v, t.Elem().Kind())
} }
return nil, fmt.Errorf("unsupported type %T, a slice of %s", v, ek)
case reflect.String: case reflect.String:
return rv.String(), nil return rv.String(), nil
} }
......
...@@ -10,8 +10,10 @@ package mysql ...@@ -10,8 +10,10 @@ package mysql
import ( import (
"crypto/tls" "crypto/tls"
"database/sql"
"database/sql/driver" "database/sql/driver"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"io" "io"
"strconv" "strconv"
...@@ -54,7 +56,7 @@ var ( ...@@ -54,7 +56,7 @@ var (
// db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom") // db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
// //
func RegisterTLSConfig(key string, config *tls.Config) error { func RegisterTLSConfig(key string, config *tls.Config) error {
if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" { if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" || strings.ToLower(key) == "preferred" {
return fmt.Errorf("key '%s' is reserved", key) return fmt.Errorf("key '%s' is reserved", key)
} }
...@@ -80,7 +82,7 @@ func DeregisterTLSConfig(key string) { ...@@ -80,7 +82,7 @@ func DeregisterTLSConfig(key string) {
func getTLSConfigClone(key string) (config *tls.Config) { func getTLSConfigClone(key string) (config *tls.Config) {
tlsConfigLock.RLock() tlsConfigLock.RLock()
if v, ok := tlsConfigRegistry[key]; ok { if v, ok := tlsConfigRegistry[key]; ok {
config = cloneTLSConfig(v) config = v.Clone()
} }
tlsConfigLock.RUnlock() tlsConfigLock.RUnlock()
return return
...@@ -104,81 +106,136 @@ func readBool(input string) (value bool, valid bool) { ...@@ -104,81 +106,136 @@ func readBool(input string) (value bool, valid bool) {
* Time related utils * * Time related utils *
******************************************************************************/ ******************************************************************************/
// NullTime represents a time.Time that may be NULL. func parseDateTime(b []byte, loc *time.Location) (time.Time, error) {
// NullTime implements the Scanner interface so const base = "0000-00-00 00:00:00.000000"
// it can be used as a scan destination: switch len(b) {
// case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
// var nt NullTime if string(b) == base[:len(b)] {
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt) return time.Time{}, nil
// ... }
// if nt.Valid {
// // use nt.Time
// } else {
// // NULL value
// }
//
// This NullTime implementation is not driver-specific
type NullTime struct {
Time time.Time
Valid bool // Valid is true if Time is not NULL
}
// Scan implements the Scanner interface. year, err := parseByteYear(b)
// The value type must be time.Time or string / []byte (formatted time-string), if err != nil {
// otherwise Scan fails. return time.Time{}, err
func (nt *NullTime) Scan(value interface{}) (err error) { }
if value == nil { if year <= 0 {
nt.Time, nt.Valid = time.Time{}, false year = 1
return }
}
if b[4] != '-' {
return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[4])
}
m, err := parseByte2Digits(b[5], b[6])
if err != nil {
return time.Time{}, err
}
if m <= 0 {
m = 1
}
month := time.Month(m)
switch v := value.(type) { if b[7] != '-' {
case time.Time: return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[7])
nt.Time, nt.Valid = v, true }
return
case []byte: day, err := parseByte2Digits(b[8], b[9])
nt.Time, err = parseDateTime(string(v), time.UTC) if err != nil {
nt.Valid = (err == nil) return time.Time{}, err
return }
case string: if day <= 0 {
nt.Time, err = parseDateTime(v, time.UTC) day = 1
nt.Valid = (err == nil) }
return if len(b) == 10 {
return time.Date(year, month, day, 0, 0, 0, 0, loc), nil
}
if b[10] != ' ' {
return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[10])
}
hour, err := parseByte2Digits(b[11], b[12])
if err != nil {
return time.Time{}, err
}
if b[13] != ':' {
return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[13])
}
min, err := parseByte2Digits(b[14], b[15])
if err != nil {
return time.Time{}, err
}
if b[16] != ':' {
return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[16])
}
sec, err := parseByte2Digits(b[17], b[18])
if err != nil {
return time.Time{}, err
}
if len(b) == 19 {
return time.Date(year, month, day, hour, min, sec, 0, loc), nil
}
if b[19] != '.' {
return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[19])
}
nsec, err := parseByteNanoSec(b[20:])
if err != nil {
return time.Time{}, err
}
return time.Date(year, month, day, hour, min, sec, nsec, loc), nil
default:
return time.Time{}, fmt.Errorf("invalid time bytes: %s", b)
} }
}
nt.Valid = false func parseByteYear(b []byte) (int, error) {
return fmt.Errorf("Can't convert %T to time.Time", value) year, n := 0, 1000
for i := 0; i < 4; i++ {
v, err := bToi(b[i])
if err != nil {
return 0, err
}
year += v * n
n = n / 10
}
return year, nil
} }
// Value implements the driver Valuer interface. func parseByte2Digits(b1, b2 byte) (int, error) {
func (nt NullTime) Value() (driver.Value, error) { d1, err := bToi(b1)
if !nt.Valid { if err != nil {
return nil, nil return 0, err
} }
return nt.Time, nil d2, err := bToi(b2)
if err != nil {
return 0, err
}
return d1*10 + d2, nil
} }
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) { func parseByteNanoSec(b []byte) (int, error) {
base := "0000-00-00 00:00:00.0000000" ns, digit := 0, 100000 // max is 6-digits
switch len(str) { for i := 0; i < len(b); i++ {
case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM" v, err := bToi(b[i])
if str == base[:len(str)] { if err != nil {
return return 0, err
} }
t, err = time.Parse(timeFormat[:len(str)], str) ns += v * digit
default: digit /= 10
err = fmt.Errorf("invalid time string: %s", str)
return
} }
// nanoseconds has 10-digits. (needs to scale digits)
// 10 - 6 = 4, so we have to multiple 1000.
return ns * 1000, nil
}
// Adjust location func bToi(b byte) (int, error) {
if err == nil && loc != time.UTC { if b < '0' || b > '9' {
y, mo, d := t.Date() return 0, errors.New("not [0-9]")
h, mi, s := t.Clock()
t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil
} }
return int(b - '0'), nil
return
} }
func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) { func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) {
...@@ -219,6 +276,64 @@ func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Va ...@@ -219,6 +276,64 @@ func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Va
return nil, fmt.Errorf("invalid DATETIME packet length %d", num) return nil, fmt.Errorf("invalid DATETIME packet length %d", num)
} }
func appendDateTime(buf []byte, t time.Time) ([]byte, error) {
year, month, day := t.Date()
hour, min, sec := t.Clock()
nsec := t.Nanosecond()
if year < 1 || year > 9999 {
return buf, errors.New("year is not in the range [1, 9999]: " + strconv.Itoa(year)) // use errors.New instead of fmt.Errorf to avoid year escape to heap
}
year100 := year / 100
year1 := year % 100
var localBuf [len("2006-01-02T15:04:05.999999999")]byte // does not escape
localBuf[0], localBuf[1], localBuf[2], localBuf[3] = digits10[year100], digits01[year100], digits10[year1], digits01[year1]
localBuf[4] = '-'
localBuf[5], localBuf[6] = digits10[month], digits01[month]
localBuf[7] = '-'
localBuf[8], localBuf[9] = digits10[day], digits01[day]
if hour == 0 && min == 0 && sec == 0 && nsec == 0 {
return append(buf, localBuf[:10]...), nil
}
localBuf[10] = ' '
localBuf[11], localBuf[12] = digits10[hour], digits01[hour]
localBuf[13] = ':'
localBuf[14], localBuf[15] = digits10[min], digits01[min]
localBuf[16] = ':'
localBuf[17], localBuf[18] = digits10[sec], digits01[sec]
if nsec == 0 {
return append(buf, localBuf[:19]...), nil
}
nsec100000000 := nsec / 100000000
nsec1000000 := (nsec / 1000000) % 100
nsec10000 := (nsec / 10000) % 100
nsec100 := (nsec / 100) % 100
nsec1 := nsec % 100
localBuf[19] = '.'
// milli second
localBuf[20], localBuf[21], localBuf[22] =
digits01[nsec100000000], digits10[nsec1000000], digits01[nsec1000000]
// micro second
localBuf[23], localBuf[24], localBuf[25] =
digits10[nsec10000], digits01[nsec10000], digits10[nsec100]
// nano second
localBuf[26], localBuf[27], localBuf[28] =
digits01[nsec100], digits10[nsec1], digits01[nsec1]
// trim trailing zeros
n := len(localBuf)
for n > 0 && localBuf[n-1] == '0' {
n--
}
return append(buf, localBuf[:n]...), nil
}
// zeroDateTime is used in formatBinaryDateTime to avoid an allocation // zeroDateTime is used in formatBinaryDateTime to avoid an allocation
// if the DATE or DATETIME has the zero value. // if the DATE or DATETIME has the zero value.
// It must never be changed. // It must never be changed.
...@@ -682,7 +797,7 @@ type atomicBool struct { ...@@ -682,7 +797,7 @@ type atomicBool struct {
value uint32 value uint32
} }
// IsSet returns wether the current boolean value is true // IsSet returns whether the current boolean value is true
func (ab *atomicBool) IsSet() bool { func (ab *atomicBool) IsSet() bool {
return atomic.LoadUint32(&ab.value) > 0 return atomic.LoadUint32(&ab.value) > 0
} }
...@@ -696,7 +811,7 @@ func (ab *atomicBool) Set(value bool) { ...@@ -696,7 +811,7 @@ func (ab *atomicBool) Set(value bool) {
} }
} }
// TrySet sets the value of the bool and returns wether the value changed // TrySet sets the value of the bool and returns whether the value changed
func (ab *atomicBool) TrySet(value bool) bool { func (ab *atomicBool) TrySet(value bool) bool {
if value { if value {
return atomic.SwapUint32(&ab.value, 1) == 0 return atomic.SwapUint32(&ab.value, 1) == 0
...@@ -724,3 +839,30 @@ func (ae *atomicError) Value() error { ...@@ -724,3 +839,30 @@ func (ae *atomicError) Value() error {
} }
return nil return nil
} }
func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
dargs := make([]driver.Value, len(named))
for n, param := range named {
if len(param.Name) > 0 {
// TODO: support the use of Named Parameters #561
return nil, errors.New("mysql: driver does not support the use of Named Parameters")
}
dargs[n] = param.Value
}
return dargs, nil
}
func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
switch sql.IsolationLevel(level) {
case sql.LevelRepeatableRead:
return "REPEATABLE READ", nil
case sql.LevelReadCommitted:
return "READ COMMITTED", nil
case sql.LevelReadUncommitted:
return "READ UNCOMMITTED", nil
case sql.LevelSerializable:
return "SERIALIZABLE", nil
default:
return "", fmt.Errorf("mysql: unsupported isolation level: %v", level)
}
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build go1.7
// +build !go1.8
package mysql
import "crypto/tls"
func cloneTLSConfig(c *tls.Config) *tls.Config {
return &tls.Config{
Rand: c.Rand,
Time: c.Time,
Certificates: c.Certificates,
NameToCertificate: c.NameToCertificate,
GetCertificate: c.GetCertificate,
RootCAs: c.RootCAs,
NextProtos: c.NextProtos,
ServerName: c.ServerName,
ClientAuth: c.ClientAuth,
ClientCAs: c.ClientCAs,
InsecureSkipVerify: c.InsecureSkipVerify,
CipherSuites: c.CipherSuites,
PreferServerCipherSuites: c.PreferServerCipherSuites,
SessionTicketsDisabled: c.SessionTicketsDisabled,
SessionTicketKey: c.SessionTicketKey,
ClientSessionCache: c.ClientSessionCache,
MinVersion: c.MinVersion,
MaxVersion: c.MaxVersion,
CurvePreferences: c.CurvePreferences,
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
Renegotiation: c.Renegotiation,
}
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build go1.8
package mysql
import (
"crypto/tls"
"database/sql"
"database/sql/driver"
"errors"
"fmt"
)
func cloneTLSConfig(c *tls.Config) *tls.Config {
return c.Clone()
}
func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
dargs := make([]driver.Value, len(named))
for n, param := range named {
if len(param.Name) > 0 {
// TODO: support the use of Named Parameters #561
return nil, errors.New("mysql: driver does not support the use of Named Parameters")
}
dargs[n] = param.Value
}
return dargs, nil
}
func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
switch sql.IsolationLevel(level) {
case sql.LevelRepeatableRead:
return "REPEATABLE READ", nil
case sql.LevelReadCommitted:
return "READ COMMITTED", nil
case sql.LevelReadUncommitted:
return "READ UNCOMMITTED", nil
case sql.LevelSerializable:
return "SERIALIZABLE", nil
default:
return "", fmt.Errorf("mysql: unsupported isolation level: %v", level)
}
}
cmd/snappytool/snappytool
testdata/bench
# These explicitly listed benchmark data files are for an obsolete version of
# snappy_test.go.
testdata/alice29.txt
testdata/asyoulik.txt
testdata/fireworks.jpeg
testdata/geo.protodata
testdata/html
testdata/html_x_4
testdata/kppkn.gtb
testdata/lcet10.txt
testdata/paper-100k.pdf
testdata/plrabn12.txt
testdata/urls.10K
# This is the official list of Snappy-Go authors for copyright purposes.
# This file is distinct from the CONTRIBUTORS files.
# See the latter for an explanation.
# Names should be added to this file as
# Name or Organization <email address>
# The email address is not required for organizations.
# Please keep the list sorted.
Amazon.com, Inc
Damian Gryski <dgryski@gmail.com>
Google Inc.
Jan Mercl <0xjnml@gmail.com>
Klaus Post <klauspost@gmail.com>
Rodolfo Carvalho <rhcarvalho@gmail.com>
Sebastien Binet <seb.binet@gmail.com>
# This is the official list of people who can contribute
# (and typically have contributed) code to the Snappy-Go repository.
# The AUTHORS file lists the copyright holders; this file
# lists people. For example, Google employees are listed here
# but not in AUTHORS, because Google holds the copyright.
#
# The submission process automatically checks to make sure
# that people submitting code are listed in this file (by email address).
#
# Names should be added to this file only after verifying that
# the individual or the individual's organization has agreed to
# the appropriate Contributor License Agreement, found here:
#
# http://code.google.com/legal/individual-cla-v1.0.html
# http://code.google.com/legal/corporate-cla-v1.0.html
#
# The agreement for individuals can be filled out on the web.
#
# When adding J Random Contributor's name to this file,
# either J's name or J's organization's name should be
# added to the AUTHORS file, depending on whether the
# individual or corporate CLA was used.
# Names should be added to this file like so:
# Name <email address>
# Please keep the list sorted.
Damian Gryski <dgryski@gmail.com>
Jan Mercl <0xjnml@gmail.com>
Jonathan Swinney <jswinney@amazon.com>
Kai Backman <kaib@golang.org>
Klaus Post <klauspost@gmail.com>
Marc-Antoine Ruel <maruel@chromium.org>
Nigel Tao <nigeltao@golang.org>
Rob Pike <r@golang.org>
Rodolfo Carvalho <rhcarvalho@gmail.com>
Russ Cox <rsc@golang.org>
Sebastien Binet <seb.binet@gmail.com>
The Snappy compression format in the Go programming language.
To download and install from source:
$ go get github.com/golang/snappy
Unless otherwise noted, the Snappy-Go source files are distributed
under the BSD-style license found in the LICENSE file.
Benchmarks.
The golang/snappy benchmarks include compressing (Z) and decompressing (U) ten
or so files, the same set used by the C++ Snappy code (github.com/google/snappy
and note the "google", not "golang"). On an "Intel(R) Core(TM) i7-3770 CPU @
3.40GHz", Go's GOARCH=amd64 numbers as of 2016-05-29:
"go test -test.bench=."
_UFlat0-8 2.19GB/s ± 0% html
_UFlat1-8 1.41GB/s ± 0% urls
_UFlat2-8 23.5GB/s ± 2% jpg
_UFlat3-8 1.91GB/s ± 0% jpg_200
_UFlat4-8 14.0GB/s ± 1% pdf
_UFlat5-8 1.97GB/s ± 0% html4
_UFlat6-8 814MB/s ± 0% txt1
_UFlat7-8 785MB/s ± 0% txt2
_UFlat8-8 857MB/s ± 0% txt3
_UFlat9-8 719MB/s ± 1% txt4
_UFlat10-8 2.84GB/s ± 0% pb
_UFlat11-8 1.05GB/s ± 0% gaviota
_ZFlat0-8 1.04GB/s ± 0% html
_ZFlat1-8 534MB/s ± 0% urls
_ZFlat2-8 15.7GB/s ± 1% jpg
_ZFlat3-8 740MB/s ± 3% jpg_200
_ZFlat4-8 9.20GB/s ± 1% pdf
_ZFlat5-8 991MB/s ± 0% html4
_ZFlat6-8 379MB/s ± 0% txt1
_ZFlat7-8 352MB/s ± 0% txt2
_ZFlat8-8 396MB/s ± 1% txt3
_ZFlat9-8 327MB/s ± 1% txt4
_ZFlat10-8 1.33GB/s ± 1% pb
_ZFlat11-8 605MB/s ± 1% gaviota
"go test -test.bench=. -tags=noasm"
_UFlat0-8 621MB/s ± 2% html
_UFlat1-8 494MB/s ± 1% urls
_UFlat2-8 23.2GB/s ± 1% jpg
_UFlat3-8 1.12GB/s ± 1% jpg_200
_UFlat4-8 4.35GB/s ± 1% pdf
_UFlat5-8 609MB/s ± 0% html4
_UFlat6-8 296MB/s ± 0% txt1
_UFlat7-8 288MB/s ± 0% txt2
_UFlat8-8 309MB/s ± 1% txt3
_UFlat9-8 280MB/s ± 1% txt4
_UFlat10-8 753MB/s ± 0% pb
_UFlat11-8 400MB/s ± 0% gaviota
_ZFlat0-8 409MB/s ± 1% html
_ZFlat1-8 250MB/s ± 1% urls
_ZFlat2-8 12.3GB/s ± 1% jpg
_ZFlat3-8 132MB/s ± 0% jpg_200
_ZFlat4-8 2.92GB/s ± 0% pdf
_ZFlat5-8 405MB/s ± 1% html4
_ZFlat6-8 179MB/s ± 1% txt1
_ZFlat7-8 170MB/s ± 1% txt2
_ZFlat8-8 189MB/s ± 1% txt3
_ZFlat9-8 164MB/s ± 1% txt4
_ZFlat10-8 479MB/s ± 1% pb
_ZFlat11-8 270MB/s ± 1% gaviota
For comparison (Go's encoded output is byte-for-byte identical to C++'s), here
are the numbers from C++ Snappy's
make CXXFLAGS="-O2 -DNDEBUG -g" clean snappy_unittest.log && cat snappy_unittest.log
BM_UFlat/0 2.4GB/s html
BM_UFlat/1 1.4GB/s urls
BM_UFlat/2 21.8GB/s jpg
BM_UFlat/3 1.5GB/s jpg_200
BM_UFlat/4 13.3GB/s pdf
BM_UFlat/5 2.1GB/s html4
BM_UFlat/6 1.0GB/s txt1
BM_UFlat/7 959.4MB/s txt2
BM_UFlat/8 1.0GB/s txt3
BM_UFlat/9 864.5MB/s txt4
BM_UFlat/10 2.9GB/s pb
BM_UFlat/11 1.2GB/s gaviota
BM_ZFlat/0 944.3MB/s html (22.31 %)
BM_ZFlat/1 501.6MB/s urls (47.78 %)
BM_ZFlat/2 14.3GB/s jpg (99.95 %)
BM_ZFlat/3 538.3MB/s jpg_200 (73.00 %)
BM_ZFlat/4 8.3GB/s pdf (83.30 %)
BM_ZFlat/5 903.5MB/s html4 (22.52 %)
BM_ZFlat/6 336.0MB/s txt1 (57.88 %)
BM_ZFlat/7 312.3MB/s txt2 (61.91 %)
BM_ZFlat/8 353.1MB/s txt3 (54.99 %)
BM_ZFlat/9 289.9MB/s txt4 (66.26 %)
BM_ZFlat/10 1.2GB/s pb (19.68 %)
BM_ZFlat/11 527.4MB/s gaviota (37.72 %)
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !appengine
// +build gc
// +build !noasm
// +build amd64 arm64
package snappy
// decode has the same semantics as in decode_other.go.
//
//go:noescape
func decode(dst, src []byte) int
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !appengine
// +build gc
// +build !noasm
// +build amd64 arm64
package snappy
// emitLiteral has the same semantics as in encode_other.go.
//
//go:noescape
func emitLiteral(dst, lit []byte) int
// emitCopy has the same semantics as in encode_other.go.
//
//go:noescape
func emitCopy(dst []byte, offset, length int) int
// extendMatch has the same semantics as in encode_other.go.
//
//go:noescape
func extendMatch(src []byte, i, j int) int
// encodeBlock has the same semantics as in encode_other.go.
//
//go:noescape
func encodeBlock(dst, src []byte) (d int)
...@@ -89,6 +89,7 @@ type Generic struct { ...@@ -89,6 +89,7 @@ type Generic struct {
DeleteSQL string DeleteSQL string
CompactSQL string CompactSQL string
UpdateCompactSQL string UpdateCompactSQL string
PostCompactSQL string
InsertSQL string InsertSQL string
FillSQL string FillSQL string
InsertLastInsertIDSQL string InsertLastInsertIDSQL string
...@@ -291,6 +292,15 @@ func (d *Generic) Compact(ctx context.Context, revision int64) (int64, error) { ...@@ -291,6 +292,15 @@ func (d *Generic) Compact(ctx context.Context, revision int64) (int64, error) {
return res.RowsAffected() return res.RowsAffected()
} }
func (d *Generic) PostCompact(ctx context.Context) error {
logrus.Trace("POSTCOMPACT")
if d.PostCompactSQL != "" {
_, err := d.execute(ctx, d.PostCompactSQL)
return err
}
return nil
}
func (d *Generic) GetRevision(ctx context.Context, revision int64) (*sql.Rows, error) { func (d *Generic) GetRevision(ctx context.Context, revision int64) (*sql.Rows, error) {
return d.query(ctx, d.GetRevisionSQL, revision) return d.query(ctx, d.GetRevisionSQL, revision)
} }
......
...@@ -39,6 +39,7 @@ var ( ...@@ -39,6 +39,7 @@ var (
`CREATE INDEX IF NOT EXISTS kine_id_deleted_index ON kine (id,deleted)`, `CREATE INDEX IF NOT EXISTS kine_id_deleted_index ON kine (id,deleted)`,
`CREATE INDEX IF NOT EXISTS kine_prev_revision_index ON kine (prev_revision)`, `CREATE INDEX IF NOT EXISTS kine_prev_revision_index ON kine (prev_revision)`,
`CREATE UNIQUE INDEX IF NOT EXISTS kine_name_prev_revision_uindex ON kine (name, prev_revision)`, `CREATE UNIQUE INDEX IF NOT EXISTS kine_name_prev_revision_uindex ON kine (name, prev_revision)`,
`PRAGMA wal_checkpoint(TRUNCATE)`,
} }
) )
...@@ -78,6 +79,7 @@ func NewVariant(ctx context.Context, driverName, dataSourceName string, connPool ...@@ -78,6 +79,7 @@ func NewVariant(ctx context.Context, driverName, dataSourceName string, connPool
kd.deleted != 0 AND kd.deleted != 0 AND
kd.id <= ? kd.id <= ?
)` )`
dialect.PostCompactSQL = `PRAGMA wal_checkpoint(FULL)`
dialect.TranslateErr = func(err error) error { dialect.TranslateErr = func(err error) error {
if err, ok := err.(sqlite3.Error); ok && err.ExtendedCode == sqlite3.ErrConstraintUnique { if err, ok := err.(sqlite3.Error); ok && err.ExtendedCode == sqlite3.ErrConstraintUnique {
return server.ErrKeyExists return server.ErrKeyExists
......
...@@ -48,6 +48,7 @@ type Dialect interface { ...@@ -48,6 +48,7 @@ type Dialect interface {
GetCompactRevision(ctx context.Context) (int64, error) GetCompactRevision(ctx context.Context) (int64, error)
SetCompactRevision(ctx context.Context, revision int64) error SetCompactRevision(ctx context.Context, revision int64) error
Compact(ctx context.Context, revision int64) (int64, error) Compact(ctx context.Context, revision int64) (int64, error)
PostCompact(ctx context.Context) error
Fill(ctx context.Context, revision int64) error Fill(ctx context.Context, revision int64) error
IsFill(key string) bool IsFill(key string) bool
BeginTx(ctx context.Context, opts *sql.TxOptions) (*generic.Tx, error) BeginTx(ctx context.Context, opts *sql.TxOptions) (*generic.Tx, error)
...@@ -173,6 +174,10 @@ outer: ...@@ -173,6 +174,10 @@ outer:
} }
} }
if err := s.postCompact(); err != nil {
logrus.Errorf("Post-compact operations failed: %v", err)
}
// Record the final results for the outer loop // Record the final results for the outer loop
compactRev = compactedRev compactRev = compactedRev
targetCompactRev = currentRev targetCompactRev = currentRev
...@@ -236,6 +241,11 @@ func (s *SQLLog) compact(compactRev int64, targetCompactRev int64) (int64, int64 ...@@ -236,6 +241,11 @@ func (s *SQLLog) compact(compactRev int64, targetCompactRev int64) (int64, int64
return targetCompactRev, currentRev, nil return targetCompactRev, currentRev, nil
} }
// postCompact executes any post-compact database cleanup - vacuuming, WAL truncate, etc.
func (s *SQLLog) postCompact() error {
return s.d.PostCompact(s.ctx)
}
func (s *SQLLog) CurrentRevision(ctx context.Context) (int64, error) { func (s *SQLLog) CurrentRevision(ctx context.Context) (int64, error) {
return s.d.CurrentRevision(ctx) return s.d.CurrentRevision(ctx)
} }
......
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
/s2/cmd/_s2sx/sfx-exe
# This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
before:
hooks:
- ./gen.sh
builds:
-
id: "s2c"
binary: s2c
main: ./s2/cmd/s2c/main.go
flags:
- -trimpath
env:
- CGO_ENABLED=0
goos:
- aix
- linux
- freebsd
- netbsd
- windows
- darwin
goarch:
- 386
- amd64
- arm
- arm64
- ppc64
- ppc64le
- mips64
- mips64le
goarm:
- 7
-
id: "s2d"
binary: s2d
main: ./s2/cmd/s2d/main.go
flags:
- -trimpath
env:
- CGO_ENABLED=0
goos:
- aix
- linux
- freebsd
- netbsd
- windows
- darwin
goarch:
- 386
- amd64
- arm
- arm64
- ppc64
- ppc64le
- mips64
- mips64le
goarm:
- 7
-
id: "s2sx"
binary: s2sx
main: ./s2/cmd/_s2sx/main.go
flags:
- -modfile=s2sx.mod
- -trimpath
env:
- CGO_ENABLED=0
goos:
- aix
- linux
- freebsd
- netbsd
- windows
- darwin
goarch:
- 386
- amd64
- arm
- arm64
- ppc64
- ppc64le
- mips64
- mips64le
goarm:
- 7
archives:
-
id: s2-binaries
name_template: "s2-{{ .Os }}_{{ .Arch }}_{{ .Version }}"
replacements:
aix: AIX
darwin: OSX
linux: Linux
windows: Windows
386: i386
amd64: x86_64
freebsd: FreeBSD
netbsd: NetBSD
format_overrides:
- goos: windows
format: zip
files:
- unpack/*
- s2/LICENSE
- s2/README.md
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^doc:'
- '^docs:'
- '^test:'
- '^tests:'
- '^Update\sREADME.md'
nfpms:
-
file_name_template: "s2_package_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
vendor: Klaus Post
homepage: https://github.com/klauspost/compress
maintainer: Klaus Post <klauspost@gmail.com>
description: S2 Compression Tool
license: BSD 3-Clause
formats:
- deb
- rpm
replacements:
darwin: Darwin
linux: Linux
freebsd: FreeBSD
amd64: x86_64
package compress
import "math"
// Estimate returns a normalized compressibility estimate of block b.
// Values close to zero are likely uncompressible.
// Values above 0.1 are likely to be compressible.
// Values above 0.5 are very compressible.
// Very small lengths will return 0.
func Estimate(b []byte) float64 {
if len(b) < 16 {
return 0
}
// Correctly predicted order 1
hits := 0
lastMatch := false
var o1 [256]byte
var hist [256]int
c1 := byte(0)
for _, c := range b {
if c == o1[c1] {
// We only count a hit if there was two correct predictions in a row.
if lastMatch {
hits++
}
lastMatch = true
} else {
lastMatch = false
}
o1[c1] = c
c1 = c
hist[c]++
}
// Use x^0.6 to give better spread
prediction := math.Pow(float64(hits)/float64(len(b)), 0.6)
// Calculate histogram distribution
variance := float64(0)
avg := float64(len(b)) / 256
for _, v := range hist {
Δ := float64(v) - avg
variance += Δ * Δ
}
stddev := math.Sqrt(float64(variance)) / float64(len(b))
exp := math.Sqrt(1 / float64(len(b)))
// Subtract expected stddev
stddev -= exp
if stddev < 0 {
stddev = 0
}
stddev *= 1 + exp
// Use x^0.4 to give better spread
entropy := math.Pow(stddev, 0.4)
// 50/50 weight between prediction and histogram distribution
return math.Pow((prediction+entropy)/2, 0.9)
}
// ShannonEntropyBits returns the number of bits minimum required to represent
// an entropy encoding of the input bytes.
// https://en.wiktionary.org/wiki/Shannon_entropy
func ShannonEntropyBits(b []byte) int {
if len(b) == 0 {
return 0
}
var hist [256]int
for _, c := range b {
hist[c]++
}
shannon := float64(0)
invTotal := 1.0 / float64(len(b))
for _, v := range hist[:] {
if v > 0 {
n := float64(v)
shannon += math.Ceil(-math.Log2(n*invTotal) * n)
}
}
return int(math.Ceil(shannon))
}
#!/bin/sh
cd s2/cmd/_s2sx/ || exit 1
go generate .
module github.com/klauspost/compress
go 1.15
...@@ -161,6 +161,70 @@ func compress(in []byte, s *Scratch, compressor func(src []byte) ([]byte, error) ...@@ -161,6 +161,70 @@ func compress(in []byte, s *Scratch, compressor func(src []byte) ([]byte, error)
return s.Out, false, nil return s.Out, false, nil
} }
// EstimateSizes will estimate the data sizes
func EstimateSizes(in []byte, s *Scratch) (tableSz, dataSz, reuseSz int, err error) {
s, err = s.prepare(in)
if err != nil {
return 0, 0, 0, err
}
// Create histogram, if none was provided.
tableSz, dataSz, reuseSz = -1, -1, -1
maxCount := s.maxCount
var canReuse = false
if maxCount == 0 {
maxCount, canReuse = s.countSimple(in)
} else {
canReuse = s.canUseTable(s.prevTable)
}
// We want the output size to be less than this:
wantSize := len(in)
if s.WantLogLess > 0 {
wantSize -= wantSize >> s.WantLogLess
}
// Reset for next run.
s.clearCount = true
s.maxCount = 0
if maxCount >= len(in) {
if maxCount > len(in) {
return 0, 0, 0, fmt.Errorf("maxCount (%d) > length (%d)", maxCount, len(in))
}
if len(in) == 1 {
return 0, 0, 0, ErrIncompressible
}
// One symbol, use RLE
return 0, 0, 0, ErrUseRLE
}
if maxCount == 1 || maxCount < (len(in)>>7) {
// Each symbol present maximum once or too well distributed.
return 0, 0, 0, ErrIncompressible
}
// Calculate new table.
err = s.buildCTable()
if err != nil {
return 0, 0, 0, err
}
if false && !s.canUseTable(s.cTable) {
panic("invalid table generated")
}
tableSz, err = s.cTable.estTableSize(s)
if err != nil {
return 0, 0, 0, err
}
if canReuse {
reuseSz = s.prevTable.estimateSize(s.count[:s.symbolLen])
}
dataSz = s.cTable.estimateSize(s.count[:s.symbolLen])
// Restore
return tableSz, dataSz, reuseSz, nil
}
func (s *Scratch) compress1X(src []byte) ([]byte, error) { func (s *Scratch) compress1X(src []byte) ([]byte, error) {
return s.compress1xDo(s.Out, src) return s.compress1xDo(s.Out, src)
} }
......
...@@ -344,35 +344,241 @@ func (d *Decoder) decompress1X8Bit(dst, src []byte) ([]byte, error) { ...@@ -344,35 +344,241 @@ func (d *Decoder) decompress1X8Bit(dst, src []byte) ([]byte, error) {
var buf [256]byte var buf [256]byte
var off uint8 var off uint8
shift := (8 - d.actualTableLog) & 7 switch d.actualTableLog {
case 8:
//fmt.Printf("mask: %b, tl:%d\n", mask, d.actualTableLog) const shift = 8 - 8
for br.off >= 4 { for br.off >= 4 {
br.fillFast() br.fillFast()
v := dt[br.peekByteFast()>>shift] v := dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry)) br.advance(uint8(v.entry))
buf[off+0] = uint8(v.entry >> 8) buf[off+0] = uint8(v.entry >> 8)
v = dt[br.peekByteFast()>>shift] v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry)) br.advance(uint8(v.entry))
buf[off+1] = uint8(v.entry >> 8) buf[off+1] = uint8(v.entry >> 8)
v = dt[br.peekByteFast()>>shift] v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry)) br.advance(uint8(v.entry))
buf[off+2] = uint8(v.entry >> 8) buf[off+2] = uint8(v.entry >> 8)
v = dt[br.peekByteFast()>>shift] v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry)) br.advance(uint8(v.entry))
buf[off+3] = uint8(v.entry >> 8) buf[off+3] = uint8(v.entry >> 8)
off += 4 off += 4
if off == 0 { if off == 0 {
if len(dst)+256 > maxDecodedSize { if len(dst)+256 > maxDecodedSize {
br.close() br.close()
return nil, ErrMaxDecodedSizeExceeded return nil, ErrMaxDecodedSizeExceeded
}
dst = append(dst, buf[:]...)
}
}
case 7:
const shift = 8 - 7
for br.off >= 4 {
br.fillFast()
v := dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+0] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+1] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+2] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+3] = uint8(v.entry >> 8)
off += 4
if off == 0 {
if len(dst)+256 > maxDecodedSize {
br.close()
return nil, ErrMaxDecodedSizeExceeded
}
dst = append(dst, buf[:]...)
}
}
case 6:
const shift = 8 - 6
for br.off >= 4 {
br.fillFast()
v := dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+0] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+1] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+2] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+3] = uint8(v.entry >> 8)
off += 4
if off == 0 {
if len(dst)+256 > maxDecodedSize {
br.close()
return nil, ErrMaxDecodedSizeExceeded
}
dst = append(dst, buf[:]...)
}
}
case 5:
const shift = 8 - 5
for br.off >= 4 {
br.fillFast()
v := dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+0] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+1] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+2] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+3] = uint8(v.entry >> 8)
off += 4
if off == 0 {
if len(dst)+256 > maxDecodedSize {
br.close()
return nil, ErrMaxDecodedSizeExceeded
}
dst = append(dst, buf[:]...)
}
}
case 4:
const shift = 8 - 4
for br.off >= 4 {
br.fillFast()
v := dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+0] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+1] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+2] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+3] = uint8(v.entry >> 8)
off += 4
if off == 0 {
if len(dst)+256 > maxDecodedSize {
br.close()
return nil, ErrMaxDecodedSizeExceeded
}
dst = append(dst, buf[:]...)
}
}
case 3:
const shift = 8 - 3
for br.off >= 4 {
br.fillFast()
v := dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+0] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+1] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+2] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+3] = uint8(v.entry >> 8)
off += 4
if off == 0 {
if len(dst)+256 > maxDecodedSize {
br.close()
return nil, ErrMaxDecodedSizeExceeded
}
dst = append(dst, buf[:]...)
}
}
case 2:
const shift = 8 - 2
for br.off >= 4 {
br.fillFast()
v := dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+0] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+1] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+2] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+3] = uint8(v.entry >> 8)
off += 4
if off == 0 {
if len(dst)+256 > maxDecodedSize {
br.close()
return nil, ErrMaxDecodedSizeExceeded
}
dst = append(dst, buf[:]...)
}
}
case 1:
const shift = 8 - 1
for br.off >= 4 {
br.fillFast()
v := dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+0] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+1] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+2] = uint8(v.entry >> 8)
v = dt[uint8(br.value>>(56+shift))]
br.advance(uint8(v.entry))
buf[off+3] = uint8(v.entry >> 8)
off += 4
if off == 0 {
if len(dst)+256 > maxDecodedSize {
br.close()
return nil, ErrMaxDecodedSizeExceeded
}
dst = append(dst, buf[:]...)
} }
dst = append(dst, buf[:]...)
} }
default:
return nil, fmt.Errorf("invalid tablelog: %d", d.actualTableLog)
} }
if len(dst)+int(off) > maxDecodedSize { if len(dst)+int(off) > maxDecodedSize {
...@@ -383,6 +589,8 @@ func (d *Decoder) decompress1X8Bit(dst, src []byte) ([]byte, error) { ...@@ -383,6 +589,8 @@ func (d *Decoder) decompress1X8Bit(dst, src []byte) ([]byte, error) {
// br < 4, so uint8 is fine // br < 4, so uint8 is fine
bitsLeft := int8(uint8(br.off)*8 + (64 - br.bitsRead)) bitsLeft := int8(uint8(br.off)*8 + (64 - br.bitsRead))
shift := (8 - d.actualTableLog) & 7
for bitsLeft > 0 { for bitsLeft > 0 {
if br.bitsRead >= 64-8 { if br.bitsRead >= 64-8 {
for br.off > 0 { for br.off > 0 {
...@@ -423,24 +631,24 @@ func (d *Decoder) decompress1X8BitExactly(dst, src []byte) ([]byte, error) { ...@@ -423,24 +631,24 @@ func (d *Decoder) decompress1X8BitExactly(dst, src []byte) ([]byte, error) {
var buf [256]byte var buf [256]byte
var off uint8 var off uint8
const shift = 0 const shift = 56
//fmt.Printf("mask: %b, tl:%d\n", mask, d.actualTableLog) //fmt.Printf("mask: %b, tl:%d\n", mask, d.actualTableLog)
for br.off >= 4 { for br.off >= 4 {
br.fillFast() br.fillFast()
v := dt[br.peekByteFast()>>shift] v := dt[uint8(br.value>>shift)]
br.advance(uint8(v.entry)) br.advance(uint8(v.entry))
buf[off+0] = uint8(v.entry >> 8) buf[off+0] = uint8(v.entry >> 8)
v = dt[br.peekByteFast()>>shift] v = dt[uint8(br.value>>shift)]
br.advance(uint8(v.entry)) br.advance(uint8(v.entry))
buf[off+1] = uint8(v.entry >> 8) buf[off+1] = uint8(v.entry >> 8)
v = dt[br.peekByteFast()>>shift] v = dt[uint8(br.value>>shift)]
br.advance(uint8(v.entry)) br.advance(uint8(v.entry))
buf[off+2] = uint8(v.entry >> 8) buf[off+2] = uint8(v.entry >> 8)
v = dt[br.peekByteFast()>>shift] v = dt[uint8(br.value>>shift)]
br.advance(uint8(v.entry)) br.advance(uint8(v.entry))
buf[off+3] = uint8(v.entry >> 8) buf[off+3] = uint8(v.entry >> 8)
...@@ -474,7 +682,7 @@ func (d *Decoder) decompress1X8BitExactly(dst, src []byte) ([]byte, error) { ...@@ -474,7 +682,7 @@ func (d *Decoder) decompress1X8BitExactly(dst, src []byte) ([]byte, error) {
br.close() br.close()
return nil, ErrMaxDecodedSizeExceeded return nil, ErrMaxDecodedSizeExceeded
} }
v := dt[br.peekByteFast()>>shift] v := dt[br.peekByteFast()]
nBits := uint8(v.entry) nBits := uint8(v.entry)
br.advance(nBits) br.advance(nBits)
bitsLeft -= int8(nBits) bitsLeft -= int8(nBits)
...@@ -709,7 +917,6 @@ func (d *Decoder) decompress4X8bit(dst, src []byte) ([]byte, error) { ...@@ -709,7 +917,6 @@ func (d *Decoder) decompress4X8bit(dst, src []byte) ([]byte, error) {
shift := (8 - d.actualTableLog) & 7 shift := (8 - d.actualTableLog) & 7
const tlSize = 1 << 8 const tlSize = 1 << 8
const tlMask = tlSize - 1
single := d.dt.single[:tlSize] single := d.dt.single[:tlSize]
// Use temp table to avoid bound checks/append penalty. // Use temp table to avoid bound checks/append penalty.
......
...@@ -245,6 +245,68 @@ func (c cTable) write(s *Scratch) error { ...@@ -245,6 +245,68 @@ func (c cTable) write(s *Scratch) error {
return nil return nil
} }
func (c cTable) estTableSize(s *Scratch) (sz int, err error) {
var (
// precomputed conversion table
bitsToWeight [tableLogMax + 1]byte
huffLog = s.actualTableLog
// last weight is not saved.
maxSymbolValue = uint8(s.symbolLen - 1)
huffWeight = s.huffWeight[:256]
)
const (
maxFSETableLog = 6
)
// convert to weight
bitsToWeight[0] = 0
for n := uint8(1); n < huffLog+1; n++ {
bitsToWeight[n] = huffLog + 1 - n
}
// Acquire histogram for FSE.
hist := s.fse.Histogram()
hist = hist[:256]
for i := range hist[:16] {
hist[i] = 0
}
for n := uint8(0); n < maxSymbolValue; n++ {
v := bitsToWeight[c[n].nBits] & 15
huffWeight[n] = v
hist[v]++
}
// FSE compress if feasible.
if maxSymbolValue >= 2 {
huffMaxCnt := uint32(0)
huffMax := uint8(0)
for i, v := range hist[:16] {
if v == 0 {
continue
}
huffMax = byte(i)
if v > huffMaxCnt {
huffMaxCnt = v
}
}
s.fse.HistogramFinished(huffMax, int(huffMaxCnt))
s.fse.TableLog = maxFSETableLog
b, err := fse.Compress(huffWeight[:maxSymbolValue], s.fse)
if err == nil && len(b) < int(s.symbolLen>>1) {
sz += 1 + len(b)
return sz, nil
}
// Unable to compress (RLE/uncompressible)
}
// write raw values as 4-bits (max : 15)
if maxSymbolValue > (256 - 128) {
// should not happen : likely means source cannot be compressed
return 0, ErrIncompressible
}
// special case, pack weights 4 bits/weight.
sz += 1 + int(maxSymbolValue/2)
return sz, nil
}
// estimateSize returns the estimated size in bytes of the input represented in the // estimateSize returns the estimated size in bytes of the input represented in the
// histogram supplied. // histogram supplied.
func (c cTable) estimateSize(hist []uint32) int { func (c cTable) estimateSize(hist []uint32) int {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package snappy package snapref
import ( import (
"encoding/binary" "encoding/binary"
...@@ -118,32 +118,23 @@ func (r *Reader) readFull(p []byte, allowEOF bool) (ok bool) { ...@@ -118,32 +118,23 @@ func (r *Reader) readFull(p []byte, allowEOF bool) (ok bool) {
return true return true
} }
// Read satisfies the io.Reader interface. func (r *Reader) fill() error {
func (r *Reader) Read(p []byte) (int, error) { for r.i >= r.j {
if r.err != nil {
return 0, r.err
}
for {
if r.i < r.j {
n := copy(p, r.decoded[r.i:r.j])
r.i += n
return n, nil
}
if !r.readFull(r.buf[:4], true) { if !r.readFull(r.buf[:4], true) {
return 0, r.err return r.err
} }
chunkType := r.buf[0] chunkType := r.buf[0]
if !r.readHeader { if !r.readHeader {
if chunkType != chunkTypeStreamIdentifier { if chunkType != chunkTypeStreamIdentifier {
r.err = ErrCorrupt r.err = ErrCorrupt
return 0, r.err return r.err
} }
r.readHeader = true r.readHeader = true
} }
chunkLen := int(r.buf[1]) | int(r.buf[2])<<8 | int(r.buf[3])<<16 chunkLen := int(r.buf[1]) | int(r.buf[2])<<8 | int(r.buf[3])<<16
if chunkLen > len(r.buf) { if chunkLen > len(r.buf) {
r.err = ErrUnsupported r.err = ErrUnsupported
return 0, r.err return r.err
} }
// The chunk types are specified at // The chunk types are specified at
...@@ -153,11 +144,11 @@ func (r *Reader) Read(p []byte) (int, error) { ...@@ -153,11 +144,11 @@ func (r *Reader) Read(p []byte) (int, error) {
// Section 4.2. Compressed data (chunk type 0x00). // Section 4.2. Compressed data (chunk type 0x00).
if chunkLen < checksumSize { if chunkLen < checksumSize {
r.err = ErrCorrupt r.err = ErrCorrupt
return 0, r.err return r.err
} }
buf := r.buf[:chunkLen] buf := r.buf[:chunkLen]
if !r.readFull(buf, false) { if !r.readFull(buf, false) {
return 0, r.err return r.err
} }
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24 checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
buf = buf[checksumSize:] buf = buf[checksumSize:]
...@@ -165,19 +156,19 @@ func (r *Reader) Read(p []byte) (int, error) { ...@@ -165,19 +156,19 @@ func (r *Reader) Read(p []byte) (int, error) {
n, err := DecodedLen(buf) n, err := DecodedLen(buf)
if err != nil { if err != nil {
r.err = err r.err = err
return 0, r.err return r.err
} }
if n > len(r.decoded) { if n > len(r.decoded) {
r.err = ErrCorrupt r.err = ErrCorrupt
return 0, r.err return r.err
} }
if _, err := Decode(r.decoded, buf); err != nil { if _, err := Decode(r.decoded, buf); err != nil {
r.err = err r.err = err
return 0, r.err return r.err
} }
if crc(r.decoded[:n]) != checksum { if crc(r.decoded[:n]) != checksum {
r.err = ErrCorrupt r.err = ErrCorrupt
return 0, r.err return r.err
} }
r.i, r.j = 0, n r.i, r.j = 0, n
continue continue
...@@ -186,25 +177,25 @@ func (r *Reader) Read(p []byte) (int, error) { ...@@ -186,25 +177,25 @@ func (r *Reader) Read(p []byte) (int, error) {
// Section 4.3. Uncompressed data (chunk type 0x01). // Section 4.3. Uncompressed data (chunk type 0x01).
if chunkLen < checksumSize { if chunkLen < checksumSize {
r.err = ErrCorrupt r.err = ErrCorrupt
return 0, r.err return r.err
} }
buf := r.buf[:checksumSize] buf := r.buf[:checksumSize]
if !r.readFull(buf, false) { if !r.readFull(buf, false) {
return 0, r.err return r.err
} }
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24 checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
// Read directly into r.decoded instead of via r.buf. // Read directly into r.decoded instead of via r.buf.
n := chunkLen - checksumSize n := chunkLen - checksumSize
if n > len(r.decoded) { if n > len(r.decoded) {
r.err = ErrCorrupt r.err = ErrCorrupt
return 0, r.err return r.err
} }
if !r.readFull(r.decoded[:n], false) { if !r.readFull(r.decoded[:n], false) {
return 0, r.err return r.err
} }
if crc(r.decoded[:n]) != checksum { if crc(r.decoded[:n]) != checksum {
r.err = ErrCorrupt r.err = ErrCorrupt
return 0, r.err return r.err
} }
r.i, r.j = 0, n r.i, r.j = 0, n
continue continue
...@@ -213,15 +204,15 @@ func (r *Reader) Read(p []byte) (int, error) { ...@@ -213,15 +204,15 @@ func (r *Reader) Read(p []byte) (int, error) {
// Section 4.1. Stream identifier (chunk type 0xff). // Section 4.1. Stream identifier (chunk type 0xff).
if chunkLen != len(magicBody) { if chunkLen != len(magicBody) {
r.err = ErrCorrupt r.err = ErrCorrupt
return 0, r.err return r.err
} }
if !r.readFull(r.buf[:len(magicBody)], false) { if !r.readFull(r.buf[:len(magicBody)], false) {
return 0, r.err return r.err
} }
for i := 0; i < len(magicBody); i++ { for i := 0; i < len(magicBody); i++ {
if r.buf[i] != magicBody[i] { if r.buf[i] != magicBody[i] {
r.err = ErrCorrupt r.err = ErrCorrupt
return 0, r.err return r.err
} }
} }
continue continue
...@@ -230,12 +221,44 @@ func (r *Reader) Read(p []byte) (int, error) { ...@@ -230,12 +221,44 @@ func (r *Reader) Read(p []byte) (int, error) {
if chunkType <= 0x7f { if chunkType <= 0x7f {
// Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f). // Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f).
r.err = ErrUnsupported r.err = ErrUnsupported
return 0, r.err return r.err
} }
// Section 4.4 Padding (chunk type 0xfe). // Section 4.4 Padding (chunk type 0xfe).
// Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd). // Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd).
if !r.readFull(r.buf[:chunkLen], false) { if !r.readFull(r.buf[:chunkLen], false) {
return 0, r.err return r.err
} }
} }
return nil
}
// Read satisfies the io.Reader interface.
func (r *Reader) Read(p []byte) (int, error) {
if r.err != nil {
return 0, r.err
}
if err := r.fill(); err != nil {
return 0, err
}
n := copy(p, r.decoded[r.i:r.j])
r.i += n
return n, nil
}
// ReadByte satisfies the io.ByteReader interface.
func (r *Reader) ReadByte() (byte, error) {
if r.err != nil {
return 0, r.err
}
if err := r.fill(); err != nil {
return 0, err
}
c := r.decoded[r.i]
r.i++
return c, nil
} }
...@@ -2,9 +2,7 @@ ...@@ -2,9 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build !amd64,!arm64 appengine !gc noasm package snapref
package snappy
// decode writes the decoding of src to dst. It assumes that the varint-encoded // decode writes the decoding of src to dst. It assumes that the varint-encoded
// length of the decompressed bytes has already been read, and that len(dst) // length of the decompressed bytes has already been read, and that len(dst)
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package snappy package snapref
import ( import (
"encoding/binary" "encoding/binary"
......
...@@ -2,9 +2,7 @@ ...@@ -2,9 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build !amd64,!arm64 appengine !gc noasm package snapref
package snappy
func load32(b []byte, i int) uint32 { func load32(b []byte, i int) uint32 {
b = b[i : i+4 : len(b)] // Help the compiler eliminate bounds checks on the next line. b = b[i : i+4 : len(b)] // Help the compiler eliminate bounds checks on the next line.
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package snappy implements the Snappy compression format. It aims for very // Package snapref implements the Snappy compression format. It aims for very
// high speeds and reasonable compression. // high speeds and reasonable compression.
// //
// There are actually two Snappy formats: block and stream. They are related, // There are actually two Snappy formats: block and stream. They are related,
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
// //
// The canonical, C++ implementation is at https://github.com/google/snappy and // The canonical, C++ implementation is at https://github.com/google/snappy and
// it only implements the block format. // it only implements the block format.
package snappy // import "github.com/golang/snappy" package snapref
import ( import (
"hash/crc32" "hash/crc32"
......
module github.com/klauspost/compress
go 1.16
...@@ -16,8 +16,7 @@ Currently the package is heavily optimized for 64 bit processors and will be sig ...@@ -16,8 +16,7 @@ Currently the package is heavily optimized for 64 bit processors and will be sig
Install using `go get -u github.com/klauspost/compress`. The package is located in `github.com/klauspost/compress/zstd`. Install using `go get -u github.com/klauspost/compress`. The package is located in `github.com/klauspost/compress/zstd`.
Godoc Documentation: https://godoc.org/github.com/klauspost/compress/zstd [![Go Reference](https://pkg.go.dev/badge/github.com/klauspost/compress/zstd.svg)](https://pkg.go.dev/github.com/klauspost/compress/zstd)
## Compressor ## Compressor
...@@ -153,7 +152,7 @@ file out level insize outsize millis mb/s ...@@ -153,7 +152,7 @@ file out level insize outsize millis mb/s
silesia.tar zskp 1 211947520 73101992 643 313.87 silesia.tar zskp 1 211947520 73101992 643 313.87
silesia.tar zskp 2 211947520 67504318 969 208.38 silesia.tar zskp 2 211947520 67504318 969 208.38
silesia.tar zskp 3 211947520 64595893 2007 100.68 silesia.tar zskp 3 211947520 64595893 2007 100.68
silesia.tar zskp 4 211947520 60995370 7691 26.28 silesia.tar zskp 4 211947520 60995370 8825 22.90
cgo zstd: cgo zstd:
silesia.tar zstd 1 211947520 73605392 543 371.56 silesia.tar zstd 1 211947520 73605392 543 371.56
...@@ -163,7 +162,7 @@ silesia.tar zstd 9 211947520 60212393 5063 39.92 ...@@ -163,7 +162,7 @@ silesia.tar zstd 9 211947520 60212393 5063 39.92
gzip, stdlib/this package: gzip, stdlib/this package:
silesia.tar gzstd 1 211947520 80007735 1654 122.21 silesia.tar gzstd 1 211947520 80007735 1654 122.21
silesia.tar gzkp 1 211947520 80369488 1168 173.06 silesia.tar gzkp 1 211947520 80136201 1152 175.45
GOB stream of binary data. Highly compressible. GOB stream of binary data. Highly compressible.
https://files.klauspost.com/compress/gob-stream.7z https://files.klauspost.com/compress/gob-stream.7z
...@@ -172,13 +171,15 @@ file out level insize outsize millis mb/s ...@@ -172,13 +171,15 @@ file out level insize outsize millis mb/s
gob-stream zskp 1 1911399616 235022249 3088 590.30 gob-stream zskp 1 1911399616 235022249 3088 590.30
gob-stream zskp 2 1911399616 205669791 3786 481.34 gob-stream zskp 2 1911399616 205669791 3786 481.34
gob-stream zskp 3 1911399616 175034659 9636 189.17 gob-stream zskp 3 1911399616 175034659 9636 189.17
gob-stream zskp 4 1911399616 167273881 29337 62.13 gob-stream zskp 4 1911399616 165609838 50369 36.19
gob-stream zstd 1 1911399616 249810424 2637 691.26 gob-stream zstd 1 1911399616 249810424 2637 691.26
gob-stream zstd 3 1911399616 208192146 3490 522.31 gob-stream zstd 3 1911399616 208192146 3490 522.31
gob-stream zstd 6 1911399616 193632038 6687 272.56 gob-stream zstd 6 1911399616 193632038 6687 272.56
gob-stream zstd 9 1911399616 177620386 16175 112.70 gob-stream zstd 9 1911399616 177620386 16175 112.70
gob-stream gzstd 1 1911399616 357382641 10251 177.82 gob-stream gzstd 1 1911399616 357382641 10251 177.82
gob-stream gzkp 1 1911399616 362156523 5695 320.08 gob-stream gzkp 1 1911399616 359753026 5438 335.20
The test data for the Large Text Compression Benchmark is the first The test data for the Large Text Compression Benchmark is the first
10^9 bytes of the English Wikipedia dump on Mar. 3, 2006. 10^9 bytes of the English Wikipedia dump on Mar. 3, 2006.
...@@ -188,11 +189,13 @@ file out level insize outsize millis mb/s ...@@ -188,11 +189,13 @@ file out level insize outsize millis mb/s
enwik9 zskp 1 1000000000 343848582 3609 264.18 enwik9 zskp 1 1000000000 343848582 3609 264.18
enwik9 zskp 2 1000000000 317276632 5746 165.97 enwik9 zskp 2 1000000000 317276632 5746 165.97
enwik9 zskp 3 1000000000 292243069 12162 78.41 enwik9 zskp 3 1000000000 292243069 12162 78.41
enwik9 zskp 4 1000000000 275241169 36430 26.18 enwik9 zskp 4 1000000000 262183768 82837 11.51
enwik9 zstd 1 1000000000 358072021 3110 306.65 enwik9 zstd 1 1000000000 358072021 3110 306.65
enwik9 zstd 3 1000000000 313734672 4784 199.35 enwik9 zstd 3 1000000000 313734672 4784 199.35
enwik9 zstd 6 1000000000 295138875 10290 92.68 enwik9 zstd 6 1000000000 295138875 10290 92.68
enwik9 zstd 9 1000000000 278348700 28549 33.40 enwik9 zstd 9 1000000000 278348700 28549 33.40
enwik9 gzstd 1 1000000000 382578136 9604 99.30 enwik9 gzstd 1 1000000000 382578136 9604 99.30
enwik9 gzkp 1 1000000000 383825945 6544 145.73 enwik9 gzkp 1 1000000000 383825945 6544 145.73
...@@ -203,13 +206,15 @@ file out level insize outsize millis mb/s ...@@ -203,13 +206,15 @@ file out level insize outsize millis mb/s
github-june-2days-2019.json zskp 1 6273951764 699045015 10620 563.40 github-june-2days-2019.json zskp 1 6273951764 699045015 10620 563.40
github-june-2days-2019.json zskp 2 6273951764 617881763 11687 511.96 github-june-2days-2019.json zskp 2 6273951764 617881763 11687 511.96
github-june-2days-2019.json zskp 3 6273951764 524340691 34043 175.75 github-june-2days-2019.json zskp 3 6273951764 524340691 34043 175.75
github-june-2days-2019.json zskp 4 6273951764 503314661 93811 63.78 github-june-2days-2019.json zskp 4 6273951764 470320075 170190 35.16
github-june-2days-2019.json zstd 1 6273951764 766284037 8450 708.00 github-june-2days-2019.json zstd 1 6273951764 766284037 8450 708.00
github-june-2days-2019.json zstd 3 6273951764 661889476 10927 547.57 github-june-2days-2019.json zstd 3 6273951764 661889476 10927 547.57
github-june-2days-2019.json zstd 6 6273951764 642756859 22996 260.18 github-june-2days-2019.json zstd 6 6273951764 642756859 22996 260.18
github-june-2days-2019.json zstd 9 6273951764 601974523 52413 114.16 github-june-2days-2019.json zstd 9 6273951764 601974523 52413 114.16
github-june-2days-2019.json gzstd 1 6273951764 1164400847 29948 199.79 github-june-2days-2019.json gzstd 1 6273951764 1164400847 29948 199.79
github-june-2days-2019.json gzkp 1 6273951764 1128755542 19236 311.03 github-june-2days-2019.json gzkp 1 6273951764 1125417694 21788 274.61
VM Image, Linux mint with a few installed applications: VM Image, Linux mint with a few installed applications:
https://files.klauspost.com/compress/rawstudio-mint14.7z https://files.klauspost.com/compress/rawstudio-mint14.7z
...@@ -218,13 +223,15 @@ file out level insize outsize millis mb/s ...@@ -218,13 +223,15 @@ file out level insize outsize millis mb/s
rawstudio-mint14.tar zskp 1 8558382592 3667489370 20210 403.84 rawstudio-mint14.tar zskp 1 8558382592 3667489370 20210 403.84
rawstudio-mint14.tar zskp 2 8558382592 3364592300 31873 256.07 rawstudio-mint14.tar zskp 2 8558382592 3364592300 31873 256.07
rawstudio-mint14.tar zskp 3 8558382592 3158085214 77675 105.08 rawstudio-mint14.tar zskp 3 8558382592 3158085214 77675 105.08
rawstudio-mint14.tar zskp 4 8558382592 3020370044 404956 20.16 rawstudio-mint14.tar zskp 4 8558382592 2965110639 857750 9.52
rawstudio-mint14.tar zstd 1 8558382592 3609250104 17136 476.27 rawstudio-mint14.tar zstd 1 8558382592 3609250104 17136 476.27
rawstudio-mint14.tar zstd 3 8558382592 3341679997 29262 278.92 rawstudio-mint14.tar zstd 3 8558382592 3341679997 29262 278.92
rawstudio-mint14.tar zstd 6 8558382592 3235846406 77904 104.77 rawstudio-mint14.tar zstd 6 8558382592 3235846406 77904 104.77
rawstudio-mint14.tar zstd 9 8558382592 3160778861 140946 57.91 rawstudio-mint14.tar zstd 9 8558382592 3160778861 140946 57.91
rawstudio-mint14.tar gzstd 1 8558382592 3926257486 57722 141.40 rawstudio-mint14.tar gzstd 1 8558382592 3926257486 57722 141.40
rawstudio-mint14.tar gzkp 1 8558382592 3970463184 41749 195.49 rawstudio-mint14.tar gzkp 1 8558382592 3962605659 45113 180.92
CSV data: CSV data:
https://files.klauspost.com/compress/nyc-taxi-data-10M.csv.zst https://files.klauspost.com/compress/nyc-taxi-data-10M.csv.zst
...@@ -233,13 +240,15 @@ file out level insize outsize millis mb/s ...@@ -233,13 +240,15 @@ file out level insize outsize millis mb/s
nyc-taxi-data-10M.csv zskp 1 3325605752 641339945 8925 355.35 nyc-taxi-data-10M.csv zskp 1 3325605752 641339945 8925 355.35
nyc-taxi-data-10M.csv zskp 2 3325605752 591748091 11268 281.44 nyc-taxi-data-10M.csv zskp 2 3325605752 591748091 11268 281.44
nyc-taxi-data-10M.csv zskp 3 3325605752 530289687 25239 125.66 nyc-taxi-data-10M.csv zskp 3 3325605752 530289687 25239 125.66
nyc-taxi-data-10M.csv zskp 4 3325605752 490907191 65939 48.10 nyc-taxi-data-10M.csv zskp 4 3325605752 476268884 135958 23.33
nyc-taxi-data-10M.csv zstd 1 3325605752 687399637 8233 385.18 nyc-taxi-data-10M.csv zstd 1 3325605752 687399637 8233 385.18
nyc-taxi-data-10M.csv zstd 3 3325605752 598514411 10065 315.07 nyc-taxi-data-10M.csv zstd 3 3325605752 598514411 10065 315.07
nyc-taxi-data-10M.csv zstd 6 3325605752 570522953 20038 158.27 nyc-taxi-data-10M.csv zstd 6 3325605752 570522953 20038 158.27
nyc-taxi-data-10M.csv zstd 9 3325605752 517554797 64565 49.12 nyc-taxi-data-10M.csv zstd 9 3325605752 517554797 64565 49.12
nyc-taxi-data-10M.csv gzstd 1 3325605752 928656485 23876 132.83 nyc-taxi-data-10M.csv gzstd 1 3325605752 928656485 23876 132.83
nyc-taxi-data-10M.csv gzkp 1 3325605752 924718719 16388 193.53 nyc-taxi-data-10M.csv gzkp 1 3325605752 922257165 16780 189.00
``` ```
## Decompressor ## Decompressor
...@@ -405,13 +414,28 @@ BenchmarkDecoder_DecodeAllParallelCgo/comp-data.bin.zst-16 749938 ...@@ -405,13 +414,28 @@ BenchmarkDecoder_DecodeAllParallelCgo/comp-data.bin.zst-16 749938
This reflects the performance around May 2020, but this may be out of date. This reflects the performance around May 2020, but this may be out of date.
## Zstd inside ZIP files
It is possible to use zstandard to compress individual files inside zip archives.
While this isn't widely supported it can be useful for internal files.
To support the compression and decompression of these files you must register a compressor and decompressor.
It is highly recommended registering the (de)compressors on individual zip Reader/Writer and NOT
use the global registration functions. The main reason for this is that 2 registrations from
different packages will result in a panic.
It is a good idea to only have a single compressor and decompressor, since they can be used for multiple zip
files concurrently, and using a single instance will allow reusing some resources.
See [this example](https://pkg.go.dev/github.com/klauspost/compress/zstd#example-ZipCompressor) for
how to compress and decompress files inside zip archives.
# Contributions # Contributions
Contributions are always welcome. Contributions are always welcome.
For new features/fixes, remember to add tests and for performance enhancements include benchmarks. For new features/fixes, remember to add tests and for performance enhancements include benchmarks.
For sending files for reproducing errors use a service like [goobox](https://goobox.io/#/upload) or similar to share your files.
For general feedback and experience reports, feel free to open an issue or write me on [Twitter](https://twitter.com/sh0dan). For general feedback and experience reports, feel free to open an issue or write me on [Twitter](https://twitter.com/sh0dan).
This package includes the excellent [`github.com/cespare/xxhash`](https://github.com/cespare/xxhash) package Copyright (c) 2016 Caleb Spare. This package includes the excellent [`github.com/cespare/xxhash`](https://github.com/cespare/xxhash) package Copyright (c) 2016 Caleb Spare.
...@@ -123,12 +123,10 @@ func newBlockDec(lowMem bool) *blockDec { ...@@ -123,12 +123,10 @@ func newBlockDec(lowMem bool) *blockDec {
// Input must be a start of a block and will be at the end of the block when returned. // Input must be a start of a block and will be at the end of the block when returned.
func (b *blockDec) reset(br byteBuffer, windowSize uint64) error { func (b *blockDec) reset(br byteBuffer, windowSize uint64) error {
b.WindowSize = windowSize b.WindowSize = windowSize
tmp := br.readSmall(3) tmp, err := br.readSmall(3)
if tmp == nil { if err != nil {
if debug { println("Reading block header:", err)
println("Reading block header:", io.ErrUnexpectedEOF) return err
}
return io.ErrUnexpectedEOF
} }
bh := uint32(tmp[0]) | (uint32(tmp[1]) << 8) | (uint32(tmp[2]) << 16) bh := uint32(tmp[0]) | (uint32(tmp[1]) << 8) | (uint32(tmp[2]) << 16)
b.Last = bh&1 != 0 b.Last = bh&1 != 0
...@@ -146,7 +144,7 @@ func (b *blockDec) reset(br byteBuffer, windowSize uint64) error { ...@@ -146,7 +144,7 @@ func (b *blockDec) reset(br byteBuffer, windowSize uint64) error {
} }
cSize = 1 cSize = 1
case blockTypeCompressed: case blockTypeCompressed:
if debug { if debugDecoder {
println("Data size on stream:", cSize) println("Data size on stream:", cSize)
} }
b.RLESize = 0 b.RLESize = 0
...@@ -155,7 +153,7 @@ func (b *blockDec) reset(br byteBuffer, windowSize uint64) error { ...@@ -155,7 +153,7 @@ func (b *blockDec) reset(br byteBuffer, windowSize uint64) error {
maxSize = int(windowSize) maxSize = int(windowSize)
} }
if cSize > maxCompressedBlockSize || uint64(cSize) > b.WindowSize { if cSize > maxCompressedBlockSize || uint64(cSize) > b.WindowSize {
if debug { if debugDecoder {
printf("compressed block too big: csize:%d block: %+v\n", uint64(cSize), b) printf("compressed block too big: csize:%d block: %+v\n", uint64(cSize), b)
} }
return ErrCompressedSizeTooBig return ErrCompressedSizeTooBig
...@@ -170,19 +168,18 @@ func (b *blockDec) reset(br byteBuffer, windowSize uint64) error { ...@@ -170,19 +168,18 @@ func (b *blockDec) reset(br byteBuffer, windowSize uint64) error {
// Read block data. // Read block data.
if cap(b.dataStorage) < cSize { if cap(b.dataStorage) < cSize {
if b.lowMem { if b.lowMem || cSize > maxCompressedBlockSize {
b.dataStorage = make([]byte, 0, cSize) b.dataStorage = make([]byte, 0, cSize)
} else { } else {
b.dataStorage = make([]byte, 0, maxBlockSize) b.dataStorage = make([]byte, 0, maxCompressedBlockSize)
} }
} }
if cap(b.dst) <= maxSize { if cap(b.dst) <= maxSize {
b.dst = make([]byte, 0, maxSize+1) b.dst = make([]byte, 0, maxSize+1)
} }
var err error
b.data, err = br.readBig(cSize, b.dataStorage) b.data, err = br.readBig(cSize, b.dataStorage)
if err != nil { if err != nil {
if debug { if debugDecoder {
println("Reading block:", err, "(", cSize, ")", len(b.data)) println("Reading block:", err, "(", cSize, ")", len(b.data))
printf("%T", br) printf("%T", br)
} }
...@@ -252,7 +249,7 @@ func (b *blockDec) startDecoder() { ...@@ -252,7 +249,7 @@ func (b *blockDec) startDecoder() {
b: b.dst, b: b.dst,
err: err, err: err,
} }
if debug { if debugDecoder {
println("Decompressed to", len(b.dst), "bytes, error:", err) println("Decompressed to", len(b.dst), "bytes, error:", err)
} }
b.result <- o b.result <- o
...@@ -267,7 +264,7 @@ func (b *blockDec) startDecoder() { ...@@ -267,7 +264,7 @@ func (b *blockDec) startDecoder() {
default: default:
panic("Invalid block type") panic("Invalid block type")
} }
if debug { if debugDecoder {
println("blockDec: Finished block") println("blockDec: Finished block")
} }
} }
...@@ -300,7 +297,7 @@ func (b *blockDec) decodeBuf(hist *history) error { ...@@ -300,7 +297,7 @@ func (b *blockDec) decodeBuf(hist *history) error {
b.dst = hist.b b.dst = hist.b
hist.b = nil hist.b = nil
err := b.decodeCompressed(hist) err := b.decodeCompressed(hist)
if debug { if debugDecoder {
println("Decompressed to total", len(b.dst), "bytes, hash:", xxhash.Sum64(b.dst), "error:", err) println("Decompressed to total", len(b.dst), "bytes, hash:", xxhash.Sum64(b.dst), "error:", err)
} }
hist.b = b.dst hist.b = b.dst
...@@ -393,7 +390,7 @@ func (b *blockDec) decodeCompressed(hist *history) error { ...@@ -393,7 +390,7 @@ func (b *blockDec) decodeCompressed(hist *history) error {
in = in[5:] in = in[5:]
} }
} }
if debug { if debugDecoder {
println("literals type:", litType, "litRegenSize:", litRegenSize, "litCompSize:", litCompSize, "sizeFormat:", sizeFormat, "4X:", fourStreams) println("literals type:", litType, "litRegenSize:", litRegenSize, "litCompSize:", litCompSize, "sizeFormat:", sizeFormat, "4X:", fourStreams)
} }
var literals []byte var literals []byte
...@@ -431,7 +428,7 @@ func (b *blockDec) decodeCompressed(hist *history) error { ...@@ -431,7 +428,7 @@ func (b *blockDec) decodeCompressed(hist *history) error {
literals[i] = v literals[i] = v
} }
in = in[1:] in = in[1:]
if debug { if debugDecoder {
printf("Found %d RLE compressed literals\n", litRegenSize) printf("Found %d RLE compressed literals\n", litRegenSize)
} }
case literalsBlockTreeless: case literalsBlockTreeless:
...@@ -442,7 +439,7 @@ func (b *blockDec) decodeCompressed(hist *history) error { ...@@ -442,7 +439,7 @@ func (b *blockDec) decodeCompressed(hist *history) error {
// Store compressed literals, so we defer decoding until we get history. // Store compressed literals, so we defer decoding until we get history.
literals = in[:litCompSize] literals = in[:litCompSize]
in = in[litCompSize:] in = in[litCompSize:]
if debug { if debugDecoder {
printf("Found %d compressed literals\n", litCompSize) printf("Found %d compressed literals\n", litCompSize)
} }
case literalsBlockCompressed: case literalsBlockCompressed:
...@@ -484,7 +481,7 @@ func (b *blockDec) decodeCompressed(hist *history) error { ...@@ -484,7 +481,7 @@ func (b *blockDec) decodeCompressed(hist *history) error {
if len(literals) != litRegenSize { if len(literals) != litRegenSize {
return fmt.Errorf("literal output size mismatch want %d, got %d", litRegenSize, len(literals)) return fmt.Errorf("literal output size mismatch want %d, got %d", litRegenSize, len(literals))
} }
if debug { if debugDecoder {
printf("Decompressed %d literals into %d bytes\n", litCompSize, litRegenSize) printf("Decompressed %d literals into %d bytes\n", litCompSize, litRegenSize)
} }
} }
...@@ -535,12 +532,12 @@ func (b *blockDec) decodeCompressed(hist *history) error { ...@@ -535,12 +532,12 @@ func (b *blockDec) decodeCompressed(hist *history) error {
br := byteReader{b: in, off: 0} br := byteReader{b: in, off: 0}
compMode := br.Uint8() compMode := br.Uint8()
br.advance(1) br.advance(1)
if debug { if debugDecoder {
printf("Compression modes: 0b%b", compMode) printf("Compression modes: 0b%b", compMode)
} }
for i := uint(0); i < 3; i++ { for i := uint(0); i < 3; i++ {
mode := seqCompMode((compMode >> (6 - i*2)) & 3) mode := seqCompMode((compMode >> (6 - i*2)) & 3)
if debug { if debugDecoder {
println("Table", tableIndex(i), "is", mode) println("Table", tableIndex(i), "is", mode)
} }
var seq *sequenceDec var seq *sequenceDec
...@@ -571,7 +568,7 @@ func (b *blockDec) decodeCompressed(hist *history) error { ...@@ -571,7 +568,7 @@ func (b *blockDec) decodeCompressed(hist *history) error {
} }
dec.setRLE(symb) dec.setRLE(symb)
seq.fse = dec seq.fse = dec
if debug { if debugDecoder {
printf("RLE set to %+v, code: %v", symb, v) printf("RLE set to %+v, code: %v", symb, v)
} }
case compModeFSE: case compModeFSE:
...@@ -587,7 +584,7 @@ func (b *blockDec) decodeCompressed(hist *history) error { ...@@ -587,7 +584,7 @@ func (b *blockDec) decodeCompressed(hist *history) error {
println("Transform table error:", err) println("Transform table error:", err)
return err return err
} }
if debug { if debugDecoder {
println("Read table ok", "symbolLen:", dec.symbolLen) println("Read table ok", "symbolLen:", dec.symbolLen)
} }
seq.fse = dec seq.fse = dec
...@@ -655,7 +652,7 @@ func (b *blockDec) decodeCompressed(hist *history) error { ...@@ -655,7 +652,7 @@ func (b *blockDec) decodeCompressed(hist *history) error {
if huff != nil { if huff != nil {
hist.huffTree = huff hist.huffTree = huff
} }
if debug { if debugDecoder {
println("Final literals:", len(literals), "hash:", xxhash.Sum64(literals), "and", nSeqs, "sequences.") println("Final literals:", len(literals), "hash:", xxhash.Sum64(literals), "and", nSeqs, "sequences.")
} }
...@@ -672,7 +669,7 @@ func (b *blockDec) decodeCompressed(hist *history) error { ...@@ -672,7 +669,7 @@ func (b *blockDec) decodeCompressed(hist *history) error {
if err != nil { if err != nil {
return err return err
} }
if debug { if debugDecoder {
println("History merged ok") println("History merged ok")
} }
br := &bitReader{} br := &bitReader{}
...@@ -731,7 +728,7 @@ func (b *blockDec) decodeCompressed(hist *history) error { ...@@ -731,7 +728,7 @@ func (b *blockDec) decodeCompressed(hist *history) error {
} }
hist.append(b.dst) hist.append(b.dst)
hist.recentOffsets = seqs.prevOffset hist.recentOffsets = seqs.prevOffset
if debug { if debugDecoder {
println("Finished block with literals:", len(literals), "and", nSeqs, "sequences.") println("Finished block with literals:", len(literals), "and", nSeqs, "sequences.")
} }
......
...@@ -156,7 +156,7 @@ func (h *literalsHeader) setSize(regenLen int) { ...@@ -156,7 +156,7 @@ func (h *literalsHeader) setSize(regenLen int) {
switch { switch {
case inBits < 5: case inBits < 5:
lh |= (uint64(regenLen) << 3) | (1 << 60) lh |= (uint64(regenLen) << 3) | (1 << 60)
if debug { if debugEncoder {
got := int(lh>>3) & 0xff got := int(lh>>3) & 0xff
if got != regenLen { if got != regenLen {
panic(fmt.Sprint("litRegenSize = ", regenLen, "(want) != ", got, "(got)")) panic(fmt.Sprint("litRegenSize = ", regenLen, "(want) != ", got, "(got)"))
...@@ -184,7 +184,7 @@ func (h *literalsHeader) setSizes(compLen, inLen int, single bool) { ...@@ -184,7 +184,7 @@ func (h *literalsHeader) setSizes(compLen, inLen int, single bool) {
lh |= 1 << 2 lh |= 1 << 2
} }
lh |= (uint64(inLen) << 4) | (uint64(compLen) << (10 + 4)) | (3 << 60) lh |= (uint64(inLen) << 4) | (uint64(compLen) << (10 + 4)) | (3 << 60)
if debug { if debugEncoder {
const mmask = (1 << 24) - 1 const mmask = (1 << 24) - 1
n := (lh >> 4) & mmask n := (lh >> 4) & mmask
if int(n&1023) != inLen { if int(n&1023) != inLen {
...@@ -312,7 +312,7 @@ func (b *blockEnc) encodeRaw(a []byte) { ...@@ -312,7 +312,7 @@ func (b *blockEnc) encodeRaw(a []byte) {
bh.setType(blockTypeRaw) bh.setType(blockTypeRaw)
b.output = bh.appendTo(b.output[:0]) b.output = bh.appendTo(b.output[:0])
b.output = append(b.output, a...) b.output = append(b.output, a...)
if debug { if debugEncoder {
println("Adding RAW block, length", len(a), "last:", b.last) println("Adding RAW block, length", len(a), "last:", b.last)
} }
} }
...@@ -325,7 +325,7 @@ func (b *blockEnc) encodeRawTo(dst, src []byte) []byte { ...@@ -325,7 +325,7 @@ func (b *blockEnc) encodeRawTo(dst, src []byte) []byte {
bh.setType(blockTypeRaw) bh.setType(blockTypeRaw)
dst = bh.appendTo(dst) dst = bh.appendTo(dst)
dst = append(dst, src...) dst = append(dst, src...)
if debug { if debugEncoder {
println("Adding RAW block, length", len(src), "last:", b.last) println("Adding RAW block, length", len(src), "last:", b.last)
} }
return dst return dst
...@@ -339,7 +339,7 @@ func (b *blockEnc) encodeLits(lits []byte, raw bool) error { ...@@ -339,7 +339,7 @@ func (b *blockEnc) encodeLits(lits []byte, raw bool) error {
// Don't compress extremely small blocks // Don't compress extremely small blocks
if len(lits) < 8 || (len(lits) < 32 && b.dictLitEnc == nil) || raw { if len(lits) < 8 || (len(lits) < 32 && b.dictLitEnc == nil) || raw {
if debug { if debugEncoder {
println("Adding RAW block, length", len(lits), "last:", b.last) println("Adding RAW block, length", len(lits), "last:", b.last)
} }
bh.setType(blockTypeRaw) bh.setType(blockTypeRaw)
...@@ -371,7 +371,7 @@ func (b *blockEnc) encodeLits(lits []byte, raw bool) error { ...@@ -371,7 +371,7 @@ func (b *blockEnc) encodeLits(lits []byte, raw bool) error {
switch err { switch err {
case huff0.ErrIncompressible: case huff0.ErrIncompressible:
if debug { if debugEncoder {
println("Adding RAW block, length", len(lits), "last:", b.last) println("Adding RAW block, length", len(lits), "last:", b.last)
} }
bh.setType(blockTypeRaw) bh.setType(blockTypeRaw)
...@@ -379,7 +379,7 @@ func (b *blockEnc) encodeLits(lits []byte, raw bool) error { ...@@ -379,7 +379,7 @@ func (b *blockEnc) encodeLits(lits []byte, raw bool) error {
b.output = append(b.output, lits...) b.output = append(b.output, lits...)
return nil return nil
case huff0.ErrUseRLE: case huff0.ErrUseRLE:
if debug { if debugEncoder {
println("Adding RLE block, length", len(lits)) println("Adding RLE block, length", len(lits))
} }
bh.setType(blockTypeRLE) bh.setType(blockTypeRLE)
...@@ -396,12 +396,12 @@ func (b *blockEnc) encodeLits(lits []byte, raw bool) error { ...@@ -396,12 +396,12 @@ func (b *blockEnc) encodeLits(lits []byte, raw bool) error {
bh.setType(blockTypeCompressed) bh.setType(blockTypeCompressed)
var lh literalsHeader var lh literalsHeader
if reUsed { if reUsed {
if debug { if debugEncoder {
println("Reused tree, compressed to", len(out)) println("Reused tree, compressed to", len(out))
} }
lh.setType(literalsBlockTreeless) lh.setType(literalsBlockTreeless)
} else { } else {
if debug { if debugEncoder {
println("New tree, compressed to", len(out), "tree size:", len(b.litEnc.OutTable)) println("New tree, compressed to", len(out), "tree size:", len(b.litEnc.OutTable))
} }
lh.setType(literalsBlockCompressed) lh.setType(literalsBlockCompressed)
...@@ -517,7 +517,7 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error { ...@@ -517,7 +517,7 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error {
lh.setSize(len(b.literals)) lh.setSize(len(b.literals))
b.output = lh.appendTo(b.output) b.output = lh.appendTo(b.output)
b.output = append(b.output, b.literals...) b.output = append(b.output, b.literals...)
if debug { if debugEncoder {
println("Adding literals RAW, length", len(b.literals)) println("Adding literals RAW, length", len(b.literals))
} }
case huff0.ErrUseRLE: case huff0.ErrUseRLE:
...@@ -525,22 +525,22 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error { ...@@ -525,22 +525,22 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error {
lh.setSize(len(b.literals)) lh.setSize(len(b.literals))
b.output = lh.appendTo(b.output) b.output = lh.appendTo(b.output)
b.output = append(b.output, b.literals[0]) b.output = append(b.output, b.literals[0])
if debug { if debugEncoder {
println("Adding literals RLE") println("Adding literals RLE")
} }
case nil: case nil:
// Compressed litLen... // Compressed litLen...
if reUsed { if reUsed {
if debug { if debugEncoder {
println("reused tree") println("reused tree")
} }
lh.setType(literalsBlockTreeless) lh.setType(literalsBlockTreeless)
} else { } else {
if debug { if debugEncoder {
println("new tree, size:", len(b.litEnc.OutTable)) println("new tree, size:", len(b.litEnc.OutTable))
} }
lh.setType(literalsBlockCompressed) lh.setType(literalsBlockCompressed)
if debug { if debugEncoder {
_, _, err := huff0.ReadTable(out, nil) _, _, err := huff0.ReadTable(out, nil)
if err != nil { if err != nil {
panic(err) panic(err)
...@@ -548,18 +548,18 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error { ...@@ -548,18 +548,18 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error {
} }
} }
lh.setSizes(len(out), len(b.literals), single) lh.setSizes(len(out), len(b.literals), single)
if debug { if debugEncoder {
printf("Compressed %d literals to %d bytes", len(b.literals), len(out)) printf("Compressed %d literals to %d bytes", len(b.literals), len(out))
println("Adding literal header:", lh) println("Adding literal header:", lh)
} }
b.output = lh.appendTo(b.output) b.output = lh.appendTo(b.output)
b.output = append(b.output, out...) b.output = append(b.output, out...)
b.litEnc.Reuse = huff0.ReusePolicyAllow b.litEnc.Reuse = huff0.ReusePolicyAllow
if debug { if debugEncoder {
println("Adding literals compressed") println("Adding literals compressed")
} }
default: default:
if debug { if debugEncoder {
println("Adding literals ERROR:", err) println("Adding literals ERROR:", err)
} }
return err return err
...@@ -577,7 +577,7 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error { ...@@ -577,7 +577,7 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error {
n := len(b.sequences) - 0x7f00 n := len(b.sequences) - 0x7f00
b.output = append(b.output, 255, uint8(n), uint8(n>>8)) b.output = append(b.output, 255, uint8(n), uint8(n>>8))
} }
if debug { if debugEncoder {
println("Encoding", len(b.sequences), "sequences") println("Encoding", len(b.sequences), "sequences")
} }
b.genCodes() b.genCodes()
...@@ -611,17 +611,17 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error { ...@@ -611,17 +611,17 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error {
nSize = nSize + (nSize+2*8*16)>>4 nSize = nSize + (nSize+2*8*16)>>4
switch { switch {
case predefSize <= prevSize && predefSize <= nSize || forcePreDef: case predefSize <= prevSize && predefSize <= nSize || forcePreDef:
if debug { if debugEncoder {
println("Using predefined", predefSize>>3, "<=", nSize>>3) println("Using predefined", predefSize>>3, "<=", nSize>>3)
} }
return preDef, compModePredefined return preDef, compModePredefined
case prevSize <= nSize: case prevSize <= nSize:
if debug { if debugEncoder {
println("Using previous", prevSize>>3, "<=", nSize>>3) println("Using previous", prevSize>>3, "<=", nSize>>3)
} }
return prev, compModeRepeat return prev, compModeRepeat
default: default:
if debug { if debugEncoder {
println("Using new, predef", predefSize>>3, ". previous:", prevSize>>3, ">", nSize>>3, "header max:", cur.maxHeaderSize()>>3, "bytes") println("Using new, predef", predefSize>>3, ". previous:", prevSize>>3, ">", nSize>>3, "header max:", cur.maxHeaderSize()>>3, "bytes")
println("tl:", cur.actualTableLog, "symbolLen:", cur.symbolLen, "norm:", cur.norm[:cur.symbolLen], "hist", cur.count[:cur.symbolLen]) println("tl:", cur.actualTableLog, "symbolLen:", cur.symbolLen, "norm:", cur.norm[:cur.symbolLen], "hist", cur.count[:cur.symbolLen])
} }
...@@ -634,7 +634,7 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error { ...@@ -634,7 +634,7 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error {
if llEnc.useRLE { if llEnc.useRLE {
mode |= uint8(compModeRLE) << 6 mode |= uint8(compModeRLE) << 6
llEnc.setRLE(b.sequences[0].llCode) llEnc.setRLE(b.sequences[0].llCode)
if debug { if debugEncoder {
println("llEnc.useRLE") println("llEnc.useRLE")
} }
} else { } else {
...@@ -645,7 +645,7 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error { ...@@ -645,7 +645,7 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error {
if ofEnc.useRLE { if ofEnc.useRLE {
mode |= uint8(compModeRLE) << 4 mode |= uint8(compModeRLE) << 4
ofEnc.setRLE(b.sequences[0].ofCode) ofEnc.setRLE(b.sequences[0].ofCode)
if debug { if debugEncoder {
println("ofEnc.useRLE") println("ofEnc.useRLE")
} }
} else { } else {
...@@ -657,7 +657,7 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error { ...@@ -657,7 +657,7 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error {
if mlEnc.useRLE { if mlEnc.useRLE {
mode |= uint8(compModeRLE) << 2 mode |= uint8(compModeRLE) << 2
mlEnc.setRLE(b.sequences[0].mlCode) mlEnc.setRLE(b.sequences[0].mlCode)
if debug { if debugEncoder {
println("mlEnc.useRLE, code: ", b.sequences[0].mlCode, "value", b.sequences[0].matchLen) println("mlEnc.useRLE, code: ", b.sequences[0].mlCode, "value", b.sequences[0].matchLen)
} }
} else { } else {
...@@ -666,7 +666,7 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error { ...@@ -666,7 +666,7 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error {
mode |= uint8(m) << 2 mode |= uint8(m) << 2
} }
b.output = append(b.output, mode) b.output = append(b.output, mode)
if debug { if debugEncoder {
printf("Compression modes: 0b%b", mode) printf("Compression modes: 0b%b", mode)
} }
b.output, err = llEnc.writeCount(b.output) b.output, err = llEnc.writeCount(b.output)
...@@ -786,7 +786,7 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error { ...@@ -786,7 +786,7 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error {
// Size is output minus block header. // Size is output minus block header.
bh.setSize(uint32(len(b.output)-bhOffset) - 3) bh.setSize(uint32(len(b.output)-bhOffset) - 3)
if debug { if debugEncoder {
println("Rewriting block header", bh) println("Rewriting block header", bh)
} }
_ = bh.appendTo(b.output[bhOffset:bhOffset]) _ = bh.appendTo(b.output[bhOffset:bhOffset])
......
...@@ -12,8 +12,8 @@ import ( ...@@ -12,8 +12,8 @@ import (
type byteBuffer interface { type byteBuffer interface {
// Read up to 8 bytes. // Read up to 8 bytes.
// Returns nil if no more input is available. // Returns io.ErrUnexpectedEOF if this cannot be satisfied.
readSmall(n int) []byte readSmall(n int) ([]byte, error)
// Read >8 bytes. // Read >8 bytes.
// MAY use the destination slice. // MAY use the destination slice.
...@@ -29,17 +29,17 @@ type byteBuffer interface { ...@@ -29,17 +29,17 @@ type byteBuffer interface {
// in-memory buffer // in-memory buffer
type byteBuf []byte type byteBuf []byte
func (b *byteBuf) readSmall(n int) []byte { func (b *byteBuf) readSmall(n int) ([]byte, error) {
if debugAsserts && n > 8 { if debugAsserts && n > 8 {
panic(fmt.Errorf("small read > 8 (%d). use readBig", n)) panic(fmt.Errorf("small read > 8 (%d). use readBig", n))
} }
bb := *b bb := *b
if len(bb) < n { if len(bb) < n {
return nil return nil, io.ErrUnexpectedEOF
} }
r := bb[:n] r := bb[:n]
*b = bb[n:] *b = bb[n:]
return r return r, nil
} }
func (b *byteBuf) readBig(n int, dst []byte) ([]byte, error) { func (b *byteBuf) readBig(n int, dst []byte) ([]byte, error) {
...@@ -81,19 +81,22 @@ type readerWrapper struct { ...@@ -81,19 +81,22 @@ type readerWrapper struct {
tmp [8]byte tmp [8]byte
} }
func (r *readerWrapper) readSmall(n int) []byte { func (r *readerWrapper) readSmall(n int) ([]byte, error) {
if debugAsserts && n > 8 { if debugAsserts && n > 8 {
panic(fmt.Errorf("small read > 8 (%d). use readBig", n)) panic(fmt.Errorf("small read > 8 (%d). use readBig", n))
} }
n2, err := io.ReadFull(r.r, r.tmp[:n]) n2, err := io.ReadFull(r.r, r.tmp[:n])
// We only really care about the actual bytes read. // We only really care about the actual bytes read.
if n2 != n { if err != nil {
if debug { if err == io.EOF {
return nil, io.ErrUnexpectedEOF
}
if debugDecoder {
println("readSmall: got", n2, "want", n, "err", err) println("readSmall: got", n2, "want", n, "err", err)
} }
return nil return nil, err
} }
return r.tmp[:n] return r.tmp[:n], nil
} }
func (r *readerWrapper) readBig(n int, dst []byte) ([]byte, error) { func (r *readerWrapper) readBig(n int, dst []byte) ([]byte, error) {
......
...@@ -113,9 +113,6 @@ func NewReader(r io.Reader, opts ...DOption) (*Decoder, error) { ...@@ -113,9 +113,6 @@ func NewReader(r io.Reader, opts ...DOption) (*Decoder, error) {
// Returns the number of bytes written and any error that occurred. // Returns the number of bytes written and any error that occurred.
// When the stream is done, io.EOF will be returned. // When the stream is done, io.EOF will be returned.
func (d *Decoder) Read(p []byte) (int, error) { func (d *Decoder) Read(p []byte) (int, error) {
if d.stream == nil {
return 0, ErrDecoderNilInput
}
var n int var n int
for { for {
if len(d.current.b) > 0 { if len(d.current.b) > 0 {
...@@ -138,7 +135,7 @@ func (d *Decoder) Read(p []byte) (int, error) { ...@@ -138,7 +135,7 @@ func (d *Decoder) Read(p []byte) (int, error) {
} }
} }
if len(d.current.b) > 0 { if len(d.current.b) > 0 {
if debug { if debugDecoder {
println("returning", n, "still bytes left:", len(d.current.b)) println("returning", n, "still bytes left:", len(d.current.b))
} }
// Only return error at end of block // Only return error at end of block
...@@ -147,7 +144,7 @@ func (d *Decoder) Read(p []byte) (int, error) { ...@@ -147,7 +144,7 @@ func (d *Decoder) Read(p []byte) (int, error) {
if d.current.err != nil { if d.current.err != nil {
d.drainOutput() d.drainOutput()
} }
if debug { if debugDecoder {
println("returning", n, d.current.err, len(d.decoders)) println("returning", n, d.current.err, len(d.decoders))
} }
return n, d.current.err return n, d.current.err
...@@ -167,20 +164,17 @@ func (d *Decoder) Reset(r io.Reader) error { ...@@ -167,20 +164,17 @@ func (d *Decoder) Reset(r io.Reader) error {
if r == nil { if r == nil {
d.current.err = ErrDecoderNilInput d.current.err = ErrDecoderNilInput
if len(d.current.b) > 0 {
d.current.b = d.current.b[:0]
}
d.current.flushed = true d.current.flushed = true
return nil return nil
} }
if d.stream == nil { // If bytes buffer and < 5MB, do sync decoding anyway.
d.stream = make(chan decodeStream, 1) if bb, ok := r.(byter); ok && bb.Len() < 5<<20 {
d.streamWg.Add(1)
go d.startStreamDecoder(d.stream)
}
// If bytes buffer and < 1MB, do sync decoding anyway.
if bb, ok := r.(byter); ok && bb.Len() < 1<<20 {
bb2 := bb bb2 := bb
if debug { if debugDecoder {
println("*bytes.Buffer detected, doing sync decode, len:", bb.Len()) println("*bytes.Buffer detected, doing sync decode, len:", bb.Len())
} }
b := bb2.Bytes() b := bb2.Bytes()
...@@ -196,12 +190,18 @@ func (d *Decoder) Reset(r io.Reader) error { ...@@ -196,12 +190,18 @@ func (d *Decoder) Reset(r io.Reader) error {
d.current.b = dst d.current.b = dst
d.current.err = err d.current.err = err
d.current.flushed = true d.current.flushed = true
if debug { if debugDecoder {
println("sync decode to", len(dst), "bytes, err:", err) println("sync decode to", len(dst), "bytes, err:", err)
} }
return nil return nil
} }
if d.stream == nil {
d.stream = make(chan decodeStream, 1)
d.streamWg.Add(1)
go d.startStreamDecoder(d.stream)
}
// Remove current block. // Remove current block.
d.current.decodeOutput = decodeOutput{} d.current.decodeOutput = decodeOutput{}
d.current.err = nil d.current.err = nil
...@@ -225,7 +225,7 @@ func (d *Decoder) drainOutput() { ...@@ -225,7 +225,7 @@ func (d *Decoder) drainOutput() {
d.current.cancel = nil d.current.cancel = nil
} }
if d.current.d != nil { if d.current.d != nil {
if debug { if debugDecoder {
printf("re-adding current decoder %p, decoders: %d", d.current.d, len(d.decoders)) printf("re-adding current decoder %p, decoders: %d", d.current.d, len(d.decoders))
} }
d.decoders <- d.current.d d.decoders <- d.current.d
...@@ -238,7 +238,7 @@ func (d *Decoder) drainOutput() { ...@@ -238,7 +238,7 @@ func (d *Decoder) drainOutput() {
} }
for v := range d.current.output { for v := range d.current.output {
if v.d != nil { if v.d != nil {
if debug { if debugDecoder {
printf("re-adding decoder %p", v.d) printf("re-adding decoder %p", v.d)
} }
d.decoders <- v.d d.decoders <- v.d
...@@ -255,17 +255,15 @@ func (d *Decoder) drainOutput() { ...@@ -255,17 +255,15 @@ func (d *Decoder) drainOutput() {
// The return value n is the number of bytes written. // The return value n is the number of bytes written.
// Any error encountered during the write is also returned. // Any error encountered during the write is also returned.
func (d *Decoder) WriteTo(w io.Writer) (int64, error) { func (d *Decoder) WriteTo(w io.Writer) (int64, error) {
if d.stream == nil {
return 0, ErrDecoderNilInput
}
var n int64 var n int64
for { for {
if len(d.current.b) > 0 { if len(d.current.b) > 0 {
n2, err2 := w.Write(d.current.b) n2, err2 := w.Write(d.current.b)
n += int64(n2) n += int64(n2)
if err2 != nil && d.current.err == nil { if err2 != nil && (d.current.err == nil || d.current.err == io.EOF) {
d.current.err = err2 d.current.err = err2
break } else if n2 != len(d.current.b) {
d.current.err = io.ErrShortWrite
} }
} }
if d.current.err != nil { if d.current.err != nil {
...@@ -297,7 +295,7 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) { ...@@ -297,7 +295,7 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) {
block := <-d.decoders block := <-d.decoders
frame := block.localFrame frame := block.localFrame
defer func() { defer func() {
if debug { if debugDecoder {
printf("re-adding decoder: %p", block) printf("re-adding decoder: %p", block)
} }
frame.rawInput = nil frame.rawInput = nil
...@@ -310,7 +308,7 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) { ...@@ -310,7 +308,7 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) {
frame.history.reset() frame.history.reset()
err := frame.reset(&frame.bBuf) err := frame.reset(&frame.bBuf)
if err == io.EOF { if err == io.EOF {
if debug { if debugDecoder {
println("frame reset return EOF") println("frame reset return EOF")
} }
return dst, nil return dst, nil
...@@ -355,7 +353,7 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) { ...@@ -355,7 +353,7 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) {
return dst, err return dst, err
} }
if len(frame.bBuf) == 0 { if len(frame.bBuf) == 0 {
if debug { if debugDecoder {
println("frame dbuf empty") println("frame dbuf empty")
} }
break break
...@@ -371,7 +369,7 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) { ...@@ -371,7 +369,7 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) {
// if no data was available without blocking. // if no data was available without blocking.
func (d *Decoder) nextBlock(blocking bool) (ok bool) { func (d *Decoder) nextBlock(blocking bool) (ok bool) {
if d.current.d != nil { if d.current.d != nil {
if debug { if debugDecoder {
printf("re-adding current decoder %p", d.current.d) printf("re-adding current decoder %p", d.current.d)
} }
d.decoders <- d.current.d d.decoders <- d.current.d
...@@ -391,7 +389,7 @@ func (d *Decoder) nextBlock(blocking bool) (ok bool) { ...@@ -391,7 +389,7 @@ func (d *Decoder) nextBlock(blocking bool) (ok bool) {
return false return false
} }
} }
if debug { if debugDecoder {
println("got", len(d.current.b), "bytes, error:", d.current.err) println("got", len(d.current.b), "bytes, error:", d.current.err)
} }
return true return true
...@@ -485,7 +483,7 @@ func (d *Decoder) startStreamDecoder(inStream chan decodeStream) { ...@@ -485,7 +483,7 @@ func (d *Decoder) startStreamDecoder(inStream chan decodeStream) {
defer d.streamWg.Done() defer d.streamWg.Done()
frame := newFrameDec(d.o) frame := newFrameDec(d.o)
for stream := range inStream { for stream := range inStream {
if debug { if debugDecoder {
println("got new stream") println("got new stream")
} }
br := readerWrapper{r: stream.r} br := readerWrapper{r: stream.r}
...@@ -493,7 +491,7 @@ func (d *Decoder) startStreamDecoder(inStream chan decodeStream) { ...@@ -493,7 +491,7 @@ func (d *Decoder) startStreamDecoder(inStream chan decodeStream) {
for { for {
frame.history.reset() frame.history.reset()
err := frame.reset(&br) err := frame.reset(&br)
if debug && err != nil { if debugDecoder && err != nil {
println("Frame decoder returned", err) println("Frame decoder returned", err)
} }
if err == nil && frame.DictionaryID != nil { if err == nil && frame.DictionaryID != nil {
...@@ -510,7 +508,7 @@ func (d *Decoder) startStreamDecoder(inStream chan decodeStream) { ...@@ -510,7 +508,7 @@ func (d *Decoder) startStreamDecoder(inStream chan decodeStream) {
} }
break break
} }
if debug { if debugDecoder {
println("starting frame decoder") println("starting frame decoder")
} }
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment