mirror of
https://github.com/TECHNOFAB11/dbmate.git
synced 2025-12-12 16:10:03 +01:00
Add ClickHouse support (#140)
This commit is contained in:
parent
8234882546
commit
c2dd1bd5af
7 changed files with 633 additions and 4 deletions
1
Makefile
1
Makefile
|
|
@ -19,6 +19,7 @@ lint:
|
||||||
wait:
|
wait:
|
||||||
dist/dbmate-linux-amd64 -e MYSQL_URL wait
|
dist/dbmate-linux-amd64 -e MYSQL_URL wait
|
||||||
dist/dbmate-linux-amd64 -e POSTGRESQL_URL wait
|
dist/dbmate-linux-amd64 -e POSTGRESQL_URL wait
|
||||||
|
dist/dbmate-linux-amd64 -e CLICKHOUSE_URL wait
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
|
|
|
||||||
23
README.md
23
README.md
|
|
@ -12,7 +12,7 @@ For a comparison between dbmate and other popular database schema migration tool
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Supports MySQL, PostgreSQL, and SQLite.
|
* Supports MySQL, PostgreSQL, SQLite, and ClickHouse.
|
||||||
* Uses plain SQL for writing schema migrations.
|
* Uses plain SQL for writing schema migrations.
|
||||||
* Migrations are timestamp-versioned, to avoid version number conflicts with multiple developers.
|
* Migrations are timestamp-versioned, to avoid version number conflicts with multiple developers.
|
||||||
* Migrations are run atomically inside a transaction.
|
* Migrations are run atomically inside a transaction.
|
||||||
|
|
@ -117,7 +117,7 @@ DATABASE_URL="postgres://postgres@127.0.0.1:5432/myapp_development?sslmode=disab
|
||||||
protocol://username:password@host:port/database_name?options
|
protocol://username:password@host:port/database_name?options
|
||||||
```
|
```
|
||||||
|
|
||||||
* `protocol` must be one of `mysql`, `postgres`, `postgresql`, `sqlite`, `sqlite3`
|
* `protocol` must be one of `mysql`, `postgres`, `postgresql`, `sqlite`, `sqlite3`, `clickhouse`
|
||||||
* `host` can be either a hostname or IP address
|
* `host` can be either a hostname or IP address
|
||||||
* `options` are driver-specific (refer to the underlying Go SQL drivers if you wish to use these)
|
* `options` are driver-specific (refer to the underlying Go SQL drivers if you wish to use these)
|
||||||
|
|
||||||
|
|
@ -161,6 +161,20 @@ To specify an absolute path, add an additional forward slash to the path. The fo
|
||||||
DATABASE_URL="sqlite:////tmp/database_name.sqlite3"
|
DATABASE_URL="sqlite:////tmp/database_name.sqlite3"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**ClickHouse**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
DATABASE_URL="clickhouse://username:password@127.0.0.1:9000/database_name"
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
|
DATABASE_URL="clickhouse://127.0.0.1:9000?username=username&password=password&database=database_name"
|
||||||
|
```
|
||||||
|
|
||||||
|
[See other supported connection options](https://github.com/ClickHouse/clickhouse-go#dsn).
|
||||||
|
|
||||||
### Creating Migrations
|
### Creating Migrations
|
||||||
|
|
||||||
To create a new migration, run `dbmate new create_users_table`. You can name the migration anything you like. This will create a file `db/migrations/20151127184807_create_users_table.sql` in the current directory:
|
To create a new migration, run `dbmate new create_users_table`. You can name the migration anything you like. This will create a file `db/migrations/20151127184807_create_users_table.sql` in the current directory:
|
||||||
|
|
@ -333,7 +347,7 @@ Why another database schema migration tool? Dbmate was inspired by many other to
|
||||||
|
|
||||||
| | [goose](https://bitbucket.org/liamstask/goose/) | [sql-migrate](https://github.com/rubenv/sql-migrate) | [golang-migrate/migrate](https://github.com/golang-migrate/migrate) | [activerecord](http://guides.rubyonrails.org/active_record_migrations.html) | [sequelize](http://docs.sequelizejs.com/manual/tutorial/migrations.html) | [dbmate](https://github.com/amacneil/dbmate) |
|
| | [goose](https://bitbucket.org/liamstask/goose/) | [sql-migrate](https://github.com/rubenv/sql-migrate) | [golang-migrate/migrate](https://github.com/golang-migrate/migrate) | [activerecord](http://guides.rubyonrails.org/active_record_migrations.html) | [sequelize](http://docs.sequelizejs.com/manual/tutorial/migrations.html) | [dbmate](https://github.com/amacneil/dbmate) |
|
||||||
| --- |:---:|:---:|:---:|:---:|:---:|:---:|
|
| --- |:---:|:---:|:---:|:---:|:---:|:---:|
|
||||||
| **Features** |||||||
|
| **Features** |
|
||||||
|Plain SQL migration files|:white_check_mark:|:white_check_mark:|:white_check_mark:|||:white_check_mark:|
|
|Plain SQL migration files|:white_check_mark:|:white_check_mark:|:white_check_mark:|||:white_check_mark:|
|
||||||
|Support for creating and dropping databases||||:white_check_mark:||:white_check_mark:|
|
|Support for creating and dropping databases||||:white_check_mark:||:white_check_mark:|
|
||||||
|Support for saving schema dump files||||:white_check_mark:||:white_check_mark:|
|
|Support for saving schema dump files||||:white_check_mark:||:white_check_mark:|
|
||||||
|
|
@ -343,10 +357,11 @@ Why another database schema migration tool? Dbmate was inspired by many other to
|
||||||
|Automatically load .env file||||||:white_check_mark:|
|
|Automatically load .env file||||||:white_check_mark:|
|
||||||
|No separate configuration file||||:white_check_mark:|:white_check_mark:|:white_check_mark:|
|
|No separate configuration file||||:white_check_mark:|:white_check_mark:|:white_check_mark:|
|
||||||
|Language/framework independent|:eight_pointed_black_star:|:eight_pointed_black_star:|:eight_pointed_black_star:|||:white_check_mark:|
|
|Language/framework independent|:eight_pointed_black_star:|:eight_pointed_black_star:|:eight_pointed_black_star:|||:white_check_mark:|
|
||||||
| **Drivers** |||||||
|
| **Drivers** |
|
||||||
|PostgreSQL|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|
|
|PostgreSQL|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|
|
||||||
|MySQL|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|
|
|MySQL|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|
|
||||||
|SQLite|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|
|
|SQLite|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|
|
||||||
|
|CliсkHouse|||:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|
|
||||||
|
|
||||||
> :eight_pointed_black_star: In theory these tools could be used with other languages, but a Go development environment is required because binary builds are not provided.
|
> :eight_pointed_black_star: In theory these tools could be used with other languages, but a Go development environment is required because binary builds are not provided.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,11 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
- mysql
|
- mysql
|
||||||
- postgres
|
- postgres
|
||||||
|
- clickhouse
|
||||||
environment:
|
environment:
|
||||||
MYSQL_URL: mysql://root:root@mysql/dbmate
|
MYSQL_URL: mysql://root:root@mysql/dbmate
|
||||||
POSTGRESQL_URL: postgres://postgres:postgres@postgres/dbmate?sslmode=disable
|
POSTGRESQL_URL: postgres://postgres:postgres@postgres/dbmate?sslmode=disable
|
||||||
|
CLICKHOUSE_URL: clickhouse://clickhouse:9000?database=dbmate
|
||||||
|
|
||||||
mysql:
|
mysql:
|
||||||
image: mysql:5.7
|
image: mysql:5.7
|
||||||
|
|
@ -20,3 +22,6 @@ services:
|
||||||
image: postgres:10
|
image: postgres:10
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
|
|
||||||
|
clickhouse:
|
||||||
|
image: yandex/clickhouse-server:19.16
|
||||||
|
|
|
||||||
1
go.mod
1
go.mod
|
|
@ -3,6 +3,7 @@ module github.com/amacneil/dbmate
|
||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/ClickHouse/clickhouse-go v1.4.1
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.5.0
|
github.com/go-sql-driver/mysql v1.5.0
|
||||||
|
|
|
||||||
13
go.sum
13
go.sum
|
|
@ -1,12 +1,20 @@
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/ClickHouse/clickhouse-go v1.4.1 h1:D9cihLg76O1ZyILLaXq1eksYzEuV010NdvucgKGGK14=
|
||||||
|
github.com/ClickHouse/clickhouse-go v1.4.1/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
|
||||||
|
github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=
|
||||||
|
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
|
||||||
|
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
|
||||||
|
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
|
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||||
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d h1:cVtBfNW5XTHiKQe7jDaDBSh/EVM4XLPutLAGboIXuM0=
|
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d h1:cVtBfNW5XTHiKQe7jDaDBSh/EVM4XLPutLAGboIXuM0=
|
||||||
|
|
@ -16,10 +24,14 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.5.2 h1:yTSXVswvWUOQ3k1sd7vJfDrbSl8lKuscqFJRqjC0ifw=
|
github.com/lib/pq v1.5.2 h1:yTSXVswvWUOQ3k1sd7vJfDrbSl8lKuscqFJRqjC0ifw=
|
||||||
github.com/lib/pq v1.5.2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.5.2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c=
|
github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c=
|
||||||
github.com/mattn/go-sqlite3 v1.13.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.13.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
|
||||||
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||||
|
|
@ -27,6 +39,7 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
|
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
|
||||||
|
|
|
||||||
289
pkg/dbmate/clickhouse.go
Normal file
289
pkg/dbmate/clickhouse.go
Normal file
|
|
@ -0,0 +1,289 @@
|
||||||
|
package dbmate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ClickHouse/clickhouse-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterDriver(ClickHouseDriver{}, "clickhouse")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClickHouseDriver provides top level database functions
|
||||||
|
type ClickHouseDriver struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeClickHouseURL(initialURL *url.URL) *url.URL {
|
||||||
|
u := *initialURL
|
||||||
|
|
||||||
|
u.Scheme = "tcp"
|
||||||
|
host := u.Host
|
||||||
|
if u.Port() == "" {
|
||||||
|
host = fmt.Sprintf("%s:9000", host)
|
||||||
|
}
|
||||||
|
u.Host = host
|
||||||
|
|
||||||
|
query := u.Query()
|
||||||
|
if query.Get("username") == "" && u.User.Username() != "" {
|
||||||
|
query.Set("username", u.User.Username())
|
||||||
|
}
|
||||||
|
password, passwordSet := u.User.Password()
|
||||||
|
if query.Get("password") == "" && passwordSet {
|
||||||
|
query.Set("password", password)
|
||||||
|
}
|
||||||
|
u.User = nil
|
||||||
|
|
||||||
|
if query.Get("database") == "" {
|
||||||
|
path := strings.Trim(u.Path, "/")
|
||||||
|
if path != "" {
|
||||||
|
query.Set("database", path)
|
||||||
|
u.Path = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
return &u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open creates a new database connection
|
||||||
|
func (drv ClickHouseDriver) Open(u *url.URL) (*sql.DB, error) {
|
||||||
|
return sql.Open("clickhouse", normalizeClickHouseURL(u).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (drv ClickHouseDriver) openClickHouseDB(u *url.URL) (*sql.DB, error) {
|
||||||
|
// connect to clickhouse database
|
||||||
|
clickhouseURL := normalizeClickHouseURL(u)
|
||||||
|
values := clickhouseURL.Query()
|
||||||
|
values.Set("database", "default")
|
||||||
|
clickhouseURL.RawQuery = values.Encode()
|
||||||
|
|
||||||
|
return drv.Open(clickhouseURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (drv ClickHouseDriver) databaseName(u *url.URL) string {
|
||||||
|
name := normalizeClickHouseURL(u).Query().Get("database")
|
||||||
|
if name == "" {
|
||||||
|
name = "default"
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
var clickhouseValidIdentifier = regexp.MustCompile(`^[a-zA-Z_][0-9a-zA-Z_]*$`)
|
||||||
|
|
||||||
|
func clickhouseQuoteIdentifier(str string) string {
|
||||||
|
if clickhouseValidIdentifier.MatchString(str) {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
str = strings.Replace(str, `"`, `""`, -1)
|
||||||
|
|
||||||
|
return fmt.Sprintf(`"%s"`, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDatabase creates the specified database
|
||||||
|
func (drv ClickHouseDriver) CreateDatabase(u *url.URL) error {
|
||||||
|
name := drv.databaseName(u)
|
||||||
|
fmt.Printf("Creating: %s\n", name)
|
||||||
|
|
||||||
|
db, err := drv.openClickHouseDB(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer mustClose(db)
|
||||||
|
|
||||||
|
_, err = db.Exec("create database " + clickhouseQuoteIdentifier(name))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DropDatabase drops the specified database (if it exists)
|
||||||
|
func (drv ClickHouseDriver) DropDatabase(u *url.URL) error {
|
||||||
|
name := drv.databaseName(u)
|
||||||
|
fmt.Printf("Dropping: %s\n", name)
|
||||||
|
|
||||||
|
db, err := drv.openClickHouseDB(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer mustClose(db)
|
||||||
|
|
||||||
|
_, err = db.Exec("drop database if exists " + clickhouseQuoteIdentifier(name))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func clickhouseSchemaDump(db *sql.DB, buf *bytes.Buffer, databaseName string) error {
|
||||||
|
buf.WriteString("\n--\n-- Database schema\n--\n\n")
|
||||||
|
|
||||||
|
buf.WriteString("CREATE DATABASE " + clickhouseQuoteIdentifier(databaseName) + " IF NOT EXISTS;\n\n")
|
||||||
|
|
||||||
|
tables, err := queryColumn(db, "show tables")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sort.Strings(tables)
|
||||||
|
|
||||||
|
for _, table := range tables {
|
||||||
|
var clause string
|
||||||
|
err = db.QueryRow("show create table " + clickhouseQuoteIdentifier(table)).Scan(&clause)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf.WriteString(clause + ";\n\n")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func clickhouseSchemaMigrationsDump(db *sql.DB, buf *bytes.Buffer) error {
|
||||||
|
// load applied migrations
|
||||||
|
migrations, err := queryColumn(db,
|
||||||
|
"select version from schema_migrations final where applied order by version asc",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
quoter := strings.NewReplacer(`\`, `\\`, `'`, `\'`)
|
||||||
|
for i := range migrations {
|
||||||
|
migrations[i] = "'" + quoter.Replace(migrations[i]) + "'"
|
||||||
|
}
|
||||||
|
|
||||||
|
// build schema_migrations table data
|
||||||
|
buf.WriteString("\n--\n-- Dbmate schema migrations\n--\n\n")
|
||||||
|
|
||||||
|
if len(migrations) > 0 {
|
||||||
|
buf.WriteString("INSERT INTO schema_migrations (version) VALUES\n (" +
|
||||||
|
strings.Join(migrations, "),\n (") +
|
||||||
|
");\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DumpSchema returns the current database schema
|
||||||
|
func (drv ClickHouseDriver) DumpSchema(u *url.URL, db *sql.DB) ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var err error
|
||||||
|
|
||||||
|
err = clickhouseSchemaDump(db, &buf, drv.databaseName(u))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = clickhouseSchemaMigrationsDump(db, &buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatabaseExists determines whether the database exists
|
||||||
|
func (drv ClickHouseDriver) DatabaseExists(u *url.URL) (bool, error) {
|
||||||
|
name := drv.databaseName(u)
|
||||||
|
|
||||||
|
db, err := drv.openClickHouseDB(u)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer mustClose(db)
|
||||||
|
|
||||||
|
exists := false
|
||||||
|
err = db.QueryRow("SELECT 1 FROM system.databases where name = ?", name).
|
||||||
|
Scan(&exists)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return exists, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMigrationsTable creates the schema_migrations table
|
||||||
|
func (drv ClickHouseDriver) CreateMigrationsTable(db *sql.DB) error {
|
||||||
|
_, err := db.Exec(`
|
||||||
|
create table if not exists schema_migrations (
|
||||||
|
version String,
|
||||||
|
ts DateTime default now(),
|
||||||
|
applied UInt8 default 1
|
||||||
|
) engine = ReplacingMergeTree(ts)
|
||||||
|
primary key version
|
||||||
|
order by version
|
||||||
|
`)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectMigrations returns a list of applied migrations
|
||||||
|
// with an optional limit (in descending order)
|
||||||
|
func (drv ClickHouseDriver) SelectMigrations(db *sql.DB, limit int) (map[string]bool, error) {
|
||||||
|
query := "select version from schema_migrations final where applied order by version desc"
|
||||||
|
if limit >= 0 {
|
||||||
|
query = fmt.Sprintf("%s limit %d", query, limit)
|
||||||
|
}
|
||||||
|
rows, err := db.Query(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer mustClose(rows)
|
||||||
|
|
||||||
|
migrations := map[string]bool{}
|
||||||
|
for rows.Next() {
|
||||||
|
var version string
|
||||||
|
if err := rows.Scan(&version); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
migrations[version] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return migrations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertMigration adds a new migration record
|
||||||
|
func (drv ClickHouseDriver) InsertMigration(db Transaction, version string) error {
|
||||||
|
_, err := db.Exec("insert into schema_migrations (version) values (?)", version)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMigration removes a migration record
|
||||||
|
func (drv ClickHouseDriver) DeleteMigration(db Transaction, version string) error {
|
||||||
|
_, err := db.Exec(
|
||||||
|
"insert into schema_migrations (version, applied) values (?, ?)",
|
||||||
|
version, false,
|
||||||
|
)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping verifies a connection to the database server. It does not verify whether the
|
||||||
|
// specified database exists.
|
||||||
|
func (drv ClickHouseDriver) Ping(u *url.URL) error {
|
||||||
|
// attempt connection to primary database, not "clickhouse" database
|
||||||
|
// to support servers with no "clickhouse" database
|
||||||
|
// (see https://github.com/amacneil/dbmate/issues/78)
|
||||||
|
db, err := drv.Open(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer mustClose(db)
|
||||||
|
|
||||||
|
err = db.Ping()
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore 'Database foo doesn't exist' error
|
||||||
|
chErr, ok := err.(*clickhouse.Exception)
|
||||||
|
if ok && chErr.Code == 81 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
305
pkg/dbmate/clickhouse_test.go
Normal file
305
pkg/dbmate/clickhouse_test.go
Normal file
|
|
@ -0,0 +1,305 @@
|
||||||
|
package dbmate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func clickhouseTestURL(t *testing.T) *url.URL {
|
||||||
|
u, err := url.Parse("clickhouse://clickhouse:9000?database=dbmate")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepTestClickHouseDB(t *testing.T) *sql.DB {
|
||||||
|
drv := ClickHouseDriver{}
|
||||||
|
u := clickhouseTestURL(t)
|
||||||
|
|
||||||
|
// drop any existing database
|
||||||
|
err := drv.DropDatabase(u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// create database
|
||||||
|
err = drv.CreateDatabase(u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// connect database
|
||||||
|
db, err := sql.Open("clickhouse", u.String())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeClickHouseURLSimplified(t *testing.T) {
|
||||||
|
u, err := url.Parse("clickhouse://user:pass@host/db")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
s := normalizeClickHouseURL(u).String()
|
||||||
|
require.Equal(t, "tcp://host:9000?database=db&password=pass&username=user", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeClickHouseURLCanonical(t *testing.T) {
|
||||||
|
u, err := url.Parse("clickhouse://host:9000?database=db&password=pass&username=user")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
s := normalizeClickHouseURL(u).String()
|
||||||
|
require.Equal(t, "tcp://host:9000?database=db&password=pass&username=user", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClickHouseCreateDropDatabase(t *testing.T) {
|
||||||
|
drv := ClickHouseDriver{}
|
||||||
|
u := clickhouseTestURL(t)
|
||||||
|
|
||||||
|
// drop any existing database
|
||||||
|
err := drv.DropDatabase(u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// create database
|
||||||
|
err = drv.CreateDatabase(u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// check that database exists and we can connect to it
|
||||||
|
func() {
|
||||||
|
db, err := sql.Open("clickhouse", u.String())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer mustClose(db)
|
||||||
|
|
||||||
|
err = db.Ping()
|
||||||
|
require.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// drop the database
|
||||||
|
err = drv.DropDatabase(u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// check that database no longer exists
|
||||||
|
func() {
|
||||||
|
db, err := sql.Open("clickhouse", u.String())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer mustClose(db)
|
||||||
|
|
||||||
|
err = db.Ping()
|
||||||
|
require.EqualError(t, err, "code: 81, message: Database dbmate doesn't exist")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClickHouseDumpSchema(t *testing.T) {
|
||||||
|
drv := ClickHouseDriver{}
|
||||||
|
u := clickhouseTestURL(t)
|
||||||
|
|
||||||
|
// prepare database
|
||||||
|
db := prepTestClickHouseDB(t)
|
||||||
|
defer mustClose(db)
|
||||||
|
err := drv.CreateMigrationsTable(db)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// insert migration
|
||||||
|
tx, err := db.Begin()
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = drv.InsertMigration(tx, "abc1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = tx.Commit()
|
||||||
|
require.NoError(t, err)
|
||||||
|
tx, err = db.Begin()
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = drv.InsertMigration(tx, "abc2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = tx.Commit()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// DumpSchema should return schema
|
||||||
|
schema, err := drv.DumpSchema(u, db)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, string(schema), "CREATE TABLE "+drv.databaseName(u)+".schema_migrations")
|
||||||
|
require.Contains(t, string(schema), "--\n"+
|
||||||
|
"-- Dbmate schema migrations\n"+
|
||||||
|
"--\n\n"+
|
||||||
|
"INSERT INTO schema_migrations (version) VALUES\n"+
|
||||||
|
" ('abc1'),\n"+
|
||||||
|
" ('abc2');\n")
|
||||||
|
|
||||||
|
// DumpSchema should return error if command fails
|
||||||
|
values := u.Query()
|
||||||
|
values.Set("database", "fakedb")
|
||||||
|
u.RawQuery = values.Encode()
|
||||||
|
db, err = sql.Open("clickhouse", u.String())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
schema, err = drv.DumpSchema(u, db)
|
||||||
|
require.Nil(t, schema)
|
||||||
|
require.EqualError(t, err, "code: 81, message: Database fakedb doesn't exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClickHouseDatabaseExists(t *testing.T) {
|
||||||
|
drv := ClickHouseDriver{}
|
||||||
|
u := clickhouseTestURL(t)
|
||||||
|
|
||||||
|
// drop any existing database
|
||||||
|
err := drv.DropDatabase(u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// DatabaseExists should return false
|
||||||
|
exists, err := drv.DatabaseExists(u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, false, exists)
|
||||||
|
|
||||||
|
// create database
|
||||||
|
err = drv.CreateDatabase(u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// DatabaseExists should return true
|
||||||
|
exists, err = drv.DatabaseExists(u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, true, exists)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClickHouseDatabaseExists_Error(t *testing.T) {
|
||||||
|
drv := ClickHouseDriver{}
|
||||||
|
u := clickhouseTestURL(t)
|
||||||
|
values := u.Query()
|
||||||
|
values.Set("username", "invalid")
|
||||||
|
u.RawQuery = values.Encode()
|
||||||
|
|
||||||
|
exists, err := drv.DatabaseExists(u)
|
||||||
|
require.EqualError(t, err, "code: 192, message: Unknown user invalid")
|
||||||
|
require.Equal(t, false, exists)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClickHouseCreateMigrationsTable(t *testing.T) {
|
||||||
|
drv := ClickHouseDriver{}
|
||||||
|
db := prepTestClickHouseDB(t)
|
||||||
|
defer mustClose(db)
|
||||||
|
|
||||||
|
// migrations table should not exist
|
||||||
|
count := 0
|
||||||
|
err := db.QueryRow("select count(*) from schema_migrations").Scan(&count)
|
||||||
|
require.EqualError(t, err, "code: 60, message: Table dbmate.schema_migrations doesn't exist.")
|
||||||
|
|
||||||
|
// create table
|
||||||
|
err = drv.CreateMigrationsTable(db)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// migrations table should exist
|
||||||
|
err = db.QueryRow("select count(*) from schema_migrations").Scan(&count)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// create table should be idempotent
|
||||||
|
err = drv.CreateMigrationsTable(db)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClickHouseSelectMigrations(t *testing.T) {
|
||||||
|
drv := ClickHouseDriver{}
|
||||||
|
db := prepTestClickHouseDB(t)
|
||||||
|
defer mustClose(db)
|
||||||
|
|
||||||
|
err := drv.CreateMigrationsTable(db)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tx, err := db.Begin()
|
||||||
|
require.NoError(t, err)
|
||||||
|
stmt, err := tx.Prepare("insert into schema_migrations (version) values (?)")
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = stmt.Exec("abc2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = stmt.Exec("abc1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = stmt.Exec("abc3")
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = tx.Commit()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
migrations, err := drv.SelectMigrations(db, -1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, true, migrations["abc1"])
|
||||||
|
require.Equal(t, true, migrations["abc2"])
|
||||||
|
require.Equal(t, true, migrations["abc2"])
|
||||||
|
|
||||||
|
// test limit param
|
||||||
|
migrations, err = drv.SelectMigrations(db, 1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, true, migrations["abc3"])
|
||||||
|
require.Equal(t, false, migrations["abc1"])
|
||||||
|
require.Equal(t, false, migrations["abc2"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClickHouseInsertMigration(t *testing.T) {
|
||||||
|
drv := ClickHouseDriver{}
|
||||||
|
db := prepTestClickHouseDB(t)
|
||||||
|
defer mustClose(db)
|
||||||
|
|
||||||
|
err := drv.CreateMigrationsTable(db)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
err = db.QueryRow("select count(*) from schema_migrations").Scan(&count)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, count)
|
||||||
|
|
||||||
|
// insert migration
|
||||||
|
tx, err := db.Begin()
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = drv.InsertMigration(tx, "abc1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = tx.Commit()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = db.QueryRow("select count(*) from schema_migrations where version = 'abc1'").Scan(&count)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClickHouseDeleteMigration(t *testing.T) {
|
||||||
|
drv := ClickHouseDriver{}
|
||||||
|
db := prepTestClickHouseDB(t)
|
||||||
|
defer mustClose(db)
|
||||||
|
|
||||||
|
err := drv.CreateMigrationsTable(db)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tx, err := db.Begin()
|
||||||
|
require.NoError(t, err)
|
||||||
|
stmt, err := tx.Prepare("insert into schema_migrations (version) values (?)")
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = stmt.Exec("abc2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = stmt.Exec("abc1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = tx.Commit()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tx, err = db.Begin()
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = drv.DeleteMigration(tx, "abc2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = tx.Commit()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
err = db.QueryRow("select count(*) from schema_migrations final where applied").Scan(&count)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClickHousePing(t *testing.T) {
|
||||||
|
drv := ClickHouseDriver{}
|
||||||
|
u := clickhouseTestURL(t)
|
||||||
|
|
||||||
|
// drop any existing database
|
||||||
|
err := drv.DropDatabase(u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// ping database
|
||||||
|
err = drv.Ping(u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// ping invalid host should return error
|
||||||
|
u.Host = "clickhouse:404"
|
||||||
|
err = drv.Ping(u)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "connect: connection refused")
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue