diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..f8c4728
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,105 @@
+name: test
+on:
+  pull_request:
+  push:
+  workflow_dispatch:
+
+env:
+  MYSQL_TEST_USER: gotest
+  MYSQL_TEST_PASS: secret
+  MYSQL_TEST_ADDR: 127.0.0.1:3306
+  MYSQL_TEST_CONCURRENT: 1
+
+jobs:
+  list:
+    runs-on: ubuntu-latest
+    outputs:
+      matrix: ${{ steps.set-matrix.outputs.matrix }}
+    steps:
+      - name: list
+        id: set-matrix
+        run: |
+          import json
+          go = [
+              # Keep the most recent production release at the top
+              '1.18',
+              # Older production releases
+              '1.17',
+              '1.16',
+              '1.15',
+              '1.14',
+              '1.13',
+          ]
+          mysql = [
+              '8.0',
+              '5.7',
+              '5.6',
+              'mariadb-10.6',
+              'mariadb-10.5',
+              'mariadb-10.4',
+              'mariadb-10.3',
+          ]
+
+          includes = []
+          # Go versions compatibility check
+          for v in go[1:]:
+                  includes.append({'os': 'ubuntu-latest', 'go': v, 'mysql': mysql[0]})
+
+          matrix = {
+              # OS vs MySQL versions
+              'os': [ 'ubuntu-latest', 'macos-latest', 'windows-latest' ],
+              'go': [ go[0] ],
+              'mysql': mysql,
+
+              'include': includes
+          }
+          output = json.dumps(matrix, separators=(',', ':'))
+          print('::set-output name=matrix::{0}'.format(output))
+        shell: python
+  test:
+    needs: list
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJSON(needs.list.outputs.matrix) }}
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-go@v2
+        with:
+          go-version: ${{ matrix.go }}
+      - uses: shogo82148/actions-setup-mysql@v1
+        with:
+          mysql-version: ${{ matrix.mysql }}
+          user: ${{ env.MYSQL_TEST_USER }}
+          password: ${{ env.MYSQL_TEST_PASS }}
+          my-cnf: |
+            innodb_log_file_size=256MB
+            innodb_buffer_pool_size=512MB
+            max_allowed_packet=16MB
+            ; TestConcurrent fails if max_connections is too large
+            max_connections=50
+            local_infile=1
+      - name: setup database
+        run: |
+          mysql --user 'root' --host '127.0.0.1' -e 'create database gotest;'
+
+      - name: test
+        run: |
+          go test -v '-covermode=count' '-coverprofile=coverage.out'
+
+      - name: Send coverage
+        uses: shogo82148/actions-goveralls@v1
+        with:
+          path-to-profile: coverage.out
+          flag-name: ${{ runner.os }}-Go-${{ matrix.go }}-DB-${{ matrix.mysql }}
+          parallel: true
+
+  # notifies that all test jobs are finished.
+  finish:
+    needs: test
+    if: always()
+    runs-on: ubuntu-latest
+    steps:
+      - uses: shogo82148/actions-goveralls@v1
+        with:
+          parallel-finished: true
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 56fcf25..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,129 +0,0 @@
-sudo: false
-language: go
-go:
-  - 1.10.x
-  - 1.11.x
-  - 1.12.x
-  - 1.13.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
-
-    - os: osx
-      osx_image: xcode10.1
-      addons:
-        homebrew:
-          packages:
-            - mysql
-          update: true
-      go: 1.12.x
-      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\nlocal_infile=1" >> /usr/local/etc/my.cnf
-        - mysql.server start
-        - mysql -uroot -e 'CREATE USER gotest IDENTIFIED BY "secret"'
-        - mysql -uroot -e 'GRANT ALL ON *.* TO gotest'
-        - mysql -uroot -e 'create database gotest;'
-        - export MYSQL_TEST_USER=gotest
-        - export MYSQL_TEST_PASS=secret
-        - export MYSQL_TEST_ADDR=127.0.0.1:3306
-        - 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
diff --git a/.travis/docker.cnf b/.travis/docker.cnf
deleted file mode 100644
index e57754e..0000000
--- a/.travis/docker.cnf
+++ /dev/null
@@ -1,5 +0,0 @@
-[client]
-user = gotest
-password = secret
-host = 127.0.0.1
-port = 3307
diff --git a/.travis/gofmt.sh b/.travis/gofmt.sh
deleted file mode 100755
index 9bf0d16..0000000
--- a/.travis/gofmt.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-set -ev
-
-# Only check for go1.10+ since the gofmt style changed
-if [[ $(go version) =~ go1\.([0-9]+) ]] && ((${BASH_REMATCH[1]} >= 10)); then
-    test -z "$(gofmt -d -s . | tee /dev/stderr)"
-fi
diff --git a/.travis/wait_mysql.sh b/.travis/wait_mysql.sh
deleted file mode 100755
index e87993e..0000000
--- a/.travis/wait_mysql.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-while :
-do
-    if mysql -e 'select version()' 2>&1 | grep 'version()\|ERROR 2059 (HY000):'; then
-        break
-    fi
-    sleep 3
-done
diff --git a/AUTHORS b/AUTHORS
index ad59898..50b9593 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -13,12 +13,17 @@
 
 Aaron Hopkins <go-sql-driver at die.net>
 Achille Roussel <achille.roussel at gmail.com>
+Alex Snast <alexsn at fb.com>
 Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
 Andrew Reid <andrew.reid at tixtrack.com>
+Animesh Ray <mail.rayanimesh at gmail.com>
 Arne Hormann <arnehormann at gmail.com>
+Ariel Mashraki <ariel at mashraki.co.il>
 Asta Xie <xiemengjun at gmail.com>
 Bulat Gaifullin <gaifullinbf at gmail.com>
+Caine Jette <jette at alum.mit.edu>
 Carlos Nieto <jose.carlos at menteslibres.net>
+Chris Kirkland <chriskirkland at github.com>
 Chris Moos <chris at tech9computers.com>
 Craig Wilson <craiggwilson at gmail.com>
 Daniel Montoya <dsmontoyam at gmail.com>
@@ -41,6 +46,7 @@ Ilia Cimpoes <ichimpoesh at gmail.com>
 INADA Naoki <songofacandy at gmail.com>
 Jacek Szwec <szwec.jacek at gmail.com>
 James Harr <james.harr at gmail.com>
+Janek Vedock <janekvedock at comcast.net>
 Jeff Hodges <jeff at somethingsimilar.com>
 Jeffrey Charles <jeffreycharles at gmail.com>
 Jerome Meyer <jxmeyer at gmail.com>
@@ -52,6 +58,7 @@ Julien Schmidt <go-sql-driver at julienschmidt.com>
 Justin Li <jli at j-li.net>
 Justin Nuß <nuss.justin at gmail.com>
 Kamil Dziedzic <kamil at klecza.pl>
+Kei Kamikawa <x00.x7f.x86 at gmail.com>
 Kevin Malachowski <kevin at chowski.com>
 Kieron Woodhouse <kieron.woodhouse at infosum.com>
 Lennart Rudolph <lrudolph at hmc.edu>
@@ -60,6 +67,7 @@ Linh Tran Tuan <linhduonggnu at gmail.com>
 Lion Yang <lion at aosc.xyz>
 Luca Looz <luca.looz92 at gmail.com>
 Lucas Liu <extrafliu at gmail.com>
+Lunny Xiao <xiaolunwen at gmail.com>
 Luke Scott <luke at webconnex.com>
 Maciej Zimnoch <maciej.zimnoch at codilime.com>
 Michael Woolnough <michael.woolnough at gmail.com>
@@ -74,26 +82,35 @@ Reed Allman <rdallman10 at gmail.com>
 Richard Wilkes <wilkes at me.com>
 Robert Russell <robert at rrbrussell.com>
 Runrioter Wung <runrioter at gmail.com>
+Santhosh Kumar Tekuri <santhosh.tekuri at gmail.com>
+Sho Iizuka <sho.i518 at gmail.com>
+Sho Ikeda <suicaicoca at gmail.com>
 Shuode Li <elemount at qq.com>
 Simon J Mudd <sjmudd at pobox.com>
 Soroush Pour <me at soroushjp.com>
 Stan Putrya <root.vagner 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>
 Xiaobing Jiang <s7v7nislands at gmail.com>
 Xiuming Chen <cc at cxm.cc>
+Xuehong Chan <chanxuehong at gmail.com>
 Zhenye Xie <xiezhenye at gmail.com>
+Zhixin Wen <john.wenzhixin at gmail.com>
+Ziheng Lyu <zihenglv at gmail.com>
 
 # Organizations
 
 Barracuda Networks, Inc.
 Counting Ltd.
 DigitalOcean Inc.
+dyves labs AG
 Facebook Inc.
 GitHub Inc.
 Google Inc.
@@ -103,3 +120,4 @@ Multiplay Ltd.
 Percona LLC
 Pivotal Inc.
 Stripe Inc.
+Zendesk Inc.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9cb97b3..72a738e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,29 @@
+## 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:
diff --git a/README.md b/README.md
index d2627a4..ded6e3b 100644
--- a/README.md
+++ b/README.md
@@ -35,12 +35,12 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
   * Supports queries larger than 16MB
   * Full [`sql.RawBytes`](https://golang.org/pkg/database/sql/#RawBytes) support.
   * Intelligent `LONG DATA` handling in prepared statements
-  * Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support
+  * Secure `LOAD DATA LOCAL INFILE` support with file allowlisting and `io.Reader` support
   * Optional `time.Time` parsing
   * Optional placeholder interpolation
 
 ## Requirements
-  * Go 1.10 or higher. We aim to support the 3 latest versions of Go.
+  * Go 1.13 or higher. We aim to support the 3 latest versions of Go.
   * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
 
 ---------------------------------------
@@ -56,15 +56,37 @@ Make sure [Git is installed](https://git-scm.com/downloads) on your machine and
 _Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](https://golang.org/pkg/database/sql/) API then.
 
 Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name)  as `dataSourceName`:
+
 ```go
-import "database/sql"
-import _ "github.com/go-sql-driver/mysql"
+import (
+	"database/sql"
+	"time"
+
+	_ "github.com/go-sql-driver/mysql"
+)
+
+// ...
 
 db, err := sql.Open("mysql", "user:password@/dbname")
+if err != nil {
+	panic(err)
+}
+// See "Important settings" section.
+db.SetConnMaxLifetime(time.Minute * 3)
+db.SetMaxOpenConns(10)
+db.SetMaxIdleConns(10)
 ```
 
 [Examples are available in our Wiki](https://github.com/go-sql-driver/mysql/wiki/Examples "Go-MySQL-Driver Examples").
 
+### Important settings
+
+`db.SetConnMaxLifetime()` is required to ensure connections are closed by the driver safely before connection is closed by MySQL server, OS, or other middlewares. Since some middlewares close idle connections by 5 minutes, we recommend timeout shorter than 5 minutes. This setting helps load balancing and changing system variables too.
+
+`db.SetMaxOpenConns()` is highly recommended to limit the number of connection used by the application. There is no recommended limit number because it depends on application and MySQL server.
+
+`db.SetMaxIdleConns()` is recommended to be set same to `db.SetMaxOpenConns()`. When it is smaller than `SetMaxOpenConns()`, connections can be opened and closed much more frequently than you expect. Idle connections can be closed by the `db.SetConnMaxLifetime()`. If you want to close idle connections more rapidly, you can use `db.SetConnMaxIdleTime()` since Go 1.15.
+
 
 ### DSN (Data Source Name)
 
@@ -122,7 +144,7 @@ Valid Values:   true, false
 Default:        false
 ```
 
-`allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files.
+`allowAllFiles=true` disables the file allowlist for `LOAD DATA LOCAL INFILE` and allows *all* files.
 [*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
 
 ##### `allowCleartextPasswords`
@@ -133,7 +155,7 @@ Valid Values:   true, false
 Default:        false
 ```
 
-`allowCleartextPasswords=true` allows using the [cleartext client side plugin](http://dev.mysql.com/doc/en/cleartext-authentication-plugin.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network.
+`allowCleartextPasswords=true` allows using the [cleartext client side plugin](https://dev.mysql.com/doc/en/cleartext-pluggable-authentication.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network.
 
 ##### `allowNativePasswords`
 
@@ -230,7 +252,7 @@ Default:        false
 
 If `interpolateParams` is true, placeholders (`?`) in calls to `db.Query()` and `db.Exec()` are interpolated into a single query string with given parameters. This reduces the number of roundtrips, since the driver has to prepare a statement, execute it with given parameters and close the statement again with `interpolateParams=false`.
 
-*This can not be used together with the multibyte encodings BIG5, CP932, GB2312, GBK or SJIS. These are blacklisted as they may [introduce a SQL injection vulnerability](http://stackoverflow.com/a/12118602/3430118)!*
+*This can not be used together with the multibyte encodings BIG5, CP932, GB2312, GBK or SJIS. These are rejected as they may [introduce a SQL injection vulnerability](http://stackoverflow.com/a/12118602/3430118)!*
 
 ##### `loc`
 
@@ -376,7 +398,7 @@ Rules:
 Examples:
   * `autocommit=1`: `SET autocommit=1`
   * [`time_zone=%27Europe%2FParis%27`](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html): `SET time_zone='Europe/Paris'`
-  * [`tx_isolation=%27REPEATABLE-READ%27`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `SET tx_isolation='REPEATABLE-READ'`
+  * [`transaction_isolation=%27REPEATABLE-READ%27`](https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_transaction_isolation): `SET transaction_isolation='REPEATABLE-READ'`
 
 
 #### Examples
@@ -432,7 +454,7 @@ user:password@/
 The connection pool is managed by Go's database/sql package. For details on how to configure the size of the pool and how long connections stay in the pool see `*DB.SetMaxOpenConns`, `*DB.SetMaxIdleConns`, and `*DB.SetConnMaxLifetime` in the [database/sql documentation](https://golang.org/pkg/database/sql/). The read, write, and dial timeouts for each individual connection are configured with the DSN parameters [`readTimeout`](#readtimeout), [`writeTimeout`](#writetimeout), and [`timeout`](#timeout), respectively.
 
 ## `ColumnType` Support
-This driver supports the [`ColumnType` interface](https://golang.org/pkg/database/sql/#ColumnType) introduced in Go 1.8, with the exception of [`ColumnType.Length()`](https://golang.org/pkg/database/sql/#ColumnType.Length), which is currently not supported.
+This driver supports the [`ColumnType` interface](https://golang.org/pkg/database/sql/#ColumnType) introduced in Go 1.8, with the exception of [`ColumnType.Length()`](https://golang.org/pkg/database/sql/#ColumnType.Length), which is currently not supported. All Unsigned database type names will be returned `UNSIGNED ` with `INT`, `TINYINT`, `SMALLINT`, `BIGINT`.
 
 ## `context.Context` Support
 Go 1.8 added `database/sql` support for `context.Context`. This driver supports query timeouts and cancellation via contexts.
@@ -445,7 +467,7 @@ For this feature you need direct access to the package. Therefore you must chang
 import "github.com/go-sql-driver/mysql"
 ```
 
-Files must be whitelisted by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the Whitelist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)).
+Files must be explicitly allowed by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the allowlist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)).
 
 To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore.
 
@@ -459,8 +481,6 @@ However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` v
 
 **Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
 
-Alternatively you can use the [`NullTime`](https://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`.
-
 
 ### Unicode support
 Since version 1.5 Go-MySQL-Driver automatically uses the collation ` utf8mb4_general_ci` by default.
@@ -477,7 +497,7 @@ To run the driver tests you may need to adjust the configuration. See the [Testi
 Go-MySQL-Driver is not feature-complete yet. Your help is very appreciated.
 If you want to contribute, you can work on an [open issue](https://github.com/go-sql-driver/mysql/issues?state=open) or review a [pull request](https://github.com/go-sql-driver/mysql/pulls).
 
-See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/CONTRIBUTING.md) for details.
+See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/.github/CONTRIBUTING.md) for details.
 
 ---------------------------------------
 
@@ -498,4 +518,3 @@ Please read the [MPL 2.0 FAQ](https://www.mozilla.org/en-US/MPL/2.0/FAQ/) if you
 You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE).
 
 ![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow")
-
diff --git a/auth.go b/auth.go
index fec7040..a253534 100644
--- a/auth.go
+++ b/auth.go
@@ -15,6 +15,7 @@ import (
 	"crypto/sha256"
 	"crypto/x509"
 	"encoding/pem"
+	"fmt"
 	"sync"
 )
 
@@ -136,10 +137,6 @@ func pwHash(password []byte) (result [2]uint32) {
 
 // Hash password using insecure pre 4.1 method
 func scrambleOldPassword(scramble []byte, password string) []byte {
-	if len(password) == 0 {
-		return nil
-	}
-
 	scramble = scramble[:8]
 
 	hashPw := pwHash([]byte(password))
@@ -247,6 +244,9 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
 		if !mc.cfg.AllowOldPasswords {
 			return nil, ErrOldPassword
 		}
+		if len(mc.cfg.Passwd) == 0 {
+			return nil, nil
+		}
 		// Note: there are edge cases where this should work but doesn't;
 		// this is currently "wontfix":
 		// https://github.com/go-sql-driver/mysql/issues/184
@@ -274,7 +274,9 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
 		if len(mc.cfg.Passwd) == 0 {
 			return []byte{0}, nil
 		}
-		if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
+		// unlike caching_sha2_password, sha256_password does not accept
+		// cleartext password on unix transport.
+		if mc.cfg.tls != nil {
 			// write cleartext auth packet
 			return append([]byte(mc.cfg.Passwd), 0), nil
 		}
@@ -365,14 +367,24 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 							return err
 						}
 						data[4] = cachingSha2PasswordRequestPublicKey
-						mc.writePacket(data)
+						err = mc.writePacket(data)
+						if err != nil {
+							return err
+						}
 
-						// parse public key
 						if data, err = mc.readPacket(); err != nil {
 							return err
 						}
 
-						block, _ := pem.Decode(data[1:])
+						if data[0] != iAuthMoreData {
+							return fmt.Errorf("unexpect resp from server for caching_sha2_password perform full authentication")
+						}
+
+						// parse public key
+						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)
 						if err != nil {
 							return err
@@ -401,6 +413,10 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 			return nil // auth successful
 		default:
 			block, _ := pem.Decode(authData)
+			if block == nil {
+				return fmt.Errorf("no Pem data found, data: %s", authData)
+			}
+
 			pub, err := x509.ParsePKIXPublicKey(block.Bytes)
 			if err != nil {
 				return err
diff --git a/auth_test.go b/auth_test.go
index 1920ef3..3bce7fe 100644
--- a/auth_test.go
+++ b/auth_test.go
@@ -1157,7 +1157,7 @@ func TestAuthSwitchOldPasswordEmpty(t *testing.T) {
 		t.Errorf("got error: %v", err)
 	}
 
-	expectedReply := []byte{1, 0, 0, 3, 0}
+	expectedReply := []byte{0, 0, 0, 3}
 	if !bytes.Equal(conn.written, expectedReply) {
 		t.Errorf("got unexpected data: %v", conn.written)
 	}
@@ -1184,7 +1184,7 @@ func TestOldAuthSwitchPasswordEmpty(t *testing.T) {
 		t.Errorf("got error: %v", err)
 	}
 
-	expectedReply := []byte{1, 0, 0, 3, 0}
+	expectedReply := []byte{0, 0, 0, 3}
 	if !bytes.Equal(conn.written, expectedReply) {
 		t.Errorf("got unexpected data: %v", conn.written)
 	}
diff --git a/benchmark_test.go b/benchmark_test.go
index 3e25a3b..1030ddc 100644
--- a/benchmark_test.go
+++ b/benchmark_test.go
@@ -127,7 +127,8 @@ func BenchmarkExec(b *testing.B) {
 				}
 
 				if _, err := stmt.Exec(); err != nil {
-					b.Fatal(err.Error())
+					b.Logf("stmt.Exec failed: %v", err)
+					b.Fail()
 				}
 			}
 		}()
diff --git a/collations.go b/collations.go
index 8d2b556..326a9f7 100644
--- a/collations.go
+++ b/collations.go
@@ -247,7 +247,7 @@ var collations = map[string]byte{
 	"utf8mb4_0900_ai_ci":       255,
 }
 
-// A blacklist of collations which is unsafe to interpolate parameters.
+// A denylist of collations which is unsafe to interpolate parameters.
 // These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
 var unsafeCollations = map[string]bool{
 	"big5_chinese_ci":        true,
diff --git a/connection.go b/connection.go
index e4bb59e..835f897 100644
--- a/connection.go
+++ b/connection.go
@@ -12,6 +12,7 @@ import (
 	"context"
 	"database/sql"
 	"database/sql/driver"
+	"encoding/json"
 	"io"
 	"net"
 	"strconv"
@@ -46,9 +47,10 @@ type mysqlConn struct {
 
 // Handles parameters set in DSN after the connection is established
 func (mc *mysqlConn) handleParams() (err error) {
+	var cmdSet strings.Builder
 	for param, val := range mc.cfg.Params {
 		switch param {
-		// Charset
+		// Charset: character_set_connection, character_set_client, character_set_results
 		case "charset":
 			charsets := strings.Split(val, ",")
 			for i := range charsets {
@@ -62,12 +64,25 @@ func (mc *mysqlConn) handleParams() (err error) {
 				return
 			}
 
-		// System Vars
+		// Other system vars accumulated in a single SET command
 		default:
-			err = mc.exec("SET " + param + "=" + val + "")
-			if err != nil {
-				return
+			if cmdSet.Len() == 0 {
+				// Heuristic: 29 chars for each other key=value to reduce reallocations
+				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
 		}
 	}
 
@@ -230,47 +245,21 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 			if v.IsZero() {
 				buf = append(buf, "'0000-00-00'"...)
 			} else {
-				v := v.In(mc.cfg.Loc)
-				v = v.Add(time.Nanosecond * 500) // To round under microsecond
-				year := v.Year()
-				year100 := year / 100
-				year1 := year % 100
-				month := v.Month()
-				day := v.Day()
-				hour := v.Hour()
-				minute := v.Minute()
-				second := v.Second()
-				micro := v.Nanosecond() / 1000
-
-				buf = append(buf, []byte{
-					'\'',
-					digits10[year100], digits01[year100],
-					digits10[year1], digits01[year1],
-					'-',
-					digits10[month], digits01[month],
-					'-',
-					digits10[day], digits01[day],
-					' ',
-					digits10[hour], digits01[hour],
-					':',
-					digits10[minute], digits01[minute],
-					':',
-					digits10[second], digits01[second],
-				}...)
-
-				if micro != 0 {
-					micro10000 := micro / 10000
-					micro100 := micro / 100 % 100
-					micro1 := micro % 100
-					buf = append(buf, []byte{
-						'.',
-						digits10[micro10000], digits01[micro10000],
-						digits10[micro100], digits01[micro100],
-						digits10[micro1], digits01[micro1],
-					}...)
+				buf = append(buf, '\'')
+				buf, err = appendDateTime(buf, v.In(mc.cfg.Loc))
+				if err != nil {
+					return "", err
 				}
 				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:
 			if v == nil {
 				buf = append(buf, "NULL"...)
@@ -480,6 +469,10 @@ func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
 
 // 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
 	}
@@ -649,3 +642,9 @@ func (mc *mysqlConn) ResetSession(ctx context.Context) error {
 	mc.reset = true
 	return nil
 }
+
+// IsValid implements driver.Validator interface
+// (From Go 1.15)
+func (mc *mysqlConn) IsValid() bool {
+	return !mc.closed.IsSet()
+}
diff --git a/connection_test.go b/connection_test.go
index 19c17ff..a6d6773 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -11,6 +11,7 @@ package mysql
 import (
 	"context"
 	"database/sql/driver"
+	"encoding/json"
 	"errors"
 	"net"
 	"testing"
@@ -36,6 +37,33 @@ func TestInterpolateParams(t *testing.T) {
 	}
 }
 
+func TestInterpolateParamsJSONRawMessage(t *testing.T) {
+	mc := &mysqlConn{
+		buf:              newBuffer(nil),
+		maxAllowedPacket: maxPacketSize,
+		cfg: &Config{
+			InterpolateParams: true,
+		},
+	}
+
+	buf, err := json.Marshal(struct {
+		Value int `json:"value"`
+	}{Value: 42})
+	if err != nil {
+		t.Errorf("Expected err=nil, got %#v", err)
+		return
+	}
+	q, err := mc.interpolateParams("SELECT ?", []driver.Value{json.RawMessage(buf)})
+	if err != nil {
+		t.Errorf("Expected err=nil, got %#v", err)
+		return
+	}
+	expected := `SELECT '{\"value\":42}'`
+	if q != expected {
+		t.Errorf("Expected: %q\nGot: %q", expected, q)
+	}
+}
+
 func TestInterpolateParamsTooManyPlaceholders(t *testing.T) {
 	mc := &mysqlConn{
 		buf:              newBuffer(nil),
diff --git a/debian/changelog b/debian/changelog
index 432ac75..457c2c6 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,9 +1,10 @@
-golang-github-go-sql-driver-mysql (1.5.0-2) UNRELEASED; urgency=medium
+golang-github-go-sql-driver-mysql (1.6.0+git20220413.1.ad9fa14-1) UNRELEASED; urgency=medium
 
   * Apply multi-arch hints.
     + golang-github-go-sql-driver-mysql-dev: Add Multi-Arch: foreign.
+  * New upstream snapshot.
 
- -- Debian Janitor <janitor@jelmer.uk>  Wed, 28 Oct 2020 23:03:12 -0000
+ -- Debian Janitor <janitor@jelmer.uk>  Sat, 16 Apr 2022 23:39:21 -0000
 
 golang-github-go-sql-driver-mysql (1.5.0-1) unstable; urgency=medium
 
diff --git a/driver_test.go b/driver_test.go
index ace083d..4850498 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -14,6 +14,7 @@ import (
 	"crypto/tls"
 	"database/sql"
 	"database/sql/driver"
+	"encoding/json"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -23,6 +24,7 @@ import (
 	"net/url"
 	"os"
 	"reflect"
+	"runtime"
 	"strings"
 	"sync"
 	"sync/atomic"
@@ -559,6 +561,29 @@ func TestRawBytes(t *testing.T) {
 	})
 }
 
+func TestRawMessage(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		v1 := json.RawMessage("{}")
+		v2 := json.RawMessage("[]")
+		rows := dbt.mustQuery("SELECT ?, ?", v1, v2)
+		defer rows.Close()
+		if rows.Next() {
+			var o1, o2 json.RawMessage
+			if err := rows.Scan(&o1, &o2); err != nil {
+				dbt.Errorf("Got error: %v", err)
+			}
+			if !bytes.Equal(v1, o1) {
+				dbt.Errorf("expected %v, got %v", v1, o1)
+			}
+			if !bytes.Equal(v2, o2) {
+				dbt.Errorf("expected %v, got %v", v2, o2)
+			}
+		} else {
+			dbt.Errorf("no data")
+		}
+	})
+}
+
 type testValuer struct {
 	value string
 }
@@ -1425,11 +1450,11 @@ func TestCharset(t *testing.T) {
 	mustSetCharset("charset=ascii", "ascii")
 
 	// when the first charset is invalid, use the second
-	mustSetCharset("charset=none,utf8", "utf8")
+	mustSetCharset("charset=none,utf8mb4", "utf8mb4")
 
 	// when the first charset is valid, use it
-	mustSetCharset("charset=ascii,utf8", "ascii")
-	mustSetCharset("charset=utf8,ascii", "utf8")
+	mustSetCharset("charset=ascii,utf8mb4", "ascii")
+	mustSetCharset("charset=utf8mb4,ascii", "utf8mb4")
 }
 
 func TestFailingCharset(t *testing.T) {
@@ -1454,7 +1479,7 @@ func TestCollation(t *testing.T) {
 		defaultCollation, // driver default
 		"latin1_general_ci",
 		"binary",
-		"utf8_unicode_ci",
+		"utf8mb4_unicode_ci",
 		"cp1257_bin",
 	}
 
@@ -1782,6 +1807,14 @@ func TestConcurrent(t *testing.T) {
 	}
 
 	runTests(t, dsn, func(dbt *DBTest) {
+		var version string
+		if err := dbt.db.QueryRow("SELECT @@version").Scan(&version); err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+		if strings.Contains(strings.ToLower(version), "mariadb") {
+			t.Skip(`TODO: "fix commands out of sync. Did you run multiple statements at once?" on MariaDB`)
+		}
+
 		var max int
 		err := dbt.db.QueryRow("SELECT @@max_connections").Scan(&max)
 		if err != nil {
@@ -2581,10 +2614,19 @@ func TestContextCancelStmtQuery(t *testing.T) {
 }
 
 func TestContextCancelBegin(t *testing.T) {
+	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
+		t.Skip(`FIXME: it sometime fails with "expected driver.ErrBadConn, got sql: connection is already closed" on windows and macOS`)
+	}
+
 	runTests(t, dsn, func(dbt *DBTest) {
 		dbt.mustExec("CREATE TABLE test (v INTEGER)")
 		ctx, cancel := context.WithCancel(context.Background())
-		tx, err := dbt.db.BeginTx(ctx, nil)
+		conn, err := dbt.db.Conn(ctx)
+		if err != nil {
+			dbt.Fatal(err)
+		}
+		defer conn.Close()
+		tx, err := conn.BeginTx(ctx, nil)
 		if err != nil {
 			dbt.Fatal(err)
 		}
@@ -2614,7 +2656,17 @@ func TestContextCancelBegin(t *testing.T) {
 			dbt.Errorf("expected sql.ErrTxDone or context.Canceled, got %v", err)
 		}
 
-		// Context is canceled, so cannot begin a transaction.
+		// The connection is now in an inoperable state - so performing other
+		// operations should fail with ErrBadConn
+		// Important to exercise isolation level too - it runs SET TRANSACTION ISOLATION
+		// LEVEL XXX first, which needs to return ErrBadConn if the connection's context
+		// is cancelled
+		_, err = conn.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelReadCommitted})
+		if err != driver.ErrBadConn {
+			dbt.Errorf("expected driver.ErrBadConn, got %v", err)
+		}
+
+		// cannot begin a transaction (on a different conn) with a canceled context
 		if _, err := dbt.db.BeginTx(ctx, nil); err != context.Canceled {
 			dbt.Errorf("expected context.Canceled, got %v", err)
 		}
@@ -2719,13 +2771,13 @@ func TestRowsColumnTypes(t *testing.T) {
 	nfNULL := sql.NullFloat64{Float64: 0.0, Valid: false}
 	nf0 := sql.NullFloat64{Float64: 0.0, Valid: true}
 	nf1337 := sql.NullFloat64{Float64: 13.37, Valid: true}
-	nt0 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC), Valid: true}
-	nt1 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 100000000, time.UTC), Valid: true}
-	nt2 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 110000000, time.UTC), Valid: true}
-	nt6 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 111111000, time.UTC), Valid: true}
-	nd1 := NullTime{Time: time.Date(2006, 01, 02, 0, 0, 0, 0, time.UTC), Valid: true}
-	nd2 := NullTime{Time: time.Date(2006, 03, 04, 0, 0, 0, 0, time.UTC), Valid: true}
-	ndNULL := NullTime{Time: time.Time{}, Valid: false}
+	nt0 := sql.NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC), Valid: true}
+	nt1 := sql.NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 100000000, time.UTC), Valid: true}
+	nt2 := sql.NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 110000000, time.UTC), Valid: true}
+	nt6 := sql.NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 111111000, time.UTC), Valid: true}
+	nd1 := sql.NullTime{Time: time.Date(2006, 01, 02, 0, 0, 0, 0, time.UTC), Valid: true}
+	nd2 := sql.NullTime{Time: time.Date(2006, 03, 04, 0, 0, 0, 0, time.UTC), Valid: true}
+	ndNULL := sql.NullTime{Time: time.Time{}, Valid: false}
 	rbNULL := sql.RawBytes(nil)
 	rb0 := sql.RawBytes("0")
 	rb42 := sql.RawBytes("42")
@@ -2756,10 +2808,10 @@ func TestRowsColumnTypes(t *testing.T) {
 		{"mediumintnull", "MEDIUMINT", "MEDIUMINT", scanTypeNullInt, true, 0, 0, [3]string{"0", "42", "NULL"}, [3]interface{}{ni0, ni42, niNULL}},
 		{"bigint", "BIGINT NOT NULL", "BIGINT", scanTypeInt64, false, 0, 0, [3]string{"0", "65535", "-42"}, [3]interface{}{int64(0), int64(65535), int64(-42)}},
 		{"bigintnull", "BIGINT", "BIGINT", scanTypeNullInt, true, 0, 0, [3]string{"NULL", "1", "42"}, [3]interface{}{niNULL, ni1, ni42}},
-		{"tinyuint", "TINYINT UNSIGNED NOT NULL", "TINYINT", scanTypeUint8, false, 0, 0, [3]string{"0", "255", "42"}, [3]interface{}{uint8(0), uint8(255), uint8(42)}},
-		{"smalluint", "SMALLINT UNSIGNED NOT NULL", "SMALLINT", scanTypeUint16, false, 0, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint16(0), uint16(65535), uint16(42)}},
-		{"biguint", "BIGINT UNSIGNED NOT NULL", "BIGINT", scanTypeUint64, false, 0, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint64(0), uint64(65535), uint64(42)}},
-		{"uint13", "INT(13) UNSIGNED NOT NULL", "INT", scanTypeUint32, false, 0, 0, [3]string{"0", "1337", "42"}, [3]interface{}{uint32(0), uint32(1337), uint32(42)}},
+		{"tinyuint", "TINYINT UNSIGNED NOT NULL", "UNSIGNED TINYINT", scanTypeUint8, false, 0, 0, [3]string{"0", "255", "42"}, [3]interface{}{uint8(0), uint8(255), uint8(42)}},
+		{"smalluint", "SMALLINT UNSIGNED NOT NULL", "UNSIGNED SMALLINT", scanTypeUint16, false, 0, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint16(0), uint16(65535), uint16(42)}},
+		{"biguint", "BIGINT UNSIGNED NOT NULL", "UNSIGNED BIGINT", scanTypeUint64, false, 0, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint64(0), uint64(65535), uint64(42)}},
+		{"uint13", "INT(13) UNSIGNED NOT NULL", "UNSIGNED INT", scanTypeUint32, false, 0, 0, [3]string{"0", "1337", "42"}, [3]interface{}{uint32(0), uint32(1337), uint32(42)}},
 		{"float", "FLOAT NOT NULL", "FLOAT", scanTypeFloat32, false, math.MaxInt64, math.MaxInt64, [3]string{"0", "42", "13.37"}, [3]interface{}{float32(0), float32(42), float32(13.37)}},
 		{"floatnull", "FLOAT", "FLOAT", scanTypeNullFloat, true, math.MaxInt64, math.MaxInt64, [3]string{"0", "NULL", "13.37"}, [3]interface{}{nf0, nfNULL, nf1337}},
 		{"float74null", "FLOAT(7,4)", "FLOAT", scanTypeNullFloat, true, math.MaxInt64, 4, [3]string{"0", "NULL", "13.37"}, [3]interface{}{nf0, nfNULL, nf1337}},
@@ -2805,131 +2857,125 @@ func TestRowsColumnTypes(t *testing.T) {
 	values2 = values2[:len(values2)-2]
 	values3 = values3[:len(values3)-2]
 
-	dsns := []string{
-		dsn + "&parseTime=true",
-		dsn + "&parseTime=false",
-	}
-	for _, testdsn := range dsns {
-		runTests(t, testdsn, func(dbt *DBTest) {
-			dbt.mustExec("CREATE TABLE test (" + schema + ")")
-			dbt.mustExec("INSERT INTO test VALUES (" + values1 + "), (" + values2 + "), (" + values3 + ")")
+	runTests(t, dsn+"&parseTime=true", func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (" + schema + ")")
+		dbt.mustExec("INSERT INTO test VALUES (" + values1 + "), (" + values2 + "), (" + values3 + ")")
 
-			rows, err := dbt.db.Query("SELECT * FROM test")
-			if err != nil {
-				t.Fatalf("Query: %v", err)
-			}
+		rows, err := dbt.db.Query("SELECT * FROM test")
+		if err != nil {
+			t.Fatalf("Query: %v", err)
+		}
 
-			tt, err := rows.ColumnTypes()
-			if err != nil {
-				t.Fatalf("ColumnTypes: %v", err)
-			}
+		tt, err := rows.ColumnTypes()
+		if err != nil {
+			t.Fatalf("ColumnTypes: %v", err)
+		}
 
-			if len(tt) != len(columns) {
-				t.Fatalf("unexpected number of columns: expected %d, got %d", len(columns), len(tt))
-			}
+		if len(tt) != len(columns) {
+			t.Fatalf("unexpected number of columns: expected %d, got %d", len(columns), len(tt))
+		}
 
-			types := make([]reflect.Type, len(tt))
-			for i, tp := range tt {
-				column := columns[i]
+		types := make([]reflect.Type, len(tt))
+		for i, tp := range tt {
+			column := columns[i]
 
-				// Name
-				name := tp.Name()
-				if name != column.name {
-					t.Errorf("column name mismatch %s != %s", name, column.name)
-					continue
-				}
+			// Name
+			name := tp.Name()
+			if name != column.name {
+				t.Errorf("column name mismatch %s != %s", name, column.name)
+				continue
+			}
 
-				// DatabaseTypeName
-				databaseTypeName := tp.DatabaseTypeName()
-				if databaseTypeName != column.databaseTypeName {
-					t.Errorf("databasetypename name mismatch for column %q: %s != %s", name, databaseTypeName, column.databaseTypeName)
-					continue
-				}
+			// DatabaseTypeName
+			databaseTypeName := tp.DatabaseTypeName()
+			if databaseTypeName != column.databaseTypeName {
+				t.Errorf("databasetypename name mismatch for column %q: %s != %s", name, databaseTypeName, column.databaseTypeName)
+				continue
+			}
 
-				// ScanType
-				scanType := tp.ScanType()
-				if scanType != column.scanType {
-					if scanType == nil {
-						t.Errorf("scantype is null for column %q", name)
-					} else {
-						t.Errorf("scantype mismatch for column %q: %s != %s", name, scanType.Name(), column.scanType.Name())
-					}
-					continue
+			// ScanType
+			scanType := tp.ScanType()
+			if scanType != column.scanType {
+				if scanType == nil {
+					t.Errorf("scantype is null for column %q", name)
+				} else {
+					t.Errorf("scantype mismatch for column %q: %s != %s", name, scanType.Name(), column.scanType.Name())
 				}
-				types[i] = scanType
-
-				// Nullable
-				nullable, ok := tp.Nullable()
+				continue
+			}
+			types[i] = scanType
+
+			// Nullable
+			nullable, ok := tp.Nullable()
+			if !ok {
+				t.Errorf("nullable not ok %q", name)
+				continue
+			}
+			if nullable != column.nullable {
+				t.Errorf("nullable mismatch for column %q: %t != %t", name, nullable, column.nullable)
+			}
+
+			// Length
+			// length, ok := tp.Length()
+			// if length != column.length {
+			// 	if !ok {
+			// 		t.Errorf("length not ok for column %q", name)
+			// 	} else {
+			// 		t.Errorf("length mismatch for column %q: %d != %d", name, length, column.length)
+			// 	}
+			// 	continue
+			// }
+
+			// Precision and Scale
+			precision, scale, ok := tp.DecimalSize()
+			if precision != column.precision {
 				if !ok {
-					t.Errorf("nullable not ok %q", name)
-					continue
+					t.Errorf("precision not ok for column %q", name)
+				} else {
+					t.Errorf("precision mismatch for column %q: %d != %d", name, precision, column.precision)
 				}
-				if nullable != column.nullable {
-					t.Errorf("nullable mismatch for column %q: %t != %t", name, nullable, column.nullable)
-				}
-
-				// Length
-				// length, ok := tp.Length()
-				// if length != column.length {
-				// 	if !ok {
-				// 		t.Errorf("length not ok for column %q", name)
-				// 	} else {
-				// 		t.Errorf("length mismatch for column %q: %d != %d", name, length, column.length)
-				// 	}
-				// 	continue
-				// }
-
-				// Precision and Scale
-				precision, scale, ok := tp.DecimalSize()
-				if precision != column.precision {
-					if !ok {
-						t.Errorf("precision not ok for column %q", name)
-					} else {
-						t.Errorf("precision mismatch for column %q: %d != %d", name, precision, column.precision)
-					}
-					continue
-				}
-				if scale != column.scale {
-					if !ok {
-						t.Errorf("scale not ok for column %q", name)
-					} else {
-						t.Errorf("scale mismatch for column %q: %d != %d", name, scale, column.scale)
-					}
-					continue
+				continue
+			}
+			if scale != column.scale {
+				if !ok {
+					t.Errorf("scale not ok for column %q", name)
+				} else {
+					t.Errorf("scale mismatch for column %q: %d != %d", name, scale, column.scale)
 				}
+				continue
 			}
+		}
 
-			values := make([]interface{}, len(tt))
-			for i := range values {
-				values[i] = reflect.New(types[i]).Interface()
+		values := make([]interface{}, len(tt))
+		for i := range values {
+			values[i] = reflect.New(types[i]).Interface()
+		}
+		i := 0
+		for rows.Next() {
+			err = rows.Scan(values...)
+			if err != nil {
+				t.Fatalf("failed to scan values in %v", err)
 			}
-			i := 0
-			for rows.Next() {
-				err = rows.Scan(values...)
-				if err != nil {
-					t.Fatalf("failed to scan values in %v", err)
-				}
-				for j := range values {
-					value := reflect.ValueOf(values[j]).Elem().Interface()
-					if !reflect.DeepEqual(value, columns[j].valuesOut[i]) {
-						if columns[j].scanType == scanTypeRawBytes {
-							t.Errorf("row %d, column %d: %v != %v", i, j, string(value.(sql.RawBytes)), string(columns[j].valuesOut[i].(sql.RawBytes)))
-						} else {
-							t.Errorf("row %d, column %d: %v != %v", i, j, value, columns[j].valuesOut[i])
-						}
+			for j := range values {
+				value := reflect.ValueOf(values[j]).Elem().Interface()
+				if !reflect.DeepEqual(value, columns[j].valuesOut[i]) {
+					if columns[j].scanType == scanTypeRawBytes {
+						t.Errorf("row %d, column %d: %v != %v", i, j, string(value.(sql.RawBytes)), string(columns[j].valuesOut[i].(sql.RawBytes)))
+					} else {
+						t.Errorf("row %d, column %d: %v != %v", i, j, value, columns[j].valuesOut[i])
 					}
 				}
-				i++
-			}
-			if i != 3 {
-				t.Errorf("expected 3 rows, got %d", i)
 			}
+			i++
+		}
+		if i != 3 {
+			t.Errorf("expected 3 rows, got %d", i)
+		}
 
-			if err := rows.Close(); err != nil {
-				t.Errorf("error closing rows: %s", err)
-			}
-		})
-	}
+		if err := rows.Close(); err != nil {
+			t.Errorf("error closing rows: %s", err)
+		}
+	})
 }
 
 func TestValuerWithValueReceiverGivenNilValue(t *testing.T) {
diff --git a/dsn.go b/dsn.go
index 75c8c24..a306d66 100644
--- a/dsn.go
+++ b/dsn.go
@@ -375,7 +375,7 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 
 		// cfg params
 		switch value := param[1]; param[0] {
-		// Disable INFILE whitelist / enable all files
+		// Disable INFILE allowlist / enable all files
 		case "allowAllFiles":
 			var isBool bool
 			cfg.AllowAllFiles, isBool = readBool(value)
@@ -426,7 +426,6 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 		// Collation
 		case "collation":
 			cfg.Collation = value
-			break
 
 		case "columnsWithAlias":
 			var isBool bool
diff --git a/dsn_test.go b/dsn_test.go
index 89815b3..fc6eea9 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -92,13 +92,14 @@ func TestDSNParser(t *testing.T) {
 
 func TestDSNParserInvalid(t *testing.T) {
 	var invalidDSNs = []string{
-		"@net(addr/",                  // no closing brace
-		"@tcp(/",                      // no closing brace
-		"tcp(/",                       // no closing brace
-		"(/",                          // no closing brace
-		"net(addr)//",                 // unescaped
-		"User:pass@tcp(1.2.3.4:3306)", // no trailing slash
-		"net()/",                      // unknown default addr
+		"@net(addr/",                            // no closing brace
+		"@tcp(/",                                // no closing brace
+		"tcp(/",                                 // no closing brace
+		"(/",                                    // no closing brace
+		"net(addr)//",                           // unescaped
+		"User:pass@tcp(1.2.3.4:3306)",           // no trailing slash
+		"net()/",                                // unknown default addr
+		"user:pass@tcp(127.0.0.1:3306)/db/name", // invalid dbname
 		//"/dbname?arg=/some/unescaped/path",
 	}
 
diff --git a/errors.go b/errors.go
index 760782f..7c037e7 100644
--- a/errors.go
+++ b/errors.go
@@ -56,10 +56,22 @@ func SetLogger(logger Logger) error {
 
 // MySQLError is an error type which represents a single MySQL error
 type MySQLError struct {
-	Number  uint16
-	Message string
+	Number   uint16
+	SQLState [5]byte
+	Message  string
 }
 
 func (me *MySQLError) Error() string {
+	if me.SQLState != [5]byte{} {
+		return fmt.Sprintf("Error %d (%s): %s", me.Number, me.SQLState, me.Message)
+	}
+
 	return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
 }
+
+func (me *MySQLError) Is(err error) bool {
+	if merr, ok := err.(*MySQLError); ok {
+		return merr.Number == me.Number
+	}
+	return false
+}
diff --git a/errors_test.go b/errors_test.go
index 96f9126..43213f9 100644
--- a/errors_test.go
+++ b/errors_test.go
@@ -10,6 +10,7 @@ package mysql
 
 import (
 	"bytes"
+	"errors"
 	"log"
 	"testing"
 )
@@ -40,3 +41,21 @@ func TestErrorsStrictIgnoreNotes(t *testing.T) {
 		dbt.mustExec("DROP TABLE IF EXISTS does_not_exist")
 	})
 }
+
+func TestMySQLErrIs(t *testing.T) {
+	infraErr := &MySQLError{Number: 1234, Message: "the server is on fire"}
+	otherInfraErr := &MySQLError{Number: 1234, Message: "the datacenter is flooded"}
+	if !errors.Is(infraErr, otherInfraErr) {
+		t.Errorf("expected errors to be the same: %+v %+v", infraErr, otherInfraErr)
+	}
+
+	differentCodeErr := &MySQLError{Number: 5678, Message: "the server is on fire"}
+	if errors.Is(infraErr, differentCodeErr) {
+		t.Fatalf("expected errors to be different: %+v %+v", infraErr, differentCodeErr)
+	}
+
+	nonMysqlErr := errors.New("not a mysql error")
+	if errors.Is(infraErr, nonMysqlErr) {
+		t.Fatalf("expected errors to be different: %+v %+v", infraErr, nonMysqlErr)
+	}
+}
diff --git a/fields.go b/fields.go
index e1e2ece..e0654a8 100644
--- a/fields.go
+++ b/fields.go
@@ -41,6 +41,9 @@ func (mf *mysqlField) typeDatabaseName() string {
 	case fieldTypeJSON:
 		return "JSON"
 	case fieldTypeLong:
+		if mf.flags&flagUnsigned != 0 {
+			return "UNSIGNED INT"
+		}
 		return "INT"
 	case fieldTypeLongBLOB:
 		if mf.charSet != collations[binaryCollation] {
@@ -48,6 +51,9 @@ func (mf *mysqlField) typeDatabaseName() string {
 		}
 		return "LONGBLOB"
 	case fieldTypeLongLong:
+		if mf.flags&flagUnsigned != 0 {
+			return "UNSIGNED BIGINT"
+		}
 		return "BIGINT"
 	case fieldTypeMediumBLOB:
 		if mf.charSet != collations[binaryCollation] {
@@ -63,6 +69,9 @@ func (mf *mysqlField) typeDatabaseName() string {
 	case fieldTypeSet:
 		return "SET"
 	case fieldTypeShort:
+		if mf.flags&flagUnsigned != 0 {
+			return "UNSIGNED SMALLINT"
+		}
 		return "SMALLINT"
 	case fieldTypeString:
 		if mf.charSet == collations[binaryCollation] {
@@ -74,6 +83,9 @@ func (mf *mysqlField) typeDatabaseName() string {
 	case fieldTypeTimestamp:
 		return "TIMESTAMP"
 	case fieldTypeTiny:
+		if mf.flags&flagUnsigned != 0 {
+			return "UNSIGNED TINYINT"
+		}
 		return "TINYINT"
 	case fieldTypeTinyBLOB:
 		if mf.charSet != collations[binaryCollation] {
@@ -106,7 +118,7 @@ var (
 	scanTypeInt64     = reflect.TypeOf(int64(0))
 	scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
 	scanTypeNullInt   = reflect.TypeOf(sql.NullInt64{})
-	scanTypeNullTime  = reflect.TypeOf(NullTime{})
+	scanTypeNullTime  = reflect.TypeOf(sql.NullTime{})
 	scanTypeUint8     = reflect.TypeOf(uint8(0))
 	scanTypeUint16    = reflect.TypeOf(uint16(0))
 	scanTypeUint32    = reflect.TypeOf(uint32(0))
diff --git a/fuzz.go b/fuzz.go
new file mode 100644
index 0000000..fa75adf
--- /dev/null
+++ b/fuzz.go
@@ -0,0 +1,24 @@
+// 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
+}
diff --git a/go.mod b/go.mod
index fffbf6a..2511104 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
 module github.com/go-sql-driver/mysql
 
-go 1.10
+go 1.13
diff --git a/infile.go b/infile.go
index 273cb0b..e6323ae 100644
--- a/infile.go
+++ b/infile.go
@@ -23,7 +23,7 @@ var (
 	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>".
 // Alternatively you can allow the use of all local files with
 // the DSN parameter 'allowAllFiles=true'
@@ -45,7 +45,7 @@ func RegisterLocalFile(filePath string) {
 	fileRegisterLock.Unlock()
 }
 
-// DeregisterLocalFile removes the given filepath from the whitelist.
+// DeregisterLocalFile removes the given filepath from the allowlist.
 func DeregisterLocalFile(filePath string) {
 	fileRegisterLock.Lock()
 	delete(fileRegister, strings.Trim(filePath, `"`))
@@ -93,10 +93,12 @@ func deferredClose(err *error, closer io.Closer) {
 	}
 }
 
+const defaultPacketSize = 16 * 1024 // 16KB is small enough for disk readahead and large enough for TCP
+
 func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
 	var rdr io.Reader
 	var data []byte
-	packetSize := 16 * 1024 // 16KB is small enough for disk readahead and large enough for TCP
+	packetSize := defaultPacketSize
 	if mc.maxWriteSize < packetSize {
 		packetSize = mc.maxWriteSize
 	}
diff --git a/nulltime.go b/nulltime.go
index afa8a89..17af92d 100644
--- a/nulltime.go
+++ b/nulltime.go
@@ -9,11 +9,32 @@
 package mysql
 
 import (
+	"database/sql"
 	"database/sql/driver"
 	"fmt"
 	"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
+//
+// 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
+
 // Scan implements the Scanner interface.
 // The value type must be time.Time or string / []byte (formatted time-string),
 // otherwise Scan fails.
@@ -28,11 +49,11 @@ func (nt *NullTime) Scan(value interface{}) (err error) {
 		nt.Time, nt.Valid = v, true
 		return
 	case []byte:
-		nt.Time, err = parseDateTime(string(v), time.UTC)
+		nt.Time, err = parseDateTime(v, time.UTC)
 		nt.Valid = (err == nil)
 		return
 	case string:
-		nt.Time, err = parseDateTime(v, time.UTC)
+		nt.Time, err = parseDateTime([]byte(v), time.UTC)
 		nt.Valid = (err == nil)
 		return
 	}
diff --git a/nulltime_go113.go b/nulltime_go113.go
deleted file mode 100644
index c392594..0000000
--- a/nulltime_go113.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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
-type NullTime sql.NullTime
diff --git a/nulltime_legacy.go b/nulltime_legacy.go
deleted file mode 100644
index 86d159d..0000000
--- a/nulltime_legacy.go
+++ /dev/null
@@ -1,34 +0,0 @@
-// 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
-}
diff --git a/packets.go b/packets.go
index 82ad7a2..003584c 100644
--- a/packets.go
+++ b/packets.go
@@ -13,6 +13,7 @@ import (
 	"crypto/tls"
 	"database/sql/driver"
 	"encoding/binary"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"io"
@@ -109,14 +110,13 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 			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 mc.cfg.CheckConnLiveness {
+			if mc.cfg.ReadTimeout != 0 {
+				err = conn.SetReadDeadline(time.Now().Add(mc.cfg.ReadTimeout))
+			}
+			if err == nil {
+				err = connCheck(conn)
+			}
 		}
 		if err != nil {
 			errLog.Print("closing bad idle connection: ", err)
@@ -348,6 +348,12 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 		return errors.New("unknown collation")
 	}
 
+	// Filler [23 bytes] (all 0x00)
+	pos := 13
+	for ; pos < 13+23; pos++ {
+		data[pos] = 0
+	}
+
 	// SSL Connection Request Packet
 	// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest
 	if mc.cfg.tls != nil {
@@ -366,12 +372,6 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 		mc.buf.nc = tlsConn
 	}
 
-	// Filler [23 bytes] (all 0x00)
-	pos := 13
-	for ; pos < 13+23; pos++ {
-		data[pos] = 0
-	}
-
 	// User [null terminated string]
 	if len(mc.cfg.User) > 0 {
 		pos += copy(data[pos:], mc.cfg.User)
@@ -587,19 +587,20 @@ func (mc *mysqlConn) handleErrorPacket(data []byte) error {
 		return driver.ErrBadConn
 	}
 
+	me := &MySQLError{Number: errno}
+
 	pos := 3
 
 	// SQL State [optional: # + 5bytes string]
 	if data[3] == 0x23 {
-		//sqlstate := string(data[4 : 4+5])
+		copy(me.SQLState[:], data[4:4+5])
 		pos = 9
 	}
 
 	// Error Message [string]
-	return &MySQLError{
-		Number:  errno,
-		Message: string(data[pos:]),
-	}
+	me.Message = string(data[pos:])
+
+	return me
 }
 
 func readStatus(b []byte) statusFlag {
@@ -760,40 +761,40 @@ func (rows *textRows) readRow(dest []driver.Value) error {
 	}
 
 	// RowSet Packet
-	var n int
-	var isNull bool
-	pos := 0
+	var (
+		n      int
+		isNull bool
+		pos    int = 0
+	)
 
 	for i := range dest {
 		// Read bytes and convert to string
 		dest[i], isNull, n, err = readLengthEncodedString(data[pos:])
 		pos += n
-		if err == nil {
-			if !isNull {
-				if !mc.parseTime {
-					continue
-				} else {
-					switch rows.rs.columns[i].fieldType {
-					case fieldTypeTimestamp, fieldTypeDateTime,
-						fieldTypeDate, fieldTypeNewDate:
-						dest[i], err = parseDateTime(
-							string(dest[i].([]byte)),
-							mc.cfg.Loc,
-						)
-						if err == nil {
-							continue
-						}
-					default:
-						continue
-					}
-				}
 
-			} else {
-				dest[i] = nil
-				continue
+		if err != nil {
+			return err
+		}
+
+		if isNull {
+			dest[i] = nil
+			continue
+		}
+
+		if !mc.parseTime {
+			continue
+		}
+
+		// Parse time field
+		switch rows.rs.columns[i].fieldType {
+		case fieldTypeTimestamp,
+			fieldTypeDateTime,
+			fieldTypeDate,
+			fieldTypeNewDate:
+			if dest[i], err = parseDateTime(dest[i].([]byte), mc.cfg.Loc); err != nil {
+				return err
 			}
 		}
-		return err // err != nil
 	}
 
 	return nil
@@ -1003,6 +1004,9 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 				continue
 			}
 
+			if v, ok := arg.(json.RawMessage); ok {
+				arg = []byte(v)
+			}
 			// cache types and values
 			switch v := arg.(type) {
 			case int64:
@@ -1112,7 +1116,10 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 				if v.IsZero() {
 					b = append(b, "0000-00-00"...)
 				} 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,
diff --git a/statement.go b/statement.go
index f7e3709..18a3ae4 100644
--- a/statement.go
+++ b/statement.go
@@ -10,6 +10,7 @@ package mysql
 
 import (
 	"database/sql/driver"
+	"encoding/json"
 	"fmt"
 	"io"
 	"reflect"
@@ -43,6 +44,11 @@ func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
 	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) {
 	if stmt.mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
@@ -129,6 +135,8 @@ func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
 	return rows, err
 }
 
+var jsonType = reflect.TypeOf(json.RawMessage{})
+
 type converter struct{}
 
 // ConvertValue mirrors the reference/default converter in database/sql/driver
@@ -146,12 +154,17 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
 		if err != nil {
 			return nil, err
 		}
-		if !driver.IsValue(sv) {
-			return nil, fmt.Errorf("non-Value type %T returned from Value", sv)
+		if driver.IsValue(sv) {
+			return sv, nil
 		}
-		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 nil, fmt.Errorf("non-Value type %T returned from Value", sv)
 	}
-
 	rv := reflect.ValueOf(v)
 	switch rv.Kind() {
 	case reflect.Ptr:
@@ -170,11 +183,14 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
 	case reflect.Bool:
 		return rv.Bool(), nil
 	case reflect.Slice:
-		ek := rv.Type().Elem().Kind()
-		if ek == reflect.Uint8 {
+		switch t := rv.Type(); {
+		case t == jsonType:
+			return v, nil
+		case t.Elem().Kind() == reflect.Uint8:
 			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:
 		return rv.String(), nil
 	}
diff --git a/statement_test.go b/statement_test.go
index 4b9914f..2563ece 100644
--- a/statement_test.go
+++ b/statement_test.go
@@ -10,6 +10,8 @@ package mysql
 
 import (
 	"bytes"
+	"database/sql/driver"
+	"encoding/json"
 	"testing"
 )
 
@@ -34,7 +36,7 @@ func TestConvertDerivedByteSlice(t *testing.T) {
 		t.Fatal("Byte slice not convertible", err)
 	}
 
-	if bytes.Compare(output.([]byte), []byte("value")) != 0 {
+	if !bytes.Equal(output.([]byte), []byte("value")) {
 		t.Fatalf("Byte slice not converted, got %#v %T", output, output)
 	}
 }
@@ -95,6 +97,14 @@ func TestConvertSignedIntegers(t *testing.T) {
 	}
 }
 
+type myUint64 struct {
+	value uint64
+}
+
+func (u myUint64) Value() (driver.Value, error) {
+	return u.value, nil
+}
+
 func TestConvertUnsignedIntegers(t *testing.T) {
 	values := []interface{}{
 		uint8(42),
@@ -102,6 +112,7 @@ func TestConvertUnsignedIntegers(t *testing.T) {
 		uint32(42),
 		uint64(42),
 		uint(42),
+		myUint64{uint64(42)},
 	}
 
 	for _, value := range values {
@@ -124,3 +135,17 @@ func TestConvertUnsignedIntegers(t *testing.T) {
 		t.Fatalf("uint64 high-bit converted, got %#v %T", output, output)
 	}
 }
+
+func TestConvertJSON(t *testing.T) {
+	raw := json.RawMessage("{}")
+
+	out, err := converter{}.ConvertValue(raw)
+
+	if err != nil {
+		t.Fatal("json.RawMessage was failed in convert", err)
+	}
+
+	if _, ok := out.(json.RawMessage); !ok {
+		t.Fatalf("json.RawMessage converted, got %#v %T", out, out)
+	}
+}
diff --git a/utils.go b/utils.go
index 9552e80..5a024aa 100644
--- a/utils.go
+++ b/utils.go
@@ -106,27 +106,136 @@ func readBool(input string) (value bool, valid bool) {
 *                           Time related utils                                *
 ******************************************************************************/
 
-func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
-	base := "0000-00-00 00:00:00.0000000"
-	switch len(str) {
+func parseDateTime(b []byte, loc *time.Location) (time.Time, error) {
+	const base = "0000-00-00 00:00:00.000000"
+	switch len(b) {
 	case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
-		if str == base[:len(str)] {
-			return
+		if string(b) == base[:len(b)] {
+			return time.Time{}, nil
 		}
-		t, err = time.Parse(timeFormat[:len(str)], str)
+
+		year, err := parseByteYear(b)
+		if err != nil {
+			return time.Time{}, err
+		}
+		if year <= 0 {
+			year = 1
+		}
+
+		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)
+
+		if b[7] != '-' {
+			return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[7])
+		}
+
+		day, err := parseByte2Digits(b[8], b[9])
+		if err != nil {
+			return time.Time{}, err
+		}
+		if day <= 0 {
+			day = 1
+		}
+		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:
-		err = fmt.Errorf("invalid time string: %s", str)
-		return
+		return time.Time{}, fmt.Errorf("invalid time bytes: %s", b)
 	}
+}
 
-	// Adjust location
-	if err == nil && loc != time.UTC {
-		y, mo, d := t.Date()
-		h, mi, s := t.Clock()
-		t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil
+func parseByteYear(b []byte) (int, error) {
+	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 /= 10
 	}
+	return year, nil
+}
 
-	return
+func parseByte2Digits(b1, b2 byte) (int, error) {
+	d1, err := bToi(b1)
+	if err != nil {
+		return 0, err
+	}
+	d2, err := bToi(b2)
+	if err != nil {
+		return 0, err
+	}
+	return d1*10 + d2, nil
+}
+
+func parseByteNanoSec(b []byte) (int, error) {
+	ns, digit := 0, 100000 // max is 6-digits
+	for i := 0; i < len(b); i++ {
+		v, err := bToi(b[i])
+		if err != nil {
+			return 0, err
+		}
+		ns += v * digit
+		digit /= 10
+	}
+	// nanoseconds has 10-digits. (needs to scale digits)
+	// 10 - 6 = 4, so we have to multiple 1000.
+	return ns * 1000, nil
+}
+
+func bToi(b byte) (int, error) {
+	if b < '0' || b > '9' {
+		return 0, errors.New("not [0-9]")
+	}
+	return int(b - '0'), nil
 }
 
 func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) {
@@ -167,6 +276,64 @@ func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Va
 	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
 // if the DATE or DATETIME has the zero value.
 // It must never be changed.
@@ -375,7 +542,7 @@ func stringToInt(b []byte) int {
 	return val
 }
 
-// returns the string read as a bytes slice, wheter the value is NULL,
+// returns the string read as a bytes slice, whether the value is NULL,
 // the number of bytes read and an error, in case the string is longer than
 // the input slice
 func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
@@ -485,32 +652,32 @@ func escapeBytesBackslash(buf, v []byte) []byte {
 	for _, c := range v {
 		switch c {
 		case '\x00':
-			buf[pos] = '\\'
 			buf[pos+1] = '0'
+			buf[pos] = '\\'
 			pos += 2
 		case '\n':
-			buf[pos] = '\\'
 			buf[pos+1] = 'n'
+			buf[pos] = '\\'
 			pos += 2
 		case '\r':
-			buf[pos] = '\\'
 			buf[pos+1] = 'r'
+			buf[pos] = '\\'
 			pos += 2
 		case '\x1a':
-			buf[pos] = '\\'
 			buf[pos+1] = 'Z'
+			buf[pos] = '\\'
 			pos += 2
 		case '\'':
-			buf[pos] = '\\'
 			buf[pos+1] = '\''
+			buf[pos] = '\\'
 			pos += 2
 		case '"':
-			buf[pos] = '\\'
 			buf[pos+1] = '"'
+			buf[pos] = '\\'
 			pos += 2
 		case '\\':
-			buf[pos] = '\\'
 			buf[pos+1] = '\\'
+			buf[pos] = '\\'
 			pos += 2
 		default:
 			buf[pos] = c
@@ -530,32 +697,32 @@ func escapeStringBackslash(buf []byte, v string) []byte {
 		c := v[i]
 		switch c {
 		case '\x00':
-			buf[pos] = '\\'
 			buf[pos+1] = '0'
+			buf[pos] = '\\'
 			pos += 2
 		case '\n':
-			buf[pos] = '\\'
 			buf[pos+1] = 'n'
+			buf[pos] = '\\'
 			pos += 2
 		case '\r':
-			buf[pos] = '\\'
 			buf[pos+1] = 'r'
+			buf[pos] = '\\'
 			pos += 2
 		case '\x1a':
-			buf[pos] = '\\'
 			buf[pos+1] = 'Z'
+			buf[pos] = '\\'
 			pos += 2
 		case '\'':
-			buf[pos] = '\\'
 			buf[pos+1] = '\''
+			buf[pos] = '\\'
 			pos += 2
 		case '"':
-			buf[pos] = '\\'
 			buf[pos+1] = '"'
+			buf[pos] = '\\'
 			pos += 2
 		case '\\':
-			buf[pos] = '\\'
 			buf[pos+1] = '\\'
+			buf[pos] = '\\'
 			pos += 2
 		default:
 			buf[pos] = c
@@ -577,8 +744,8 @@ func escapeBytesQuotes(buf, v []byte) []byte {
 
 	for _, c := range v {
 		if c == '\'' {
-			buf[pos] = '\''
 			buf[pos+1] = '\''
+			buf[pos] = '\''
 			pos += 2
 		} else {
 			buf[pos] = c
@@ -597,8 +764,8 @@ func escapeStringQuotes(buf []byte, v string) []byte {
 	for i := 0; i < len(v); i++ {
 		c := v[i]
 		if c == '\'' {
-			buf[pos] = '\''
 			buf[pos+1] = '\''
+			buf[pos] = '\''
 			pos += 2
 		} else {
 			buf[pos] = c
@@ -623,6 +790,12 @@ type noCopy struct{}
 // Lock is a no-op used by -copylocks checker from `go vet`.
 func (*noCopy) Lock() {}
 
+// Unlock is a no-op used by -copylocks checker from `go vet`.
+// noCopy should implement sync.Locker from Go 1.11
+// https://github.com/golang/go/commit/c2eba53e7f80df21d51285879d51ab81bcfbf6bc
+// https://github.com/golang/go/issues/26165
+func (*noCopy) Unlock() {}
+
 // atomicBool is a wrapper around uint32 for usage as a boolean value with
 // atomic access.
 type atomicBool struct {
diff --git a/utils_test.go b/utils_test.go
index 10a60c2..b006925 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -14,6 +14,7 @@ import (
 	"database/sql/driver"
 	"encoding/binary"
 	"testing"
+	"time"
 )
 
 func TestLengthEncodedInteger(t *testing.T) {
@@ -227,7 +228,9 @@ func TestAtomicBool(t *testing.T) {
 		t.Fatal("Expected value to be false")
 	}
 
-	ab._noCopy.Lock() // we've "tested" it ¯\_(ツ)_/¯
+	// we've "tested" them ¯\_(ツ)_/¯
+	ab._noCopy.Lock()
+	defer ab._noCopy.Unlock()
 }
 
 func TestAtomicError(t *testing.T) {
@@ -291,3 +294,217 @@ func TestIsolationLevelMapping(t *testing.T) {
 		t.Fatalf("Expected error to be %q, got %q", expectedErr, err)
 	}
 }
+
+func TestAppendDateTime(t *testing.T) {
+	tests := []struct {
+		t   time.Time
+		str string
+	}{
+		{
+			t:   time.Date(1234, 5, 6, 0, 0, 0, 0, time.UTC),
+			str: "1234-05-06",
+		},
+		{
+			t:   time.Date(4567, 12, 31, 12, 0, 0, 0, time.UTC),
+			str: "4567-12-31 12:00:00",
+		},
+		{
+			t:   time.Date(2020, 5, 30, 12, 34, 0, 0, time.UTC),
+			str: "2020-05-30 12:34:00",
+		},
+		{
+			t:   time.Date(2020, 5, 30, 12, 34, 56, 0, time.UTC),
+			str: "2020-05-30 12:34:56",
+		},
+		{
+			t:   time.Date(2020, 5, 30, 22, 33, 44, 123000000, time.UTC),
+			str: "2020-05-30 22:33:44.123",
+		},
+		{
+			t:   time.Date(2020, 5, 30, 22, 33, 44, 123456000, time.UTC),
+			str: "2020-05-30 22:33:44.123456",
+		},
+		{
+			t:   time.Date(2020, 5, 30, 22, 33, 44, 123456789, time.UTC),
+			str: "2020-05-30 22:33:44.123456789",
+		},
+		{
+			t:   time.Date(9999, 12, 31, 23, 59, 59, 999999999, time.UTC),
+			str: "9999-12-31 23:59:59.999999999",
+		},
+		{
+			t:   time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
+			str: "0001-01-01",
+		},
+	}
+	for _, v := range tests {
+		buf := make([]byte, 0, 32)
+		buf, _ = appendDateTime(buf, v.t)
+		if str := string(buf); str != v.str {
+			t.Errorf("appendDateTime(%v), have: %s, want: %s", v.t, str, v.str)
+		}
+	}
+
+	// year out of range
+	{
+		v := time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC)
+		buf := make([]byte, 0, 32)
+		_, err := appendDateTime(buf, v)
+		if err == nil {
+			t.Error("want an error")
+			return
+		}
+	}
+	{
+		v := time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC)
+		buf := make([]byte, 0, 32)
+		_, err := appendDateTime(buf, v)
+		if err == nil {
+			t.Error("want an error")
+			return
+		}
+	}
+}
+
+func TestParseDateTime(t *testing.T) {
+	cases := []struct {
+		name string
+		str  string
+	}{
+		{
+			name: "parse date",
+			str:  "2020-05-13",
+		},
+		{
+			name: "parse null date",
+			str:  sDate0,
+		},
+		{
+			name: "parse datetime",
+			str:  "2020-05-13 21:30:45",
+		},
+		{
+			name: "parse null datetime",
+			str:  sDateTime0,
+		},
+		{
+			name: "parse datetime nanosec 1-digit",
+			str:  "2020-05-25 23:22:01.1",
+		},
+		{
+			name: "parse datetime nanosec 2-digits",
+			str:  "2020-05-25 23:22:01.15",
+		},
+		{
+			name: "parse datetime nanosec 3-digits",
+			str:  "2020-05-25 23:22:01.159",
+		},
+		{
+			name: "parse datetime nanosec 4-digits",
+			str:  "2020-05-25 23:22:01.1594",
+		},
+		{
+			name: "parse datetime nanosec 5-digits",
+			str:  "2020-05-25 23:22:01.15949",
+		},
+		{
+			name: "parse datetime nanosec 6-digits",
+			str:  "2020-05-25 23:22:01.159491",
+		},
+	}
+
+	for _, loc := range []*time.Location{
+		time.UTC,
+		time.FixedZone("test", 8*60*60),
+	} {
+		for _, cc := range cases {
+			t.Run(cc.name+"-"+loc.String(), func(t *testing.T) {
+				var want time.Time
+				if cc.str != sDate0 && cc.str != sDateTime0 {
+					var err error
+					want, err = time.ParseInLocation(timeFormat[:len(cc.str)], cc.str, loc)
+					if err != nil {
+						t.Fatal(err)
+					}
+				}
+				got, err := parseDateTime([]byte(cc.str), loc)
+				if err != nil {
+					t.Fatal(err)
+				}
+
+				if !want.Equal(got) {
+					t.Fatalf("want: %v, but got %v", want, got)
+				}
+			})
+		}
+	}
+}
+
+func TestParseDateTimeFail(t *testing.T) {
+	cases := []struct {
+		name    string
+		str     string
+		wantErr string
+	}{
+		{
+			name:    "parse invalid time",
+			str:     "hello",
+			wantErr: "invalid time bytes: hello",
+		},
+		{
+			name:    "parse year",
+			str:     "000!-00-00 00:00:00.000000",
+			wantErr: "not [0-9]",
+		},
+		{
+			name:    "parse month",
+			str:     "0000-!0-00 00:00:00.000000",
+			wantErr: "not [0-9]",
+		},
+		{
+			name:    `parse "-" after parsed year`,
+			str:     "0000:00-00 00:00:00.000000",
+			wantErr: "bad value for field: `:`",
+		},
+		{
+			name:    `parse "-" after parsed month`,
+			str:     "0000-00:00 00:00:00.000000",
+			wantErr: "bad value for field: `:`",
+		},
+		{
+			name:    `parse " " after parsed date`,
+			str:     "0000-00-00+00:00:00.000000",
+			wantErr: "bad value for field: `+`",
+		},
+		{
+			name:    `parse ":" after parsed date`,
+			str:     "0000-00-00 00-00:00.000000",
+			wantErr: "bad value for field: `-`",
+		},
+		{
+			name:    `parse ":" after parsed hour`,
+			str:     "0000-00-00 00:00-00.000000",
+			wantErr: "bad value for field: `-`",
+		},
+		{
+			name:    `parse "." after parsed sec`,
+			str:     "0000-00-00 00:00:00?000000",
+			wantErr: "bad value for field: `?`",
+		},
+	}
+
+	for _, cc := range cases {
+		t.Run(cc.name, func(t *testing.T) {
+			got, err := parseDateTime([]byte(cc.str), time.UTC)
+			if err == nil {
+				t.Fatal("want error")
+			}
+			if cc.wantErr != err.Error() {
+				t.Fatalf("want `%s`, but got `%s`", cc.wantErr, err)
+			}
+			if !got.IsZero() {
+				t.Fatal("want zero time")
+			}
+		})
+	}
+}