Compare commits

...
666 Commits
Author SHA1 Message Date
Charles MartinotandGitHub 15931649a2 Mysql - Fix crash with ansi_quotes sql_mode enabled (#476)
* Fix crash with ansi_quotes

* Run lint
2020-11-18 12:38:28 -08:00
Mitsuo HeijoandGitHub fedbe42fc8 Add io/fs#FS Driver #471 (#472)
* Add io/fs#FS Driver #471

* Refactor iofs.Iofs to be independent of httpfs

* Rename Iofs to IoFS, accepts fs.FS and use fs.DirEntry.Info() to undo the changes in ErrDuplicateMigration

* fix documents and migration directory for testing

* Fix iofs.Driver so that it can be used by embedding it like httpfs.PartialDriver

* iofs.Driver closes the file system if possible

* Rename receiver

* Refactor file driver to use iofs.Driver

* iofs build constraint is not needed so remove it

* Properly return an error in a corner case

* Increase golangci-lint timeout

* Fix example code

* Revert "Increase golangci-lint timeout"

This reverts commit 2cd12a63b50b7ac48d12dd0adc1ec4f4b49c3b93.

* unexport driver, add iofs.PartialDriver, remove type alias from file driver

* do not panic in Open, return error
2020-11-18 10:06:11 -08:00
Dale Hui 52e7117d37 Use flagset's NArg() and Arg() methods instead of global flag NArg() and Arg() for sub-commands
Fixes: https://github.com/golang-migrate/migrate/issues/467
2020-11-04 23:37:05 -08:00
Yannick HeinrichandGitHub 8a56273871 Add help statement for cli (#460)
* Add help statement for cli

Refers to #372

* Make constant for argument

* Create a flagset for each command

* Factor error handling

* Factor flagset creation

* fix: Typo and bad behaviour

* Fix lint error

* Fix according to the comments

* Fix according to the comments
2020-11-03 14:54:12 -08:00
Dale Hui 110c0f74b4 Update dktest to v0.3.3 which also updates lib/pq to v1.8.0 2020-10-30 22:26:55 -07:00
EvanandGitHub f76b94ab7d Document drop -f in migrate -help (#459)
* Document drop -f in migrate -help

* Document drop -f in migrate -help 2
2020-10-17 01:44:43 -07:00
Vladimir DimitrovandGitHub 91deba8df0 Update driver.go (#454)
* Update driver.go

* addressing comments
2020-10-16 22:19:05 -07:00
Maarten BezemerandGitHub f18b16515a postgres - Force MigrationsTable in 'public' schema (#414)
* Force MigrationsTable on public schema

* Revert "Force MigrationsTable on public schema"

This reverts commit e5b25e8858ed42d19d420cb8bd16795c060994d2.

* Add example to fix search_path issue

* Use POSTGRESQL_URL in example and fix typo

* Make example simpler by using the `search_path` query parameter of the DB URL
2020-10-15 20:21:00 -07:00
Dale HuiandGitHub f8f021730e Merge pull request #458 from xortive/patch-1
Fix typo in doc comment
2020-10-13 09:19:03 -07:00
MattandGitHub a17aa6e83d Fix typo in doc comment 2020-10-13 11:55:41 -04:00
Dale HuiandGitHub 5d84bec1c8 Merge pull request #456 from hypnoglow/iter-err
Check iterator error on Drop where applicable
2020-10-10 20:46:14 -07:00
Igor Zibarev 5f932c05c8 Check iterator error on Drop where applicable 2020-10-10 23:53:20 +03:00
Dale HuiandGitHub 0521ebeb71 Warn about installing the CLI from within a module
Addresses: https://github.com/golang-migrate/migrate/pull/257
2020-10-07 16:51:03 -07:00
Dale Hui 99ca856a7c Run go fix 2020-10-07 15:31:28 -07:00
Dale Hui 04a572114a Parse out custom TLS params and register the TLS config before calling mysql.ParseDSN
Fixes: https://github.com/golang-migrate/migrate/issues/411
2020-10-06 19:49:59 -07:00
Dale Hui 8c531c7836 Use spanner cloud emulator to run tests
Addresses: https://github.com/golang-migrate/migrate/issues/410
2020-10-01 17:58:26 -07:00
Dale Hui fa3e11943a Add local locks for spanner db driver 2020-10-01 17:58:23 -07:00
d6881f1826 Add Bitbucket Cloud Support as source (#450)
* Added bitbucket cloud support as source for migration

* added test

* address PR Comments

* Add entry in README.md; update go.mod and go.sum

Co-authored-by: abhigupta91 <abhigupta0818@gmail.com>
2020-09-28 22:08:20 -07:00
Dale HuiandGitHub c6026053bc Merge pull request #448 from andyNewman42/locking
Add advisory locking to mongodb
2020-09-25 21:05:56 -07:00
Andy Newman 2c2f6910f4 Rename mongo flag for enabling locking. Add validation around parsing mongo params. 2020-09-25 15:02:49 -07:00
Andy Newman 73760ed4cb Implement PR feedback
rename constants
rename query params
add new timeout interval query param
add new feature flag
use timeout context
2020-09-24 15:38:57 -07:00
Andy Newman 7208a16bf3 Add advisory locking to mongodb 2020-09-24 15:38:57 -07:00
Dale HuiandGitHub 50439fee71 Merge pull request #447 from markokoleznik-cs/patch-1
Fix typo
2020-09-17 10:21:45 -07:00
markokoleznik-csandGitHub 5d91d93b7e typo 2020-09-17 07:59:02 +02:00
Dale HuiandGitHub 6819a17049 Merge pull request #439 from antonklava/add-mysql-no-lock
`NoLock` config option for mysql driver
2020-09-09 10:21:01 -07:00
Anton Klava 8a67f7e535 mysql - strconv.ParseBool for x-no-lock parame
`strconv.ParseBool` should ensure that if x-no-lock is set to any
non-booleanic value an error is returned. The parameter still
defaults to false.
2020-09-09 11:57:28 +02:00
Anton Klava aefffb6f6c NoLock config option for mysql driver
Skipping `GET_LOCK`/`RELEASE_LOCK` is useful when running MySQL as
multi-master. This change simply disables the locking.

The internal client lock is left in place as a sanity check.
2020-09-08 09:25:42 +02:00
Dale HuiandGitHub cb28f6bffb Merge pull request #436 from klingtnet/add-sqlcipher-database-driver
Add database driver for SQLCipher
2020-09-02 17:23:05 -07:00
Andreas Linz 7845cb88da Mention SQLCipher in the list of supported databases 2020-09-02 20:48:32 +02:00
Andreas Linz 7129dfa5d9 Add build file for sqlcipher 2020-09-02 20:44:52 +02:00
Andreas Linz 84eea4aaf2 Add sqlcipher to the DATABASE_TEST variable 2020-09-02 20:43:49 +02:00
Andreas Linz 39cb3234a7 Link to sqlite3 driver 2020-09-02 20:43:03 +02:00
Andreas Linz 85d673f95f Add database driver for sqlcipher
This is a copy of the existing sqlite3 driver, except that it imports `_
"github.com/mutecomm/go-sqlcipher/v4"` as database driver.  This allows
to use migrate with encrypted sqlite databases.
2020-09-02 18:29:45 +02:00
Roman LeventovandGitHub f1cf81e851 Include file path in errors from PartialDriver.ReadUp() and ReadDown() (#421)
* Include file path in errors from PartialDriver.ReadUp() and ReadDown()

* Use errors.Is(...) instead of os.Is...() in migrate.go: https://github.com/golang/go/issues/41122
2020-08-29 16:44:38 -07:00
Dale Hui c6ebff6469 Update golangci-lint from v1.27.0 to v1.30.0 2020-08-19 02:04:00 -07:00
Dale Hui c904438dda Add support for Go 1.15 and drop support for Go 1.13 2020-08-19 00:58:47 -07:00
Dale HuiandGitHub ed3f30cf12 Merge pull request #427 from wyardley/wyardley-426
fix(spanner): resolve "want scalar type" error
2020-08-19 00:11:11 -07:00
William Yardley e5eb088eaf fix(spanner): resolve "want scalar type" error
- Update spanner library to cloud.google.com/go/spanner@v1.9.0
This prevents erroring on DDL statements like:

ALTER TABLE users ALTER COLUMN created
  SET OPTIONS (allow_commit_timestamp = true);
https://github.com/googleapis/google-cloud-go/pull/2656

- Add test case which fails with older version of the library, and
succeeds with this one.

Fixes: #426
2020-08-18 22:35:07 -07:00
Danny HermesandGitHub 6035eb5469 Replacing Github with GitHub. (#429)
* Replacing `Github` with `GitHub`.

Done via:

```
$ git grep -l Github | xargs sed -i '' s/Github/GitHub/g
```

* Reverting changes to Go files.
2020-08-17 13:55:44 -07:00
Dale HuiandGitHub ce34d50b16 Merge pull request #428 from wyardley/wyardley-docs
docs(spanner): clarify how to set parameters
2020-08-16 11:05:38 -07:00
William Yardley 0010bc8495 docs(spanner): clarify how to set paramters
- include param in DSN example
- link to README docs on param (option) format
- switch to 'parameter' instead of 'option', in the main README, since
this is preferred
2020-08-16 05:40:20 -07:00
Dale Hui 31c22870d3 Use latest version of neo4j db driver to fix 32-bit builds
Relevant fix: https://github.com/neo4j/neo4j-go-driver/pull/129
2020-08-03 17:32:42 -07:00
Dale Hui 49f67a8ad4 Use make for docker builds so all releases use the same db and source drivers 2020-08-03 16:28:46 -07:00
Dale Hui 23608f1e63 Update to Alpine 3.12 for docker builds 2020-08-03 16:24:32 -07:00
Dale HuiandGitHub d72c200a64 Merge pull request #419 from dawidd6/patch-1
Makefile: provide a simpler build task
2020-07-28 12:39:51 -07:00
Dawid Dziurla ea98a5bc9f Makefile: provide a simpler build task 2020-07-28 18:55:15 +02:00
Dale HuiandGitHub 07052cd704 Merge pull request #417 from orijtech/master
go.mod: upgrade dependencies and rid vulnerable thrift version@v0.12.0
2020-07-26 23:18:33 -07:00
Emmanuel T Odeke a258a44f2a go.mod: upgrade dependencies and rid vulnerable thrift version@v0.12.0
Coming here, after digging through
google/exposure-notifications-server#749
in which the version of github.com/apache/thrift was reported by
@whaber as having known critical vulnerabilities.

Transitively however, this version was pinning to
cloud.google.com/go@v0.37.4 indirectly, which then imported
versions of opencensus:
* go.opencensus.io@v0.19.
* go.opencensus.io@v0.20.1
that imported
    github.com/apache/thrift@v0.12.0

The target package to upgrade was
    github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec
which now uses
    cloud.google.com/go@v0.61.0
2020-07-25 19:56:06 -07:00
Dale Hui f34bc2cba0 Distribute releases to Ubuntu 20.04 LTS Focal Fossa
Stop distributing release to non-LTS EOL'd Ubuntu 18.10 Cosmic Cuttlefish
Addresses: https://github.com/golang-migrate/migrate/issues/416
2020-07-21 11:43:31 -07:00
Dale Hui f75042ac5d Run go mod tidy 2020-07-20 21:30:45 -07:00
Dale HuiandGitHub bf6c1ec3d3 Merge pull request #415 from arjantop-cai/neo4j-no-cgo
Allow compilation of neo4j support without CGO.
2020-07-20 21:27:20 -07:00
Arjan Topolovec 271087a63b Allow compilation of neo4j support without CGO.
Since 1.8.0 neo4j driver no longer requires CGO.
2020-07-17 14:07:37 +02:00
Dale Hui 53ef02da13 Use multistmt package to parse multi-statement migrations for clickhouse, cassandra, and neo4j
Addresses: https://github.com/golang-migrate/migrate/issues/406
2020-07-07 23:41:00 -07:00
Dale Hui 620492183f Add helper package to parse multi-statement migrations
Addresses: https://github.com/golang-migrate/migrate/issues/406
2020-07-07 22:50:09 -07:00
Dale HuiandGitHub 8eb1d3031c Merge pull request #409 from alethenorio/spanner-comments
Add support for comments in Spanner migrations
2020-06-30 03:48:41 -07:00
Alexandre Thenorio e6faa29d36 Add support for comments in Spanner migrations
At the moment GCP Spanner backend does not seem to support comments
(issue being tracked at
https://issuetracker.google.com/issues/159730604).
By adding support for parsing the migration DDL with spansql we are
able to support comments and remove them before applying on the
database
2020-06-30 09:25:33 +02:00
Richard LindhoutandGitHub 8b6e3f1a2e Fix small typo in GETTING_STARTED.md (#407)
* Update GETTING_STARTED.md

* Update GETTING_STARTED.md
2020-06-22 10:09:05 -07:00
Dmitry IvankovandGitHub 1f973cc3ff Bump spanner lib to v1.5.0 (#403)
* Bump spanner lib to v1.5.0

Will enable spanner emulator support
https://cloud.google.com/spanner/docs/emulator#go

Current version supports SPANNER_EMULATOR_HOST but fails at run-time
with spanner permission errors.

* go mod tidy
2020-06-09 12:34:55 -07:00
Dale HuiandGitHub 64180398eb Add disclaimer to snowflake db driver 2020-06-03 23:16:43 -07:00
Dale HuiandGitHub 79396ffccc Merge pull request #402 from abhinavcohesity/master
Adding support for schema management in snowflake
2020-06-03 23:14:04 -07:00
Abhinav Kumar b00a0cc77b Adding support for schema management in snowflake 2020-06-03 13:55:24 +05:30
Dale HuiandGitHub 7236e82911 Merge pull request #397 from hnnsgstfssn/add-pkger-readme
source/pkger: add readme
2020-05-25 23:15:14 -07:00
Hannes Gustafsson 7b58ded3b3 source/pkger: add readme 2020-05-24 01:32:26 +01:00
Dale Hui 3c7150850e Update golangci-lint from v1.24.0 to v1.27.0 2020-05-21 15:23:17 -07:00
Dale Hui cd9f7bce91 Update neo4j db driver from v1.8.0-beta01 to v1.8.0-beta02
https://github.com/neo4j/neo4j-go-driver/compare/v1.8.0-beta01...v1.8.0-beta02
2020-05-21 13:37:35 -07:00
Dale Hui 805c416251 Use neo4j v1.8.0-beta01 client
The neo4j v1.8 client drops cgo dependencies (seabolt)
2020-05-21 13:32:52 -07:00
Dale HuiandGitHub 371e18dde4 Merge pull request #391 from shiwano/shiwano/mongodb-srv
Register mongodb+srv scheme
2020-05-18 10:45:39 -07:00
Shogo IwanoandGitHub 524edc9f0b Add -f option to drop command (#390)
* Add -y option to drop command

* Replace -y -> -f

* Update -f help text of drop command
2020-05-18 10:42:14 -07:00
Shogo Iwano 306d4577a5 Update README.md 2020-05-18 22:32:26 +09:00
Shogo Iwano 894821e1ef Register mongodb+srv scheme 2020-05-17 10:29:46 +09:00
Dale HuiandGitHub b88b868b91 Merge pull request #386 from KadenLNelson/update-cassandra-ssl-options
Update Cassandra SSL options
2020-05-12 10:19:35 -07:00
Kaden L. Nelson b8ee1b9733 update cassandra ssl options 2020-05-11 19:24:52 -07:00
hnnsgstfssnandGitHub f5a22be0a0 Add pkger source driver support (#377)
* Add pkger source driver support

As go-bindata has been abandoned [1] there are open requests, #116, for
alternative sources with similar functionality. The Buffalo project [2]
created packr and recently pkger [3] was announced [4] with the
intention to supersede packr.

This change adds support for using pkger as a source.

The implementation relies on httpfs.PartialDriver for pretty much all
functionality.

[1] https://github.com/jteeuwen/go-bindata/issues/5
[2] https://gobuffalo.io/
[3] https://github.com/markbates/pkger
[4] https://blog.gobuffalo.io/introducing-pkger-static-file-embedding-in-go-1ce76dc79c65

* pkger: rename Instance to Pkger

* pkger: make WithInstance accept *Pkger

* pkger: refactor and add access to global pkging.Pkger instance

* pkger: fix typo and cleanup debug logging
2020-04-21 16:43:53 -07:00
Dimas Ragil TandGitHub 423c2d35ee Add neo4j driver on params instance (#373)
* Add neo4j driver on instance params

* Check constraint if exist return nil

* Create new driver if nil

* Remove URL and AuthToken from neo4j config

* Change call db constraint

* Add neo4j image 4.0

* Handle create new driver

* Use labels instead constraints

* Turn off TLS encryption on test
2020-04-16 15:16:07 -07:00
Dale Hui 833d00711e Run go mod tidy 2020-04-15 02:41:33 -07:00
Dale HuiandGitHub ace4ca8556 Merge pull request #375 from jmramos02/master
Upgrade multierror to v1.1.0
2020-04-15 02:37:30 -07:00
Jose Mari Ramos 8a7f164b67 Upgrade multierror to v1.1.0 2020-04-15 10:25:13 +08:00
Dale Hui 5180ec47dc [DB-DRIVER][neo4j] Properly handle null ts 2020-04-06 05:09:41 -07:00
Dale Hui 4598673138 Update golangci-lint from v1.23.6 to v1.24.0 2020-04-06 00:07:15 -07:00
Dale Hui 2c5115907f [DB-DRIVER][neo4j] Track timestamp when schema version was last updated 2020-04-06 00:07:15 -07:00
Dale Hui 3d2a99ec64 Correctly mark migrations as dirty when the first down migration fails
Fixes: https://github.com/golang-migrate/migrate/issues/330

Other changes:
  - Cleanup code by using the database.NilVersion constant instead of the magic number -1
2020-04-06 00:07:15 -07:00
Dale HuiandGitHub 20a1dcfe80 Fix command for installing with Go toolchain (versioned)
Thanks pointing out the issue @JKKGBE !
2020-04-03 22:59:35 -07:00
Dale HuiandGitHub 43f6d84de7 Make "force db version" answer more descriptive 2020-03-26 15:01:32 -07:00
Dale HuiandGitHub 00b39b2b6c Merge pull request #361 from mknycha/add-docs-info-about-dirty-database
Docs: add info about troubleshooting dirty database
2020-03-25 18:22:46 -07:00
Marcin K 3a349451e2 Docs: add info about troubleshooting dirty database
[#337]
2020-03-24 19:16:26 +01:00
Dale HuiandGitHub 098a3e7efe Merge pull request #360 from Rocketmakers/drop-prompt
Prompt before dropping the entire database
2020-03-18 12:30:57 -07:00
Nicholas Soper ec26edc735 Give positive feedback when dropping schema 2020-03-18 09:02:00 +00:00
Nicholas Soper 1f66249648 Prompt before dropping the entire database 2020-03-17 17:37:27 +00:00
Dale HuiandGitHub 5cc28f061e Merge pull request #356 from jace-ys/master
Move migrate binary to /usr/local/bin in Dockerfile
2020-03-14 17:24:58 -07:00
jace-ys 6cdebb0871 Move migrate binary to /usr/local/bin in Dockerfile
Move the migrate binary to `/usr/local/bin` in the Dockerfile, so
it can be invoked from anywhere - this makes it easier to write
entrypoint scripts which call `migrate` that can be used
consistently both locally and in Docker containers.

To preserve backwards compatibility for anything that depends on
`migrate` existing at its existing path, we create a symlink to the
`/usr/local/bin/migrate` binary.
2020-03-14 19:21:12 +00:00
8e142dfaa7 Improved migration creation + Windows fix (#352)
* CLI: Improve migration creation.

* Validates migration version on creation, to avoid creation of duplicated versions
* Uses `os.OpenFile` with `O_CREATE|O_EXCL` to create files to avoid file collisions
* Uses `filepath.Join` to concatenate paths, making `cleanPath()` not necessary
* Prints generated filenames
* Fixes #238
* Supersedes #250

* CLI: change `createCmd` to return error and accept `print` parameter

* feat: print out an abs path

Better for Windows OS when specify -dir as /subdir, user will see C:/subdir/0001_name.up.sql rather than /subdir/0001_name.up.sql

* test: fixed abs path test fail for OS Windows

abs path tests fails because filepath.IsAbs() treats `/subdir` path as invalid abs path at windows when drive letter is not present

* feat: print absolute path for created files

Better for Windows OS systems where `/path` can be interpreted in different ways depending on working dir

* test: corrected tests for OS Windows

OS Windows has different interpretation of `/path`, it depends on working dir.
If working dir D:\test it interprets `/path` as `D:\path`

* test: fixed `dir invalid` test

Linux OS has less restriction on a filepath than Windows, so path invalid in windows is perfectly valid for Linux. The only invalid dir name in Linux is one ending with null string terminator (\000)

* refac(cli): *Cmd() now returns an error and not uses log.fatalErr(err)

* docs: added godoc, migarate usage updated

* refac(cli): code refactored

* refac: removed unnecessary path covert

* docs: comment added

* test: fixed code review issue, noErrorExpected var removed

https://github.com/golang-migrate/migrate/pull/352#discussion_r389409789

* docs: fixed godoc

Co-authored-by: Kiyoshi '13k' Murata <kbmurata@gmail.com>
2020-03-10 16:59:11 -07:00
Dale Hui 9b3db6cd7d Fix README markdown lint issues 2020-03-08 15:28:53 -07:00
Dale Hui fb3ffecbb5 Add support for Go 1.14 and drop support for Go 1.12 2020-03-08 15:28:29 -07:00
Dale Hui 1de555b72d Update cockroach db
Fixes: https://github.com/golang-migrate/migrate/issues/308
2020-03-04 08:34:50 -08:00
Dale HuiandGitHub 39cc0970bd Merge pull request #350 from saj/saj/sqlite-no-implicit-tx
sqlite3: Allow users to disable implicit transactions
2020-03-02 20:08:16 -08:00
Saj Goonatilleke 831d8c9ae6 sqlite3: Explicitly reject invalid x-no-tx-wrap values 2020-03-02 22:28:18 +11:00
Saj Goonatilleke d75ded973a sqlite3: Add README 2020-02-29 05:47:13 +11:00
Saj Goonatilleke 583757ab2d sqlite3: Allow users to disable implicit transactions
An sqlite3 database may be opened with the x-no-tx-wrap=true query
parameter if the user would prefer to retain full control over the
bounds of their transactions.  With x-no-tx-wrap enabled, migrations
should contain explicit BEGIN and COMMIT statements.  This (optional)
behaviour for the sqlite3 driver is consistent with the default
behaviour for other drivers.

Fixes #346.
2020-02-29 05:14:59 +11:00
Dale HuiandGitHub 6dc95bd201 Merge pull request #348 from martinlindhe/master
Add Windows install instructions using scoop
2020-02-25 08:59:46 -08:00
Martin Lindhe b4872fb19d Add Windows install instructions using scoop 2020-02-25 11:45:36 +01:00
Dale Hui 777550e4d7 Actually release windows 386 build 2020-02-17 08:46:08 -08:00
Dale HuiandGitHub 51f9aabf68 Merge pull request #343 from martinlindhe/win386
build-cli: Also build GOOS=windows GOARCH=386, fixes #339
2020-02-17 02:23:11 -08:00
Dale HuiandGitHub cbadf63c13 Merge branch 'master' into win386 2020-02-16 22:53:07 -08:00
Dale Hui d9d6d85ecd POSTGRES_PASSWORD is now required when using postgres via docker
See: https://github.com/docker-library/postgres/issues/681
2020-02-16 01:37:09 -08:00
Dale Hui 9dcc6f061f Drop support for postgres 9.4 and add support for postgres 12
https://www.postgresql.org/about/news/2011/
https://www.postgresql.org/about/news/1976/
2020-02-16 00:38:23 -08:00
Martin Lindhe 115177e679 build-cli: Also build GOOS=windows GOARCH=386, fixes #339 2020-02-16 05:11:07 +01:00
Dale Hui 67536b7f1c Update github.com/fsouza/fake-gcs-server from v1.7.0 to v1.17.0 2020-02-14 01:14:05 -08:00
Dale Hui 9e6ca5ccaa Update cloud.google.com/go from v0.37.4 to v0.53.0 2020-02-14 00:44:21 -08:00
Dale Hui 7291b60643 Update golangci-lint from v1.22.2 to v1.23.6 2020-02-14 00:08:02 -08:00
Dale Hui 64db556342 Update dktest from v0.3.1 to v0.3.2
Addresses: https://github.com/golang-migrate/migrate/issues/338
2020-02-13 23:35:01 -08:00
Dale HuiandGitHub 607b1142fa Merge pull request #336 from Glyphack/add-errnochange-check-up
Add ErrNoChange check when using Up.
2020-02-12 00:44:59 -08:00
glyphack 22b2dbf747 Add ErrNoChange check when using Up. https://github.com/golang-migrate/migrate/issues/100 2020-02-12 00:55:09 +03:30
Dale HuiandGitHub e1085cfef2 Merge pull request #334 from bishtawi/bishtawi/aws-session-override
Allow passing in your own aws s3client object
2020-02-04 18:30:44 -08:00
Stephen Bishtawi 1637f18132 Cleanup 2020-02-04 17:58:29 -08:00
Stephen Bishtawi 453547cf81 Cleanup 2020-02-04 09:40:17 -08:00
Stephen Bishtawi a50ea56eda Cleanup 2020-02-03 12:32:27 -08:00
Dale HuiandGitHub 402f080149 Merge pull request #335 from bcomnes/patch-1
Fix URL in tutorial
2020-01-31 15:48:44 -08:00
Bret ComnesandGitHub a74470d619 Fix URL in tutorial
This link needed to be updated.
2020-01-31 14:13:30 -07:00
Stephen Bishtawi df9067fec5 Update 2020-01-31 10:56:16 -08:00
Stephen Bishtawi 5d38bead9a Implement WithInstance pattern 2020-01-31 10:24:31 -08:00
Stephen Bishtawi c2d3db8c2b More testing 2020-01-30 13:00:45 -08:00
Stephen Bishtawi 0f10e74f69 Add test 2020-01-30 11:09:08 -08:00
Stephen Bishtawi 459f15b500 Fix when prefix is empty 2020-01-29 17:31:18 -08:00
Stephen Bishtawi 5439c3a702 Allow using a custom aws session 2020-01-29 14:57:07 -08:00
Mantas VidutisandDale Hui 69093192ef Allow for multistatement migrations in Neo4j (#328)
* allow for multistatement migrations

* multi statement option

* parse bool and package const

* default false multi, change package var to byte[]
2020-01-22 23:59:32 -08:00
f29923306d add statement timeout query param for postgres (#327)
* add statement timeout query param for postgres

* Update database/postgres/postgres.go

Co-Authored-By: Roopak Venkatakrishnan <roopak.v@gmail.com>

* add enable flag

* remove deafult catch errors

* reformat

* only use background context for 0 timeout

* refactor

* refactor

Co-authored-by: Pranay Suresh <53918678+psuresh309@users.noreply.github.com>
Co-authored-by: Roopak Venkatakrishnan <roopak.v@gmail.com>
2020-01-21 11:22:41 -08:00
Dale Hui 8b62949069 Update go-sql-driver from v1.4.1 to v1.5.0 2020-01-20 22:34:00 -08:00
Dale Hui 8346ecefea Update pq from v1.0.0 to v1.3.0 2020-01-20 22:33:01 -08:00
Mantas VidutisandDale Hui 4ff83b9d74 Fix issues with Neo driver (#324)
* initial pass

* new cli build file

* add ready function for docker start

* lowercase j

* explicit return

* test migration

* undo dockerfile change

* neo4j in dockerfile

* remove auth from url once transferred to auth token

* assorted review notes

* initial docs attempt

* unlock behavior more in line with lock behavior

* consistent naming

* linting

* seabolt in travis ci

* stdlib logger

* install seabolt as sudo

* enabled cgo

* correct version #

* remove neo4j todo

* named return values for errors

* review notes

* add libssl for alpine

* updates for review

* updates for neo driver

* go mod tidy
2020-01-19 18:56:51 -08:00
Dale Hui dde480b416 Reference seabolt in neo4j db driver README 2020-01-14 22:36:26 -08:00
Dale Hui 57c0e0d7c0 Rename "downloader" Docker build stage to "builder" 2020-01-14 22:24:00 -08:00
Dale Hui 9ca70b31ef Split up Go module fetching in Docker builds to take advantage of Docker build cache 2020-01-14 22:24:00 -08:00
Dale Hui 07bed0e214 Shallow clone seabolt repo 2020-01-14 22:24:00 -08:00
Dale Hui 3de5c9af24 Support SQLite in Docker CLI 2020-01-14 22:24:00 -08:00
Dale Hui c32255c13d Fix broken Docker CLI
Need to copy over seabolt shared/static library
2020-01-14 22:24:00 -08:00
Dale Hui 407d0cec58 Run go mod tidy 2020-01-14 22:24:00 -08:00
Mantas VidutisandDale Hui 3061f4564a Neo4J Support (#320)
* initial pass

* new cli build file

* add ready function for docker start

* lowercase j

* explicit return

* test migration

* undo dockerfile change

* neo4j in dockerfile

* remove auth from url once transferred to auth token

* assorted review notes

* initial docs attempt

* unlock behavior more in line with lock behavior

* consistent naming

* linting

* seabolt in travis ci

* stdlib logger

* install seabolt as sudo

* enabled cgo

* correct version #

* remove neo4j todo

* named return values for errors

* review notes

* add libssl for alpine

* updates for review
2020-01-14 21:59:22 -08:00
Dale Hui 9e2a0cdc38 Use new ql driver location 2020-01-03 02:59:33 -08:00
Dale Hui 49afea4d86 Don't pass params to SQL statements using fmt.Sprintf() 2020-01-03 02:59:33 -08:00
Dale Hui 4e593fd2a9 Remove unnecessary byte slice arg from TestMigrate() in database/testing 2020-01-03 00:15:52 -08:00
Oras Al-KubaisiandDale Hui 3001e743c3 More examples in Mongo (#268)
* adding/removing a new field in the schema

* example of updating field value from other fields

* added new line at end of files

Co-authored-by: Dale Hui <dhui@users.noreply.github.com>
2020-01-03 00:12:08 -08:00
Dale Hui f6d44b4b24 Use Golang 1.13 and Alpine Linux 3.11 to build standard Docker images 2020-01-02 22:49:04 -08:00
Dale HuiandGitHub 0eedf49c28 Merge pull request #297 from mknycha/add-tutorial-for-cockroachdb
Add a tutorial for cockroachdb
2020-01-02 21:22:25 -08:00
Marcin K d993b8eaee Prevent shell interpretation in the exported variables 2020-01-02 20:17:56 +01:00
Dale Hui 4146594df3 Rename httpfs package files for improved readability 2020-01-02 00:10:22 -08:00
Dale Hui 8261b52d33 Use httpfs.PartialDriver as main godoc_vfs source implementation 2020-01-02 00:10:22 -08:00
Dale Hui a4820755ab Use httpfs.PartialDriver as main file source implementation 2020-01-01 16:26:24 -08:00
Dale Hui c27f930c6e Update golangci-lint from v1.20.0 to v1.22.2 2019-12-31 19:24:20 -08:00
Dale HuiandGitHub 6355956008 Merge pull request #316 from pcarranza/pc/add-arm-binaries
Add arm binaries to the build-cli phase
2019-12-31 19:10:11 -08:00
Dale HuiandGitHub 2f4bfa29e5 Merge branch 'master' into pc/add-arm-binaries 2019-12-31 17:39:25 -08:00
Dale Hui 4100c1d952 Update dktest from v0.3.0 to v0.3.1 2019-12-31 17:11:17 -08:00
Dale Hui 7965ee37ab Fix failing sqlserver tests by using latest images
Don't need to specify Ubuntu in tag since all mcr.microsoft.com/mssql/server images are based on Ubuntu anyways
2019-12-31 17:10:49 -08:00
Pablo Carranza fb317bb597 Add arm binaries to the build-cli phase 2019-12-29 11:18:51 +01:00
Dale Hui 6b1121a658 Tweak source.httpfs README 2019-12-24 15:38:36 -08:00
Dale Hui 525949de9f Add additional information in source.ErrDuplicateMigration struct 2019-12-24 15:35:08 -08:00
Dale HuiandGitHub 09f532b773 Merge pull request #293 from fln/master
Add http.FileSystem migration source driver support
2019-12-24 15:30:35 -08:00
Julius Kriukas dc7fa068e3 Drop errors.Is() usage to support go 1.12.x 2019-12-19 15:46:12 +02:00
Julius Kriukas e57ac62141 Rename PartialDriver receiver variable to p 2019-12-19 15:36:14 +02:00
Julius Kriukas 516bfe1c27 Drop delayed error reporting 2019-12-19 15:30:00 +02:00
Julius Kriukas 0491d12e50 Rename Migrator -> PartialDriver 2019-12-19 15:13:02 +02:00
Julius Kriukas b7e05731ea Add new test with example driver implemenation 2019-12-16 12:01:52 +02:00
Julius Kriukas 0bea9205d9 Improve driver.Open() error message 2019-12-16 11:06:52 +02:00
Julius Kriukas bf817e5de6 Fix README.md and documentation typo 2019-12-16 10:15:28 +02:00
Julius Kriukas bfa4bdf7a0 Split New() tests 2019-12-16 10:11:31 +02:00
Julius Kriukas 9e851de675 Split source and tests into two files to match data types 2019-12-13 17:41:47 +02:00
Julius Kriukas 98a84cdaec Fix documentation 2019-12-13 17:31:06 +02:00
Julius Kriukas 46206b7330 Split Migrator and Driver tests 2019-12-13 02:09:14 +02:00
Julius Kriukas cf23a7307e Split Migrator and Driver structures 2019-12-13 02:01:24 +02:00
Julius Kriukas 515a6d1664 Move ErrDuplicateMigration to the source package 2019-12-13 01:04:06 +02:00
Julius Kriukas 1fc246e82d Prefer field mutation over struct replacement 2019-12-13 00:59:23 +02:00
Julius Kriukas 33cf17647b Use string concatenation instead of fmt.Sprintf() 2019-12-13 00:20:02 +02:00
Julius Kriukas df5e294c5c Simplify not test.ok to else 2019-12-13 00:13:23 +02:00
Julius Kriukas 29df549a5b Add ErrDuplicateMigration typed error 2019-12-12 16:49:30 +02:00
Julius Kriukas 49ae0330b0 Test for error on First() when no migration files are present 2019-12-12 16:13:43 +02:00
Julius Kriukas 20e6881fd8 Cover subdir ignore code path 2019-12-12 15:42:56 +02:00
Julius Kriukas 1ed56cb468 Use external package for tests 2019-12-12 15:36:24 +02:00
Julius Kriukas 73ba745097 Replace WithInstance with Init, make main struct public 2019-12-12 12:06:18 +02:00
Julius Kriukas 7b6a813149 Replace test messages with sub-test names 2019-12-12 11:00:26 +02:00
Julius Kriukas 9c45ae5db0 Switch to if ok pattern for ReadUp and ReadDown 2019-12-12 10:36:01 +02:00
Julius Kriukas 9c9a49920d Drop coverage.out 2019-12-11 17:51:18 +02:00
Julius KriukasandJulius Kriukas 41dfe1d575 Add httpfs.New() constructor with delayed errors
This PR adds a new httpfs source driver constructor New(). It is
identical to the WithInstance() constructor but will postpone any errors
until the driver is actually being used.

This allows clients of this package to have less error checks without
loosing correctness.

In addition this PR adds more tests, improves test code coverage, and
makes golinter happy by removing deferred Close call.
2019-12-06 15:55:07 +02:00
Julius KriukasandJulius Kriukas b5bd00dc8a Add http.FileSystem migration source driver support
This PR adds a new migration source driver capable of reading files from
any source that implements http.FileSystem interface.

Notable http.FileSystem interface implementations:

* http.Dir() - wrapper over local file-system
* github.com/shurcooL/vfsgen

Because user of this package is responsible for getting an
implementation of http.FileSystem, this driver does not support creating
instances from driver URLs.
2019-12-06 15:55:07 +02:00
Marcin K 89b51584c5 Add a reference to the tutorial in README 2019-11-29 08:00:00 +01:00
Marcin K 63f26f6fb9 Add a simple tutorial for cockroachdb 2019-11-29 08:00:00 +01:00
Dale HuiandGitHub 41b578a45e Merge pull request #305 from bxcodec/fixClickHouse
hotfix(dependency): resolve clickhouse dependency breaking changes
2019-11-21 00:26:34 -08:00
Iman Tumorang 9d1abc92fa fix(dependency): resolve clickhouse dependency breaking changes 2019-11-21 10:18:48 +08:00
Dale Hui ae62039386 Add official support for firebird back 2019-10-17 08:15:47 -07:00
Dale Hui a7c2a28bec Deploy using Go 1.13 2019-10-17 07:53:29 -07:00
Dale Hui dc9b4dab5c Only populate db config values if it's not specified
See also: https://github.com/golang-migrate/migrate/issues/262
2019-10-17 07:48:52 -07:00
Dale HuiandGitHub 7f20f01a3b Merge pull request #288 from jon-whit/master
Trim statement leading and trailing whitespace.
2019-10-14 20:53:54 -07:00
Dale Hui 57b2ba7115 Actually use schema and db names specified in postgres db config
Addresses: https://github.com/golang-migrate/migrate/issues/262
2019-10-13 23:24:10 -07:00
Dale Hui bbe4ec3cde Update golangci-lint to v1.20.0 2019-10-12 15:14:27 -07:00
Dale Hui 84f30c64d0 Specify the expected min go version as 1.12 2019-10-12 15:14:21 -07:00
Jonathan Whitaker 241ad96665 Trim statement leading and trailing whitespace.
The changes herein make sure Spanner DDL statements have leading and
trailing whitespace characters trimmed.
2019-10-03 14:35:37 -06:00
Dale HuiandGitHub fc060940b4 Merge pull request #287 from ctuong/pg-lock-comment
Update postgres driver Lock comment to reflect actual behavior
2019-09-25 00:57:45 -07:00
Calvin Tuong 9ef241d273 Update postgres driver Lock comment to reflect actual behavior 2019-09-24 13:57:01 -07:00
Dale Hui 002e0aced4 Update golangci-lint to v1.18.0 for Go 1.13 compatibility 2019-09-17 02:21:45 -07:00
Dale Hui be590bf1b9 Drop support for Go 1.11 and add support for Go 1.13 2019-09-09 08:00:12 -07:00
Peter DotchevandDale Hui 3dc81826fd Improve error message in case no migration is found (#158)
* Improve error message in case no migration is found

* Return similar error

* Just log the error

* Fix error message
2019-08-29 20:09:02 -07:00
Dale HuiandGitHub b6df2cab18 Merge pull request #277 from ch3rub1m/bugfix-remove-debug-output
Remove debug output
2019-08-29 00:45:16 -07:00
ch3rub1m 0601111d88 Remove debug output 2019-08-28 18:36:44 +08:00
Dale Hui 0064ee83cf Properly filter out custom query params in MySQL DB driver
Addresses: https://github.com/golang-migrate/migrate/issues/272
2019-08-22 00:03:26 -07:00
Dale Hui a354c6d446 Run go mod tidy 2019-08-22 00:00:09 -07:00
Erik DubbelboerandDale Hui e5b4be7771 Let database.Open() use schemeFromURL as well (#271)
* Let database.Open() use schemeFromURL as well

Otherwise it will fail on MySQL DSNs.

Moved schemeFromURL into the database package. Also removed databaseSchemeFromURL
and sourceSchemeFromURL as they were just calling schemeFromURL.

Fixes https://github.com/golang-migrate/migrate/pull/265#issuecomment-522301237

* Moved url functions into internal/url

Also merged the test cases.

* Add some database tests to improve coverage

* Fix suggestions
2019-08-20 09:59:15 -07:00
Sebastiaan van StijnandDale Hui d5960ade4a Docker: restore "from env" behavior, and perform API version negotiation (#270)
* NewDockerContainer: restore "from env" behavior

commit c31948caeb replaced the deprecated
`dockerclient.NewEnvClient` with `dockerclient.NewClientWithOpts`, but
did not add the `FromEnv` option to keep  the old behavior.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

* NewDockerContainer: use API version negotiation

This helps in situations where the daemon is older than
the API client. API version negotiation checks the maximum
supported API version by the daemon, and downgrades to that
API version if needed.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-08-20 08:42:07 -07:00
Dale Hui d068374972 Test against MS SQL Server 2019-CTP3.1 (Ubuntu) 2019-08-18 23:37:33 -07:00
Dale Hui 947936279a Update firebird README to reflect lack of official support 2019-08-18 23:34:07 -07:00
Dale Hui 42492c82ca Update golangci-lint from v1.16.0 to v1.17.1 2019-08-17 10:50:42 -07:00
Dale HuiandGitHub 97cfb4d2a6 Merge pull request #267 from zikaeroh/fix-mongodb-dep
Update MongoDB driver to 1.1.0, use correct import path
2019-08-17 10:20:45 -07:00
zikaeroh d80e0e2f7f Merge branch 'master' into fix-mongodb-dep 2019-08-17 08:13:16 -07:00
Erik DubbelboerandDale Hui eb7d0dd6a1 Fix in the URL parser with go 1.12.8 and github.com/go-sql-driver/mysql (#265)
* Fix in the URL parser with go 1.12.8 and github.com/go-sql-driver/mysql

Change schemeFromURL to just split the url by :// to find the scheme.
It's not required to parse the whole URL. MySQL DSNs aren't valid URLs.

Fixes #264

* The mysql driver itself also used net/url.Parse

* Also fix TestPasswordUnencodedReservedURLChars

* Keep backwards compatibility with url encoded username and passwords

* Fix suggestions

* Reuse old function names
2019-08-16 23:09:12 -07:00
zikaeroh 9e406c40b5 Update MongoDB driver to 1.1.0, use correct import path 2019-08-14 17:40:52 -07:00
Dale HuiandGitHub b071731cc2 Merge pull request #258 from mknycha/add-tutorial-for-getting-started
Add getting started docs and Postgres tutorial
2019-08-13 08:31:01 -07:00
Marcin K d3ec78d996 Use different word 2019-08-13 16:40:03 +02:00
Marcin K 98c383cc42 Update info regarding migrations inconsistency 2019-08-13 16:38:16 +02:00
Marcin K 12f7e22e36 Put tutorial specific info into a separate file 2019-08-11 22:06:04 +02:00
Dale HuiandGitHub 6c7d3123c2 Merge pull request #260 from groyoh/exit_status
Change exit status to 2 on command error
2019-08-10 02:16:59 -07:00
Marcin K 86e4e4965d FAQ: Add question regarding non-Go project usage 2019-08-09 18:50:24 +02:00
Marcin K d0c2e446b7 Remove hint regarding Makefile
As it is too much environment-specific
2019-08-09 18:46:57 +02:00
Marcin K bb1e9dd3ae Describe database transactions 2019-08-09 18:46:04 +02:00
Yohan Robert dc5b8a46c6 Add documentation for exit 2 2019-08-09 15:15:29 +02:00
Marcin K d90f7d51aa Cosmetics 2019-08-08 21:03:54 +02:00
Marcin K d6fa9be6e6 Add a caveat regarding DB lock when running migrations 2019-08-08 21:03:00 +02:00
Marcin K fb4fb978e1 Use idempotent queries 2019-08-08 20:34:21 +02:00
Marcin K 604f1254ba Use sequential migrations naming in example
And add migration conflicts description
2019-08-08 20:11:05 +02:00
Marcin K b49854bde5 TUTORIAL: Use password explicitly 2019-08-07 22:36:20 +02:00
Marcin K fb771c8dd8 Rephrase, list created files 2019-08-07 22:35:22 +02:00
Marcin K 44b07c5f7b Use reverse/down migration instead of rollback 2019-08-07 22:01:04 +02:00
Marcin K e98f9f6a24 FAQ: Remove driver specific info 2019-08-07 21:52:15 +02:00
Yohan Robert 95623b991f Change exit status to 2 on command error
The default exit status when a command cannot be found was 0.
This can cause migrate to fail "silently" in case of typos.
Given that providing an invalid flag causes the program to exit
with a exit status set to 2, the default exit status was also set to 2.
2019-08-07 20:13:16 +02:00
Marcin K 1d402e46a8 Tutorial: Remove info about migration version table 2019-08-07 01:09:26 +02:00
Marcin K d7e0e40e0a FAQ: Add one more question 2019-08-07 01:07:29 +02:00
Marcin K 74b9fdb3cb Add reference to README 2019-08-07 01:01:15 +02:00
Marcin K 01107cb677 Add tutorial file 2019-08-07 01:00:58 +02:00
Dale Hui e93eaeb3fe Another attempt at fixing builds
Addresses: https://github.com/golang-migrate/migrate/issues/254
2019-07-30 00:06:35 -07:00
Dale Hui b55570703d Fix broken builds
Addresses: https://github.com/golang-migrate/migrate/issues/254
2019-07-29 23:59:39 -07:00
Dale HuiandGitHub fd16003059 Add Go Report Card badge 2019-07-24 22:58:07 -07:00
Dale Hui 71c6afaf98 Reduce docker binary size by stripping symbols 2019-07-12 10:54:42 -07:00
Dale Hui bb99610517 Update Docker image to use Alpine 3.10 2019-07-11 23:22:30 -07:00
Dale HuiandGitHub ff1977cbd4 Update migration docs
Correct and clarify information around empty migrations
Addresses: https://github.com/golang-migrate/migrate/issues/244
2019-07-11 23:05:33 -07:00
Dale HuiandGitHub 14a3bcdaa8 Merge pull request #236 from KlotzAndrew/migrate-down-less-destructive
migrate down less destructive
2019-06-26 10:55:06 -07:00
Andrew Klotz ffea024e7c migrate down less destructive
default behaviour for down is to apply all down migrations, which is
comparable to dropping a database - and usually not the desired default
action

proposed changes:

 * `down` prompts for a confirm `y` before applying all down migrations, defaulting to doing nothing
 * `down --all` does the current behaviour, applying all down migrations
 * `down N` is unchanged

 * `down N --all` errors
 * `down --all N` errors
2019-06-26 00:00:54 -04:00
Dale Hui d4e8e9ac07 Deprecate migrate's MultiError in favor of github.com/hashicorp/go-multierror 2019-06-24 08:58:28 -07:00
Dale HuiandGitHub 5399892051 Merge pull request #240 from k-yomo/fix/typo
Fix typo in README for CLI
2019-06-23 22:08:45 -07:00
k-yomo 8d9d92b7b1 Fix typo in README for CLI 2019-06-23 18:46:05 +09:00
Dale Hui cde3cfc30d Fix broken sqlite tests
Since v4.3.0, Migrate.Drop() no longer recreates the migrate schema version table.
See: https://github.com/golang-migrate/migrate/releases/tag/v4.3.0
2019-06-21 00:15:01 -07:00
Dale Hui 481bf0fb03 Run tests so that coverage profiles don't need to be manually concatenated
Addresses: https://github.com/golang-migrate/migrate/issues/2
2019-06-20 23:07:47 -07:00
Dale Hui 9f6c7e5fc9 Properly clean/normalize user specified directory for create command
Addresses: https://github.com/golang-migrate/migrate/issues/238
2019-06-20 21:35:51 -07:00
Kasparas GaldikasandDale Hui 7c76166697 Github Enterprise support (#234)
* exported Github struct fields and ReadDirectory method

* github ee implementation, tests and docs

* build fixes

* Github Enterprise API endpoint based on docs

* addressing PR comments

* code review

* make linter happy

* parseBool() takes fallback

* pr comments

* tweaks to Config{}
2019-06-16 12:48:01 -07:00
Dale Hui 0d13e794e4 Clean up CLI docs
- Move main CLI docs from cli to cmd/migrate
    - Clean up markdown
    - Update example CLI usage in Docker
2019-06-13 01:30:58 -07:00
Dale HuiandGitHub 8437fe6dc6 Merge pull request #227 from zhevron/mssql-params
Fix incorrect syntax errors for MSSQL driver
2019-05-27 10:25:36 -07:00
Thomas Lokshall dd0ead011e update Dockerfile referencing renamed mssql driver 2019-05-27 08:20:47 +02:00
Thomas Lokshall ffbd6893f6 sqlserver: update comment referencing mssql 2019-05-27 08:20:35 +02:00
Thomas Lokshall bad962cf21 Merge branch 'mssql-params' of https://github.com/zhevron/migrate into mssql-params 2019-05-24 15:16:24 +02:00
Thomas Lokshall 293bfec844 rename mssql driver to sqlserver 2019-05-24 15:16:12 +02:00
Thomas LokshallandGitHub bc0b686fc0 Merge branch 'master' into mssql-params 2019-05-23 11:12:50 +02:00
Thomas Lokshall e08ae0e996 mssql: remove mssql registration, document only support for sqlserver driver 2019-05-23 09:43:13 +02:00
Alex PliutauandDale Hui e877644ed1 Fix MongoDB docs: x-migrations-collection (#225) 2019-05-22 18:29:44 -07:00
Dale HuiandGitHub 5ed8a441d4 Merge pull request #229 from aryzing/patch-1
Typo
2019-05-22 18:25:41 -07:00
Eduard Bardají PuigandGitHub 6ff5b4ed4c Typo 2019-05-22 20:25:18 +01:00
Thomas Lokshall 3d4dad5655 mssql: use sqlserver driver instead of deprecated mssql driver 2019-05-22 14:10:37 +02:00
Thomas Lokshall ec3ae16f2e mssql: fix sp_MSforeachtable parameter 2019-05-22 14:09:50 +02:00
Thomas Lokshall 9f282f4ee3 use correct mssql parameter syntax 2019-05-22 10:34:53 +02:00
Ferdy PruisandDale Hui e85c5f51b9 Disable foreign_key_checks in Drop command for MySQL (#224)
* Disable FOREIGN_KEY_CHECKS in MySQL when dropping all tables.

* Lowercased system variable

* Discard error enabling foreign_key_checks, dropping is already successful at this point

* Explicitly discard error
2019-05-21 10:56:37 -07:00
Dale HuiandGitHub 2327ddb52d Fix reference to MS SQL Server support in README 2019-05-21 10:54:54 -07:00
Dale HuiandGitHub 7b3cd164d7 Merge pull request #222 from nathan-c/master
Add MS SQL Server Support
2019-05-21 10:52:41 -07:00
Nathan Collard aeb7f633f3 mssql: fix code review comments 2019-05-21 08:44:17 +01:00
Nathan Collard 7817573893 Merge remote-tracking branch 'root/master' 2019-05-21 08:21:33 +01:00
Dale Hui 923901c81f Cleanup regex usage in spanner DB driver
- Only compile a regex once for re-use
    - Don't use regex unnecessarily (removing trailing semicolon)
    - Add test for parsing multiple statements in a migration
2019-05-20 08:28:07 -07:00
Dale Hui bd81e32d1a Mark testing package as deprecated 2019-05-19 16:52:41 -07:00
nathan-c e211c5b5f5 mssql: increase pull timeout 2019-05-19 21:36:50 +01:00
nathan-c 98e5f88b9f mssql: fix error parsing and add tests 2019-05-19 16:08:15 +01:00
nathan-c 5ac583ba7b mssql: fix linter errors 2019-05-19 15:21:25 +01:00
nathan-c 3bd91a774e mssql: reorder readme 2019-05-19 15:15:07 +01:00
nathan-c 95290f2fbd mssql: change migration table name and add readme 2019-05-19 15:09:56 +01:00
nathan-c 14e6c50d2e fix mssql build flag 2019-05-19 14:47:09 +01:00
nathan-c c273636efe parse mssql errors 2019-05-19 14:08:10 +01:00
nathan-c 2fbec2f760 update mssql driver module 2019-05-19 13:43:47 +01:00
nathan-c ae500dbe45 finish implementing mssql db and add tests 2019-05-19 13:37:40 +01:00
nathan-c 50e972912d Merge remote-tracking branch 'evosure/mssql' 2019-05-18 14:39:55 +01:00
Dale HuiandGitHub e364488761 Merge pull request #220 from Flaque/patch-1
Fix Broken Markdown in cli/README.md
2019-05-16 12:15:54 -07:00
Evan ConradandGitHub c7e9414bfe Fix Broken Markdown in cli/README.md 2019-05-16 11:30:01 -07:00
Dale HuiandGitHub f2e5e31837 Remove offical Firebird support from README 2019-04-26 22:52:56 -07:00
Dale Hui 10e2545ceb Update dependencies to resolve: https://github.com/golang-migrate/migrate/issues/202 2019-04-26 19:09:13 -07:00
Dale Hui 89970b8951 Bring built-in and supported sources and databases into parity 2019-04-26 19:04:37 -07:00
Dale Hui 66510b82c4 Remove official support for Firebird
- In the future, support for Firebird should also include v2.5
2019-04-26 19:04:05 -07:00
Kirill MuratovandDale Hui 6c96ef02df Enabled maligned, staticcheck and errcheck linters (#206)
* Enabled maligned check

* Enabled staticcheck

* Fixes for golint

* Enabled errcheck linter

* Added fixes for error check

* Added errcheck for tests

* Fixed test

* Increased golangci-lint deadline for travis

* Increased golangci-lint deadline for travis

* Decreased golangci-lint deadline for travis

* Revert for backward compatibility

* Using log.Println() instead of fmt.Println()

* Handling os.RemoveAll() errors

* Using t.Error(error) instead of t.Errorf("%v", err)

* Using t.Fatal(error) instead of t.Fatalf("%v", err)

* Using fmt.Sprint(sum) instead of t.Srintf("%v", sum)

* Refactoring

* Revert for backward compatibility

* Revert

* go mod tidy

* Added error logging

* Added error logging

* Added error handling

* Added error handling

* Added error logging

* Fix error logging

* Added error handling

* Fix

* Added logging for migr.Buffer()

* Fixes

* Firebird test disabled

* Fixed nolint comment

* Updated firebird docker image version

* Disabled test for firebird 2.5

* Fixed // nolint
2019-04-26 15:47:16 -07:00
Dale Hui ddc72468fc Update packages that depend on the wrong golint import path
Addresses: https://github.com/golang-migrate/migrate/issues/202
2019-04-09 00:31:17 -07:00
Dale Hui 16197b67e3 Update to golangci-lint v1.16.0
- Fix newly raised issues
2019-04-07 00:51:27 -07:00
Dale Hui 6e2056f5b4 Include firebird db driver in default builds 2019-04-05 23:25:57 -07:00
Dale Hui 8d63b3f95b Remove spurious list item 2019-03-28 21:35:54 -07:00
Dale Hui 43e4503343 Update golangci-lint config and implore contributors to use it
- Run golangci-lint on tests and fix found issues
2019-03-28 18:55:12 -07:00
Dale HuiandGitHub c1404f0f98 Merge pull request #194 from kmuratov/master
Enabled golangci-lint
2019-03-28 11:57:10 -07:00
Kirill Muratov 46ee425bac Updated golangci.yml 2019-03-28 19:30:02 +03:00
Kirill Muratov 81f457f379 Added newline 2019-03-26 22:05:47 +03:00
Kirill Muratov 07c594d988 Added scopelint check 2019-03-26 22:01:12 +03:00
Kirill Muratov be4c129168 Added interfacer, unconvert, goconst, nakedret, prealloc 2019-03-23 00:40:35 +03:00
Kirill Muratov ef4a805b02 Added misspell linter 2019-03-22 23:57:00 +03:00
Kirill Muratov 5059f0bee6 Fixes for govet 2019-03-22 23:05:19 +03:00
Kirill Muratov c31948caeb Fixed dockerclient.NewEnvClient is depricated 2019-03-22 22:47:24 +03:00
Kirill Muratov 2758eb210f Fixes for unused linter 2019-03-22 22:08:29 +03:00
Kirill Muratov 465060c9a7 Fixes for gosimple linter 2019-03-22 22:02:08 +03:00
Kirill Muratov 8f6bfb0295 Added golangci-lint config 2019-03-22 21:11:31 +03:00
Dale HuiandGitHub cac03318eb List Gitlab as a supported source 2019-03-21 01:54:59 -07:00
Dale HuiandGitHub 7bf5fd7859 Add missing backtick 2019-03-21 01:54:41 -07:00
Kirill MuratovandDale Hui 2c9773ed89 Added Firebird support (#191)
* Added Firebird support

* Fixed typo

* Refactoring

* Schema migrations table name don't have to be upper case

* Fixed readme

* Added Firebird 2.5 support

* Removed SchemaName

* Refactoring
2019-03-21 01:51:49 -07:00
Cyrille HemidyandDale Hui 46fb82ce3d Fmt (#189)
* fix gofmt -s

* fix gofmt -s + fix mispelling
2019-03-13 12:15:21 -07:00
Bence PatyiandDale Hui 144aaaa986 Gitlab source (#184)
* New Gitlab source

* fix: use net package to build url string

* add: gitlab source to Makefile and Dockerfile
2019-03-10 18:38:23 -07:00
Dale HuiandGitHub 14f26d8818 Update supported Go versions badge
Drop 1.10 and add 1.12
2019-03-07 11:42:22 -08:00
Dale Hui f8f7beea54 Deploy using Go 1.12 2019-03-02 02:04:53 -08:00
Dale HuiandGitHub c6c22b34c8 Fix broken "versions" table formatting in README 2019-03-01 18:52:20 -08:00
Dale Hui 752cdc3b11 Update "versions" table in the README
- Reflect supported versions
    - With Go 1.12 released, module support is no longer an issue
2019-03-01 18:49:12 -08:00
Dale Hui f5c4ccff1f Update dependencies 2019-03-01 17:45:37 -08:00
Dale Hui 5e004dbef4 Fix issues with new version of MongoDB driver:
- "connect=single" option is no longer supported
        - "connect=direct" is the equivalent
        - https://jira.mongodb.org/browse/GODRIVER-665
    - Count() needs to be passed a non-nil filter/document
        - https://jira.mongodb.org/browse/GODRIVER-572
2019-03-01 17:44:40 -08:00
Dale Hui 4fba554b47 Drop support for go1.10
- Remove dep package pinnings
2019-03-01 14:37:54 -08:00
Dale Hui 6593dc8dbf Support Go 1.12 2019-02-27 23:44:23 -08:00
Lukas JoergensenandDale Hui 480a5a634a postgres: Move lock out of ensureVersionTable, for consistency with other SQL operations (#173)
* Consistently lock in ensureVersionTable and do not call ensureVersionTable from Drop across all database implementations

* Add test for dropping postgres databases

* Fix failing database tests

* Fix CockroachDb test, lock table should be created before versionTable

* Add Initialize() to Driver interface, and add integration tests for Drop() between database implementations and migrate

* Remove Initialize, document breaking behaviour of Drop

* Revert introduction of Initialize method

* Removed Initialize in Stub as well

* Remove call to non-existent Initialize and make sure to close re-initialized database connections

* Revert changes to TestDrop in database/testing

* Split Test and TestMigrate into different test entrypoints

* Remove unused import in migrate_testing

* Remove erroneous code to fix tests

* Add stub source imports to database tests

* Add Stub source to migrate tests

* Use example migrations for tests

* Add file driver to database tests

* Align database directory layout

* Add file source driver to Cassandra

* Review changes

* Minor syntactic change for cleaner diff
2019-02-26 15:56:57 -08:00
Dale Hui f213007d4d Use alpine 3.9 for building Docker images 2019-02-19 03:25:47 -08:00
Dale Hui 0f8263de2e Remove unnecessary panics from core implementation 2019-02-19 02:07:07 -08:00
Dale HuiandGitHub 9b449be538 Merge pull request #176 from jszwedko/quote-postgres-identifiers
Quote postgres table identifiers when `DROP`ing
2019-02-18 03:14:11 -08:00
Jesse Szwedko 3db0395e0a Replace " quoting with pq.QuoteIdentifier for postgres driver 2019-02-18 11:44:38 +01:00
Jesse Szwedko fe66cd7734 Quote postgres table identifiers when DROPing
Handles table identifiers that are invalid without quotes (such as
uppercase letters).
2019-02-18 11:29:10 +01:00
Dale HuiandGitHub 4190b85018 Merge pull request #175 from plutov/master
Add mongodb driver to Dockerfile
2019-02-18 02:05:12 -08:00
Alex Pliutau 0a355bbee0 Add mongodb driver to Dockerfile 2019-02-18 10:15:23 +01:00
Brian Wigginton 407d1e3982 bump go.sum 2019-02-15 16:12:57 -06:00
Brian Wigginton 9b551a1c10 initial mssql support 2019-02-15 16:12:57 -06:00
Dale HuiandGitHub c0f025d2f6 Merge pull request #167 from arpando/fix/DocumentNilError
Fix document nil error
2019-02-05 03:48:34 -08:00
Arcadio Pando 56620dc3dd Fix document nil error 2019-02-04 17:37:22 +01:00
Dale HuiandGitHub 598addc6d7 Merge pull request #166 from bcho/fix/sqlite3-migration-table
sqlite3: fix wrong schema table usage
2019-02-02 11:19:58 -08:00
hbc 95e701da6a sqlite3: fix wrong schema table usage
fixes: https://github.com/golang-migrate/migrate/issues/165
2019-02-02 13:22:40 +08:00
Dale HuiandGitHub f6d624c729 Merge pull request #150 from tsenart/atomic-migrations-table-creation
postgres: Make `ensureVersionTable` atomic
2019-01-15 11:55:50 -08:00
Dale Hui 5ad19c5071 Run dep ensure
- Updates Gopkg.lock after github.com/hashicorp/go-multierror package was manually added to Gopkg.toml
2019-01-15 11:33:17 -08:00
Tomás Senart 41a595f62a fixup! Add github.com/hashicorp/go-multierror@v1.0.0 to Gopkg.yaml 2019-01-15 11:28:49 +01:00
Tomás Senart f58da1d4dc fixup! More explicit conditionals 2019-01-15 11:28:49 +01:00
Tomás Senart b57dbceec3 postgres: Revert back to use CREATE IF NOT EXISTS 2019-01-15 11:28:49 +01:00
Tomás Senart 06c80ae2da postgres: Use multierror.Error in ensureVersionTable 2019-01-15 11:28:49 +01:00
Tomás Senart 0beddcd2d0 postgres: Be explicit about max idle and open conns 2019-01-15 11:28:48 +01:00
Tomás Senart 94f5aa00fc postgres: Query table existance in ensureVersionTable
This commit reverts back to querying the existance of the table instead
of using CREATE IF NOT EXISTS because we want to support versions of
Postgres older than 9.1 which is when this feature was introduced.
2019-01-15 11:28:48 +01:00
Tomás Senart 5bf80d7ef2 postgres: Add comment about connection pool in TestWithInstance_Concurrent 2019-01-15 11:28:48 +01:00
Tomás Senart 09a1959309 postgres: Preserve Unlock error when outer err isn't nil 2019-01-15 11:28:48 +01:00
Tomás Senart 15713e64cb postgres: Use sync.WaitGroup in TestWithInstance_Concurrent 2019-01-15 11:28:48 +01:00
Tomás Senart 519dae2639 postgres: Make ensureVersionTable atomic
Fixes https://github.com/golang-migrate/migrate/issues/55
2019-01-15 11:28:48 +01:00
Tomás Senart ce7a2234ee fixup! Pass config to WithInstance 2019-01-15 11:28:48 +01:00
Tomás Senart a06a92d620 postgres: TestWithInstance_Concurrent 2019-01-15 11:28:47 +01:00
Dale Hui 1ca4da9a33 Run go mod tidy 2019-01-14 12:56:51 -08:00
Dale Hui 52fc08fdf5 Update supported DB versions 2019-01-12 16:13:26 -08:00
Dale Hui 2feaaaddda Update dktest from v0.2.0 to v0.3.0
- Update ReadyFuncs accordingly
2019-01-08 22:39:26 -08:00
Dale Hui 9870a84b99 Update Cassandra DB driver (gocql) 2019-01-08 11:25:23 -08:00
Dale Hui bbec63fda7 Use one of the 2 ports exposed by MySQL Docker images
- MySQL driver tests should pass more consistently now
2019-01-08 05:11:07 -08:00
Dale Hui 809c7f0ac8 Use dktest to run docker tests
- Leaving migrate/testing in case there are unknown consumers
    - Add migrate/dktesting package
    - Update tests to use migrate/dktesting instead of migrate/testing
2019-01-08 05:11:07 -08:00
Dale Hui fd47ba9a9a Remove TODO for MongoDB database driver 2019-01-08 05:01:48 -08:00
Dale HuiandGitHub b1a199a272 Merge pull request #146 from bobrovde/mongodb-driver
added mongodb driver
2019-01-08 02:34:59 -08:00
Dale HuiandGitHub fc8d57d128 Merge branch 'master' into mongodb-driver 2019-01-07 22:50:51 -08:00
Dale HuiandGitHub f24ea4d6c9 Update TravisCI badge after migration from .org to .com 2019-01-07 22:45:35 -08:00
DBobrov 12b93a3a07 Change checking of mongo replica set initialization 2019-01-07 15:17:35 +03:00
DBobrov 3fa0df7553 Refactor mongodb transactions test 2019-01-07 11:47:37 +03:00
DBobrov 13978a108a Add test for migrations in transaction mode 2019-01-06 12:45:22 +03:00
Dale Hui d549fd5265 Fix memory leak in tests
- Stop leaking tickers. As the docs state, using time.Tick() will leak tickers,
      so use time.NewTicker() with time.Ticker.Stop().
    - Be more efficient by using time.NewTimer() with time.Timer.Stop() instead of time.After().
2019-01-05 13:52:25 -08:00
DBobrov 30d2d94a22 Fix review comments 2019-01-05 11:10:25 +03:00
Dale Hui caa5719a86 Update pinned (via dep) MySQL driver from v1.4.0 to v1.4.0 2019-01-04 17:47:53 -08:00
DBobrov 436b81b357 Replaced bsonx.Doc with bson.D 2019-01-04 22:01:21 +03:00
Dale Hui 600f2eda93 Fix broken build
- `gem install` no longer recognizes the `--no-ri` and `--no-rdoc` options. Use `--no-document` instead.
    - References:
        - https://github.com/rubygems/rubygems/pull/2354
        - https://github.com/bundler/bundler/pull/6624
2019-01-03 16:35:22 -08:00
DBobrov 309edb6da6 - Add dependency for dep
- Add transaction mode for migrations
2019-01-03 18:00:23 +03:00
DBobrov bcd4f6e7dd added mongodb driver 2019-01-01 11:14:27 +03:00
Dale HuiandGitHub fd50054781 Merge pull request #145 from bobrovde/fix-clickhouse
Changed clickhouse migrations table struct
2018-12-30 13:42:39 -08:00
DBobrov 26064905d4 Changed migrations table struct of field version to fix support database.NilVersion and default version format. 2018-12-28 15:53:55 +03:00
Dale Hui 24176463f4 Update docker/docker checksum for Go 1.11.4
Addresses: https://github.com/golang-migrate/migrate/issues/138
2018-12-21 12:53:53 -08:00
Dale HuiandGitHub 418d41c2a1 Merge pull request #143 from coolaj86/url-opaque
use URL.Opaque when available
2018-12-20 21:37:54 -08:00
AJ ONeal 01291f7cb5 use URL.Opaque when available 2018-12-20 15:08:49 -07:00
Dale HuiandGitHub e8b8975ec3 Merge pull request #141 from fsouza/upgrade-mysql-driver
Upgrade go-sql-driver/mysql to v1.4.1
2018-12-19 11:31:31 -08:00
Francisco Souza 6a53d7d10d Upgrade go-sql-driver/mysql to v1.4.1
Fixes #139.
2018-12-18 18:01:52 -05:00
Dale HuiandGitHub 65870ff3e8 Merge pull request #126 from a-h/patch-1
Include details about migration locking in the FAQ
2018-12-14 17:36:07 -08:00
6f1479fa41 Update FAQ.md
Co-Authored-By: a-h <a-h@users.noreply.github.com>
2018-12-14 16:28:04 +00:00
Dale Hui f694b616aa Add supported major versions table to README
- Consolidates info about migrate major versions
    - https://github.com/golang-migrate/migrate/issues/134#issuecomment-446664565
2018-12-13 22:53:07 -08:00
Dale Hui 1bd41c2a0b README copy tweak
- helpers -> snippets
2018-12-13 22:41:26 -08:00
Dale HuiandGitHub f1d88ac6c1 Add guide for installing a specific version of migrate using the Go toolchain 2018-11-28 01:00:46 -08:00
Dale HuiandGitHub 0d5d918563 Fix CLI install with Go toolchain docs
`-d` option shouldn't be specified with `go get` when installing
2018-11-28 00:44:59 -08:00
Dale Hui 547bc41026 Merge branch 'deb' 2018-11-27 13:54:07 -08:00
Dale Hui d27dc63026 Move before_deploy steps to after_success to avoid re-building the binary before every deploy
- https://docs.travis-ci.com/user/job-lifecycle/#deploying-your-code
2018-11-27 13:48:57 -08:00
Dale Hui 83f5b9f644 Don't need to move and remove files before caching build data in Travis 2018-11-27 13:48:57 -08:00
Dale Hui addb3c1a79 .deb package changes
- Install binary to /usr/local/bin instead of /usr/bin
    - Support recent/stable Debian and Ubuntu distros that work with .deb packages
2018-11-27 13:48:57 -08:00
Dale HuiandGitHub 88e7eafcf9 Merge pull request #131 from vearutop/cli-layout
Refactor cli package to allow `go get` one-liner with a proper binary name
2018-11-27 12:15:59 -08:00
Viacheslav Poturaev 5f88d4bf22 Copy cli main package under migrate directory, keep deprecated original main package for backwards compatibility, fixes #70 2018-11-21 09:01:35 +01:00
Dale HuiandGitHub 7f00868584 Merge pull request #128 from andrei-m/fix-redshift
Fix Redshift migrations driver
2018-11-14 10:32:07 -08:00
Dale Hui 6ebc6c7dae Unlock in postgres db driver should also use the schema name 2018-11-14 02:18:57 -08:00
Dale Hui f38fe38d45 Use git describe --tags to set the version number when building the CLI 2018-11-12 02:23:08 -08:00
Dale Hui 323427e951 Update copyright in LICENSE 2018-11-08 01:45:19 -08:00
Dale Hui 06d3ebde1c Use strings.NewReader() instead of bytes.NewReader() when source is a string
- Group imports
2018-11-05 22:50:08 -08:00
Dale HuiandGitHub 1df8057f97 Merge pull request #127 from vporoshok/master
Add support for multi-schema migrations in Postgres
2018-11-05 22:34:54 -08:00
Evgeniy Bastrykov d2d449ad78 Review comments 2018-11-05 16:03:54 +04:00
Evgeniy Bastrykov 16d63e3a76 Add support for multi-schema migrations in Postgres
There is lock conflict on parallel migrations in different postgres
schemas. To avoid this conflicts function GenerateAdvisoryLockId added
variadic params to change lock id with schema name. Schema name taked
with postgres CURRENT_SCHEMA function. Null byte used as separator
between database and schema name, because any other symbol may be used
in both of it.

Closes #118
2018-11-05 12:56:38 +04:00
Andrei Mackenzie cc573a74dc Remove unused errors 2018-11-03 19:27:55 -04:00
Andrei Mackenzie 1d54cf5f18 Replace 'redshift' with 'redshift2'
This addresses https://github.com/golang-migrate/migrate/issues/90 . The
exported Redshift object no longer exports an embedde 'Driver' however,
so some more work is needed to make this backwards compatible.
2018-11-03 19:01:37 -04:00
Andrei Mackenzie 4e098f74cd Use 'DELETE FROM' over 'TRUNCATE' in Redshift
TRUNCATE commits the current transaction, which breaks the expection of
the 'Commit()' that follows.

See:
https://github.com/golang-migrate/migrate/issues/90
https://docs.aws.amazon.com/redshift/latest/dg/r_TRUNCATE.html
2018-11-03 18:52:09 -04:00
Andrei Mackenzie b0300df5e7 Use the most Redshift-like Postgres version available on Dockerhub
Redshift is based on Postgres version 8.0.2.
2018-11-03 18:49:31 -04:00
Andrei Mackenzie 9c132fe622 Update README with Redshift-specific info 2018-11-03 18:47:12 -04:00
Andrei Mackenzie 22334834ac Support the 'redshift' URL scheme
This brings the 'redshift2' package in alignment with the existing
'redshift' package.
2018-11-03 18:44:11 -04:00
Andrei Mackenzie 3373343f23 Remove advisory locking from the Redshift implementation
Redshift does not support advisory lock functions. The closest
capability is in-transaction table locks, which aren't quite right here
because the transaction scope is established within SetVersion, not
higher up above the Lock-before/Unlock-after SetVersion.

Local locking is left intact to satisfy expected "can't Lock twice
before Unlocking" behavior asserted in shared tests.
2018-11-03 18:31:41 -04:00
Andrei Mackenzie 1d0bedc454 Rename 'Postgres' to 'Redshift' in 'redshift2' 2018-11-03 18:09:53 -04:00
Andrei Mackenzie 8381ea0307 Clone the 'postgres' driver as 'redshift2'
Avoid stepping on the 'redshift' driver for the time being. Driver
registration is also modified to identify the clone as 'redshift2'
rather than 'postgres'.
2018-11-03 18:04:22 -04:00
Adrian HeskethandGitHub 2e0b820637 Include details about migration locking in the FAQ 2018-11-02 15:41:41 +00:00
Dale HuiandGitHub 9f5e1bd505 Merge pull request #124 from lopezator/master
cli: Update README.md with MacOS install info
2018-10-29 11:02:56 -07:00
Dale Hui 7db5c9a274 Remove references to dep from docs 2018-10-29 10:56:55 -07:00
David López 4fad212a25 cli: Update README.md with MacOS install info
Document in MacOS install section of README.md that brew packag is now available. References mattes/migrate#156 https://github.com/Homebrew/homebrew-core/pull/30032
2018-10-29 17:27:48 +01:00
Dale HuiandGitHub f6b3bf548d Merge pull request #121 from wmetaw/master
Add NewDB to spanner.go
2018-10-28 16:58:22 -07:00
Ryo Takashima ca888b96e3 Add NewDB to spanner.go 2018-10-28 22:00:05 +09:00
Dale HuiandGitHub 2fc1610248 Merge pull request #115 from weszeloos/add_support_for_tls_in_cassandra
Add support for TLS on Cassandra.
2018-10-16 15:09:30 -07:00
Wessel Oosthuizen bc28cee1dc Add support for TLS on Cassandra. 2018-10-16 11:18:38 +02:00
Dale Hui ea1928c904 Update dependencies 2018-10-15 13:26:18 -07:00
Dale HuiandGitHub 8c3f34e0c9 Merge pull request #114 from albenik/master
github.com/docker/docker package updated to api v1.37
2018-10-15 13:09:43 -07:00
Veniamin Albaev 956441176f github.com/docker/docker package updated to api v1.37
To avoid github.com/sirupsen/logrus package name case collision with go mod
2018-10-15 17:39:43 +03:00
Dale Hui 83d01b7e1c Run go mod tidy 2018-10-10 16:16:23 -07:00
Dale Hui fc2b2e9c64 Use v4 for Go module support 2018-10-10 16:16:23 -07:00
Dale Hui 60b45ba428 Revert "Docker builds need to use dep to manage dependencies"
This reverts commit 4937cd088f.
2018-10-10 16:16:23 -07:00
Dale Hui 107db3f407 Revert "Remove support for Go modules"
This reverts commit 5e96539f55.
2018-10-10 16:15:33 -07:00
Dale Hui 4937cd088f Docker builds need to use dep to manage dependencies 2018-10-10 16:11:44 -07:00
Dale Hui 5e96539f55 Remove support for Go modules 2018-10-10 13:34:03 -07:00
Dale HuiandGitHub 708d67fa02 Merge pull request #112 from xiamengyu/master
Fix typo in comments
2018-10-10 13:26:36 -07:00
xiamengyu1 a95b13d804 Fix typo 2018-10-10 16:26:27 +08:00
Dale HuiandGitHub 1e60261d3f Merge pull request #110 from MikeFitzgerald/master
Fix postgres drop
2018-10-04 00:30:33 -07:00
Mike Fitzgerald 0d4f1c0212 Fix postgres drop 2018-10-04 01:22:39 -04:00
Dale HuiandGitHub 4d30c4c87f Add Go version to but report template 2018-09-19 03:10:08 -07:00
Dale Hui 6c02d65563 Update commented out imports to use v3 2018-09-14 14:12:07 -07:00
Dale Hui 16dea48c82 Fix module support by opting in to modules 2018-09-14 01:14:42 -07:00
Dale Hui f517029483 Test latest version of Go as well, but allow failures 2018-09-13 16:14:43 -07:00
Dale Hui 6f233b3b58 Update dependencies 2018-09-13 16:14:43 -07:00
Dale HuiandGitHub f8c85735f4 Merge pull request #98 from Kay-Zee/Kay-Zee/close-sql-db-connections-on-driver-close
Add sql.DB to Postgres and Mysql structs to allow closing in Close()
2018-09-07 01:26:43 -07:00
Kay-Zee 1e4c50b70d Add sql.DB to Postgres and Mysql structs to allow closing in Close() 2018-09-06 17:16:02 -07:00
Dale Hui 93d53a5ae8 Use Go 1.11 (with modules enabled on alpine linux 3.8) for docker builds 2018-09-04 22:18:49 -07:00
Dale Hui 16f2b1736e Support Go 1.11 and drop support for Go 1.9
- Support modules in Go 1.11
2018-09-04 19:11:19 -07:00
Dale Hui 4d60607b0b Update issue templates 2018-08-29 12:14:54 -07:00
Dale Hui beb09d85fa Upgrade dep from v0.4.1 to v0.5.0 2018-08-15 12:08:25 -07:00
Dale HuiandGitHub 8ed902364c Merge pull request #91 from stabacov/master
Fixes MySQL custom TLS connection failure.
2018-08-15 00:03:14 -07:00
Tatsuhito KATO 02c83fb38b more descriptive. 2018-08-15 02:42:18 +00:00
Tatsuhito KATO 9049e49f9e x-tls-cert and x-tls-key must set both. 2018-08-14 07:43:14 +00:00
Tatsuhito KATO 9d36dd9841 Add detail of tls query. 2018-08-14 07:42:10 +00:00
Tatsuhito KATO 9905791932 Fixes MySQL custom TLS connection failure. 2018-08-13 11:43:19 +00:00
Dale Hui dd00ca926e Add support for postgres 10 and drop support for postgres 9.2
https://www.postgresql.org/support/versioning/
2018-08-12 15:54:21 -07:00
Dale HuiandGitHub 4092e160a4 Merge pull request #85 from HaraldNordgren/improve_tests
Update tests for 'Migrate', 'Steps' and 'UpAndDown' to assert exact migration sequence
2018-08-08 15:57:04 -07:00
Dale HuiandGitHub 6092802b3a Merge pull request #88 from HaraldNordgren/error_output
Improve error output for missing or malformed '-database' and '-source' parameters
2018-08-08 15:24:39 -07:00
Harald Nordgren 42f074062b Update tests for 'Migrate', 'Steps' and 'UpAndDown' to assert exact migration sequence 2018-08-08 22:24:37 +02:00
Harald Nordgren 112191042b Improve error output for missing or malformed '-database' and '-source' parameters 2018-08-08 21:27:12 +02:00
Dale Hui 1566dd06cd Pass the error through instead of overriding it 2018-08-07 16:22:05 -07:00
Dale HuiandGitHub 251bf76be4 Merge pull request #89 from HaraldNordgren/improve_documentation
Document that '-source' is a required parameter
2018-08-07 15:59:20 -07:00
Harald Nordgren a6eddccfe5 Document that '-source' is a required parameter 2018-08-07 09:53:36 +02:00
Dale Hui 3bd43bedad Reference mattes/migrate now that golang-migrate/migrate has been detached
https://github.com/golang-migrate/migrate/issues/81
2018-08-07 00:17:39 -07:00
Dale HuiandGitHub 4750e57ce1 Merge pull request #82 from UnitedTraders/clickhouse-multiline
Add support for the multiline clickhouse queries
2018-08-06 04:09:10 -06:00
Anton Markelov df7ce674e9 add support for the multiline clickhouse queries 2018-08-03 16:08:47 +10:00
Dale Hui e968e8a5f0 Merge branch 'update_pkgs' 2018-07-24 18:30:53 -07:00
Dale HuiandGitHub c8705dd876 Merge pull request #78 from dhui/mysql_dsn
Allow DB connection URLs to contain encoded reserved URL characters
2018-07-24 18:22:04 -07:00
Dale Hui c7a29e331f Another round of package updates couldn't hurt... 2018-07-24 18:19:22 -07:00
Dale Hui ffcbbd5fea Override dependency on docker/distribution to fix build
https://github.com/golang/dep/issues/1890#issuecomment-404036309
2018-07-24 18:13:11 -07:00
Dale Hui ebf9c0bf91 Update package dependencies 2018-07-24 18:13:11 -07:00
Dale Hui df658e8fa8 Don't urlencode the username and password for MySQL
Addresses: https://github.com/golang-migrate/migrate/pull/69
2018-07-24 17:56:17 -07:00
Dale Hui b5edb8e50c Improve error messaging when URL parsing fails
- Improve docs around escaping/encoding reserved URL characters
    - Add tests for net/url Parse() and reserved characters to document understanding
    - Addresses: https://github.com/golang-migrate/migrate/issues/77
2018-07-24 17:55:58 -07:00
Dale Hui 97eb0d191b Fix broken GenerateAdvisoryLockId() test
- Add it to the test suite to ensure that it doesn't break again
2018-07-24 17:55:58 -07:00
Dale Hui 857d7a620d Revert "Merge pull request #69 from bcho/fix/mysql-url-password-encode"
This reverts commit 78d696c1e5, reversing
changes made to 18583d5a91.
2018-07-24 17:55:58 -07:00
Dale HuiandGitHub fad64ed381 Merge pull request #76 from agquick/master
Support for multiple statements in Cassandra cql migration files
2018-07-24 16:02:44 -07:00
Alan Graham 4244ebea6a Add detail to README for Cassandra flag, improve if block 2018-07-24 19:30:44 +00:00
Alan Graham 327822c29a Added Cassandra URL flag to enable multi statement migrations 2018-07-20 13:27:54 +00:00
Dale HuiandGitHub feea45c205 Merge pull request #75 from LUSHDigital/master
Lock go-sql-driver/mysql to v1.4.0
2018-07-17 16:42:01 -07:00
Alan Graham c4b81d1210 Support for multiple statements in Cassandra cql migration files based on semi-colon and newline. 2018-07-17 15:19:54 +00:00
Dan Richards 3988074cae Lock digest. 2018-07-16 12:09:29 +01:00
Dan Richards ad2b69e115 Typo 2018-07-16 12:08:24 +01:00
Dan Richards f9eb960848 Just lock go-sql-driver/mysql. 2018-07-16 12:06:27 +01:00
Dan Richards 5927d6e5f4 Locked go-sql-driver/mysql to v1.4.0 2018-07-16 12:01:52 +01:00
Dale Hui 6bb3e494e5 Disable CGO to build binaries statically on the building platform
- Previously, only cross-compiled binaries were built statically
    - Addresses: https://github.com/golang-migrate/migrate/issues/45
2018-07-14 13:31:59 -07:00
Dale Hui 5d5d15e7a3 Link to comparison of transactional DDLs 2018-07-09 19:25:29 -07:00
Dale Hui b742ec3de4 Use unix line endings 2018-07-09 19:21:40 -07:00
Dale Hui 75f0aade0d Reduce docker image size by removing unnecessary chown
https://github.com/moby/moby/issues/5505
2018-07-09 14:52:59 -07:00
Dale HuiandGitHub 3872c8f41d Merge pull request #71 from qdentity/postgres-parse-error
Add postgres lib/pq error parsing
2018-07-09 12:26:24 -07:00
Damir Vandic ae2fd4fa7b Support postgres non-ascii error parsing 2018-07-09 11:39:29 +02:00
Damir Vandic 3eb26a65d3 Add postgres lib/pq error parsing 2018-07-07 12:17:38 +02:00
Dale Hui 157433893c Update CLI installation docs to reduce confusion
https://github.com/golang-migrate/migrate/issues/64#issuecomment-402300146
2018-07-05 10:55:44 -07:00
Dale Hui 236dac0c96 Add latest tag to docker image during build 2018-07-03 14:09:26 -07:00
Dale HuiandGitHub 78d696c1e5 Merge pull request #69 from bcho/fix/mysql-url-password-encode
prevent net/url encoding the user password
2018-06-29 14:17:45 -07:00
Chao Dengandhbc 48a4062f75 prevent net/url encoding the user password 2018-06-29 21:07:10 +08:00
Dale Hui 18583d5a91 Update docs
- Addresses: https://github.com/golang-migrate/migrate/issues/67
    - Add badge for supported Go versions
    - Suggest dep for dependency management
2018-06-27 11:45:54 -07:00
Dale HuiandGitHub 642913dbf4 Merge pull request #66 from dadlerj/cli-readme
Update CLI usage in readme
2018-06-26 13:25:18 -07:00
dadlerj 2ca5473e52 Update CLI usage in readme 2018-06-26 09:16:04 -07:00
Dale Hui 6070aa8281 Push latest image to docker hub 2018-06-19 01:42:37 -07:00
Dale HuiandGitHub 78dc468555 Merge pull request #47 from dhui/docker_badge
Add badge for num docker pulls
2018-06-19 01:33:06 -07:00
Dale Hui bcd996f3df Use environment variables since there's no way to pass secure data to deploy script provider 2018-06-19 00:39:54 -07:00
Dale Hui a1d959d0e9 Use latest version of docker 2018-06-18 23:07:54 -07:00
Dale Hui 71e97bccc7 Fix TravisCI conditional release 2018-06-18 21:42:31 -07:00
Dale Hui 3239b18671 Fix time format flag to be backwards compatible
https://github.com/golang-migrate/migrate/pull/17#issuecomment-397960867
2018-06-18 00:06:17 -07:00
Dale Hui c3794da4ed Make -ext flag required for the create command 2018-06-17 23:18:23 -07:00
Dale Hui 872a561495 Fix broken CLI build 2018-06-17 23:16:38 -07:00
Dale Hui d1adc8b15f Add golang/x/tools (godoc/vfs) as a dependency 2018-06-17 19:59:39 -07:00
Dale HuiandGitHub e7d20b35ee Merge pull request #57 from SamWhited/vfs
source/vfs: add virtual file system source
2018-06-17 19:22:06 -07:00
Dale Hui 21f566a60a Add docker hub credentials for uploading docker image 2018-06-17 17:42:03 -07:00
Sam Whited 7562e9ad5d source/vfs: add virtual file system source
Fixes #56
2018-06-17 19:35:13 -05:00
Dale Hui 0f16f7f6a2 Rename sources to reflect updated build constraint names 2018-06-12 16:45:56 +08:00
Dale Hui dfa9fc05cb Use correct build contraints (must be alphanumeric)
Fixes: https://github.com/golang-migrate/migrate/issues/52
2018-06-12 16:18:27 +08:00
Dale Hui c4e855b58c List source and database drivers in help text 2018-06-12 16:17:52 +08:00
Dale Hui 0d9f636356 Use dep to build docker image 2018-06-11 19:18:05 +08:00
Dale HuiandGitHub 387f79be13 Merge pull request #48 from dhui/static_build
Build binaries statically
2018-06-04 09:24:35 -07:00
Dale Hui 87f3e4a910 Build binaries statically
Addresses: https://github.com/golang-migrate/migrate/issues/45
2018-06-04 19:27:37 +08:00
Dale Hui 01ea635328 Add badge for num docker pulls 2018-06-04 18:54:43 +08:00
Dale HuiandGitHub 3d1bb8a4b4 Merge pull request #13 from Teddy-Schmitz/postgres-wait
postgres driver should wait for lock
2018-05-31 19:04:30 -07:00
Teddy Schmitz 046d1cb767 postgres driver should wait for lock 2018-06-01 09:11:24 +08:00
Dale Hui 3d7b06e24c Override google cloud package version (was pinned to v0.19.0 by github.com/fsouza/fake-gcs-server) 2018-05-31 15:26:18 -07:00
Dale Hui 063b69db2f Update OpenCensus dependency 2018-05-31 15:05:31 -07:00
Dale Hui c0b0829d5a Use "vanity" import to allow forks to build
https://docs.travis-ci.com/user/languages/go/#Go-Import-Path
2018-05-31 14:57:38 -07:00
Dale Hui 3cf42dd36d Don't need to re-solve package dependencies every time. Only use Gopkg.lock
Reference: https://github.com/golang/dep/blob/master/docs/ensure-mechanics.md#-no-vendor-and--vendor-only
2018-05-31 14:57:38 -07:00
Dale Hui 89e558ee4f Don't cache go source since dependencies are managed by dep now 2018-05-31 14:57:38 -07:00
Dale Hui f277ef7428 Update github.com/go-sql-driver/mysql and github.com/gocql/gocql 2018-05-31 14:57:38 -07:00
Dale Hui b7647a749e Use the master branch for go-sql-driver/mysql since there hasn't been a release in a yr and a half 2018-05-31 14:57:38 -07:00
Dale Hui 4cc6aeb274 Update gocql package 2018-05-31 14:57:38 -07:00
Dale Hui 3de903f51f Use latest minor revision of Go 1.10 in TravisCI 2018-05-31 14:57:38 -07:00
Dale Hui 4b4f49b1eb Update docs regarding usage of dep 2018-05-31 14:57:38 -07:00
Dale Hui 4407720b95 Remove "deps" target from Makefile 2018-05-31 14:57:38 -07:00
Dale Hui 36dc939f91 Use dep for TravisCI builds 2018-05-31 14:57:38 -07:00
Dale Hui 40cd326ed7 Use dep to manage dependencies 2018-05-31 14:57:38 -07:00
Dale Hui a890d44d2f CockroachDB driver improvements
- Correctly return database.ErrLocked when the DB is locked
    - Group imports
    - gofmt goodness
2018-05-31 14:44:48 -07:00
Dale HuiandGitHub c98426505e Merge pull request #43 from wmetaw/master
Fixed unkeyed fields in file.go for GoogleAppEngine
2018-05-31 14:23:14 -07:00
Ryo Takashima 6fa8039b55 Fixed unkeyed fields for GoogleAppEngine 2018-05-31 15:40:15 +09:00
Dale HuiandGitHub faf4307fcc Merge pull request #32 from kenjones-cisco/feature/cassandra-withinstance
Minor tweaks to remove duplication
2018-05-17 01:01:47 -07:00
kenjones 1512e41e41 Minor tweaks to remove duplication
Adds missing connection close for cassandra tests

Revert to default timeout of 600ms
2018-05-16 20:09:12 -04:00
Dale Hui 55a25c5e0e Cassandra DB driver test improvements
- shorten connect timeout to allow more connection retry attempts
    - close session after it's created
    - group imports
2018-05-16 15:12:36 -07:00
Dale HuiandGitHub f815731412 Merge pull request #30 from Decemberlabs/master
Add WithSession helper for Cassandra driver
2018-05-16 14:26:12 -07:00
Andrés Rodríguez d607064fd2 Switch Cassandra's package lock to instance lock 2018-05-16 16:26:23 -03:00
Andrés Rodríguez 8b0f876416 Rename Cassandra's WithSession to WithInstance 2018-05-16 16:23:21 -03:00
Andrés Rodríguez bbd8f410d9 Correct method name in comment 2018-05-16 10:54:56 -03:00
Dale Hui fb3d809e81 Cleanup CLI docs 2018-05-12 15:25:19 -07:00
Dale HuiandGitHub 95db494b30 Merge pull request #28 from tayhalla/patch-1
Update README.md
2018-05-12 14:58:12 -07:00
Taylor HallidayandGitHub 5ebdb74ba8 Update README.md
There's no homebrew support. Even the referenced TODO says so, so not sure why it's in the Readme. Let's encourage others to support the development of it rather than think the support for it is broken.
2018-05-12 07:22:21 -07:00
Dale HuiandGitHub 07c20c5357 Merge pull request #27 from Its-Alex/master
Fix dockerfile version and deps
2018-05-10 01:13:05 -07:00
ItsAlex 31daa8dad3 fix dockerfile version and deps
Signed-off-by: Alexandre MARRE <me@itsalex.fr>
2018-05-10 09:12:23 +02:00
Dale HuiandGitHub a0b03c3a68 Merge pull request #24 from Its-Alex/master
Dockerization
2018-05-09 11:22:34 -07:00
ItsAlex fb7bb4f87a add dockerization Fix #23
Signed-off-by: Alexandre MARRE <me@itsalex.fr>
2018-05-09 09:48:20 +02:00
Dale HuiandGitHub 81e9fff605 Merge pull request #22 from thurt/add-ref-to-github
add ref (SHA, branch, or tag) to the github migration source
2018-05-04 10:13:18 +02:00
thurt e6baca2ffb add ref (SHA, branch, or tag) to the github migration source 2018-05-04 03:16:12 +00:00
Dale HuiandGitHub 2b28a0bc0a Merge pull request #17 from bobmeister/cli_create_yyyymmddhhmmss_migration_files
-datetime option to create files in yyyymmddhhmmss format
2018-04-17 10:50:48 -07:00
Robert Haupt 1f389f1422 Removed commented code 2018-04-17 11:17:10 -06:00
Robert Haupt 8cdee40695 Use Go time format string if version other than timestamp is desired. 2018-04-17 11:09:08 -06:00
Robert Haupt c6c6874d72 Added -datetime option to create up/down files with version in yyyymmddhhmmss format. 2018-04-16 11:12:06 -06:00
Andrés Rodríguez 78c47074a3 Add WithSession helper for Cassandra driver
Also changes method receivers names for the Cassandra struct to "c"
since "p" makes no sense in this context.
2018-03-13 15:49:08 -03:00
Dale Hui 22f249514d Merge branch 'postgres-lock-fix'
Addresses: https://github.com/golang-migrate/migrate/pull/4
2018-02-20 16:12:54 -08:00
Dale HuiandGitHub 8f6826c9e3 Merge pull request #8 from rdallman/fix-lockyloo-golang
Fix lockyloo golang
2018-02-20 15:51:18 -08:00
Dale Hui 1519c59188 Rename Postgres.db to Postgres.conn 2018-02-20 15:40:20 -08:00
Dale HuiandGitHub a9176e9969 Merge branch 'master' into fix-lockyloo-golang 2018-02-20 15:37:20 -08:00
Dale Hui ab7dfb38ac Merge remote-tracking branch 'origin/master' into postgres-lock-fix 2018-02-20 15:32:34 -08:00
Dale Hui aa21a21647 Support Go 1.10 and drop support for 1.8
- Test using latest minor versions of the last 2 major (supported) releases
        - TravisCI's gimme doesn't support Go 1.10.x until a minor version is released
    - Explicitly use YAML strings so Go versions aren't interpreted as numbers
2018-02-20 15:20:43 -08:00
Reed Allman 094bad431e s/db/conn/ 2018-02-09 15:14:05 -08:00
Reed Allman a8ef2b8cfe update CI and build tags to go1.9 2018-02-09 00:32:47 -08:00
Reed Allman 636c911d33 fixes mysql lock failure
I believe this closes #297 as well.

I have been working on adding testing of migrations and it requires acquiring
the lock in mysql multiple times to go up and down. After nailing this down to
GET_LOCK returning a failure for every subsequent GET_LOCK call after the
first, I decided it was time to rtfm and lo and behold:

https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_release-lock

RELEASE_LOCK will not work if called from a different thread than GET_LOCK.
The simplest solution using the golang database/sql pkg appears to be to just
get a single conn to use for every operation. since migrations are happening
sequentially, I don't think this will be a performance hit (possible
improvement). now calling Lock() and Unlock() multiple times will work;
prior to this patch, every call to RELEASE_LOCK would fail. this required
minimal changes to use the *sql.Conn methods instead of the *sql.DB methods.

other changes:

* upped time out to 10s on GET_LOCK, 1s timeout can too easily leave us in a
state where we think we have the lock but it has timed out (during the
operation).
* fixes SetVersion which was not using the tx it was intending to, and fixed a
bug where the transaction could have been left open since Rollback or Commit
may never have been called.

I added a test but it does not seem to come up in the previous patch, at least
easily, I tried some shenanigans. At least, this behavior should be fixed with
this patch and hopefully the test / comment is advisory enough.

thank you for maintaining this library, it has been relatively easy to
integrate with and most stuff works (pg works great :)
2018-02-09 00:27:01 -08:00
Dale Hui 53dd06057f Add release badge 2018-01-21 02:07:25 -08:00
Dale Hui 1b840d9c79 Remove unused invalid formatter 2018-01-21 02:04:36 -08:00
Dale Hui 012f051f2e Use cached badges when possible and make badges reflect the state of the master branch 2018-01-21 01:40:41 -08:00
Dale Hui 8684cad808 Update README to reflect the current status of the sqlite driver 2018-01-21 00:20:10 -08:00
Dale Hui 15611d4559 Fix cli builds
- Removed sqlite3 support from binaries
        - It was never working anyways: https://github.com/mattes/migrate/issues/244
    - Don't use cgo as is requires a cross-compiler to build for other platforms
        - cgo was originally added for this reason: https://github.com/mattes/migrate/pull/238#issuecomment-306291988
2018-01-20 23:59:36 -08:00
Dale Hui d542e144dc Manually fetch github.com/kshvakov/clickhouse 2018-01-20 10:30:57 -08:00
Dale Hui f8fd159fbc Fork packagecloud releases 2018-01-20 02:10:22 -08:00
Dale Hui 2eb9570b52 Build release binaries (using G 1.9) and upload to Github using new key 2018-01-20 01:49:02 -08:00
Dale Hui f5ae4b42db Support creating migrations using sequences in addition to timestamps 2018-01-20 00:29:06 -08:00
Dale Hui 24dd870559 MySQL and Postgres db driver test improvements
- Log unexpected connection errors while waiting for the docker image to start
    - Don't leave connections open
2018-01-20 00:11:35 -08:00
Dale Hui 97ef1b8393 Remove redundant delay since the readyFn is sufficient 2018-01-20 00:11:35 -08:00
Dale Hui bdb332a5f8 Remove unused slowReader 2018-01-19 22:38:32 -08:00
Dale Hui 8cbdc28e66 Source code needs to be kept for goveralls to use but removed before caching in travis-ci 2018-01-19 20:53:20 -08:00
Dale Hui 4ebf080da8 Speed up travis-ci builds by caching dependencies 2018-01-19 20:32:15 -08:00
Dale Hui 53606261bf Make coverage directory configurable 2018-01-19 20:32:15 -08:00
Dale Hui 50d049d5f5 Only use coverage profiles from test runs to create combined coverage profile.
- Prevent issue where the combined coverage profile is combined with itself
2018-01-19 18:04:46 -08:00
Daniel CormierandDale Hui 395391525b Changed source.Up from untyped to a typed const
Changed source.Up from being an untyped string const to being a typed source.Direction const.
2018-01-19 16:24:25 -08:00
Dale Hui 59f3f6d6c2 Increase test timeout from default 10m to 20m since some tests are slow as molasses in January
- Example build being killed by Go's test binary: https://travis-ci.org/golang-migrate/migrate/jobs/331027558
2018-01-19 15:42:02 -08:00
Dale Hui 12cc536541 Drop support for Go 1.7
- Only the latest 2 major release version is supported. https://golang.org/doc/devel/release.html#policy
2018-01-19 15:36:13 -08:00
Dale Hui 51ec421ad8 Remove debugging print from mysql driver test 2018-01-19 15:13:07 -08:00
Dale Hui 1cf095c51d Update more references to github.com/mattes/migrate 2018-01-19 14:59:27 -08:00
Dale Hui 7c9a261dc0 Fix MySQL docker image connection testing 2018-01-19 14:50:31 -08:00
Dale Hui 31367fbc97 Add tip about dealing with docker client API version issues 2018-01-19 14:31:56 -08:00
Dale Hui 7159f40304 Update all *DockerContainer receivers to handle nil
- Use errors.New() instead of fmt.Errorf() if no formatter string is needed
    - Spacing changes due to `go fmt`
2018-01-19 14:22:55 -08:00
Dale Hui 042e307bef Ignore vendor dir 2018-01-19 10:57:17 -08:00
Dale Hui f584949470 Update imports to reference fork.
Thanks `make rewrite-import-paths`!
2018-01-19 10:56:55 -08:00
Dale Hui 303cd74e5f Fix Docker install on TravisCi by allowing the docker-ce package to be downgraded
- Started happening since TravisCI updated docker-ce https://docs.travis-ci.com/user/build-environment-updates/2017-12-12/
    - Can't use v17.09 since the git tag doesn't exist yet
2018-01-19 02:28:41 -08:00
Dale Hui 3b61abf20a Update some examples and badges in README to reference new fork 2018-01-19 02:07:34 -08:00
Matthias KadenbachandGitHub d23f71b03c Merge pull request #305 from zikes/master
use DELETE FROM to avoid schema changes within transaction
2017-12-08 13:48:26 -08:00
Jason Hutchinson c156f5f3a1 replace TRUNCATE with DELETE FROM to avoid schema changes within transaction 2017-11-02 16:59:26 -05:00
Teddy Schmitz 155a8b241c Use db.Conn to fix postgres lock/unlock 2017-10-27 10:56:10 +08:00
Matthias KadenbachandGitHub 69472d5f5c Merge pull request #301 from shaneodonnell/master
Issue 247: Removed '(always included)'
2017-10-24 11:00:00 -07:00
Shane O'Donnell 4ac3ee9d75 Removed '(always included)' 2017-10-23 13:27:24 -04:00
Matthias KadenbachandGitHub 5b98c13eff Merge pull request #281 from ashoda/support_multiple_statements
(Spanner) Add support for multiple statements in one migration step
2017-10-09 11:03:59 -07:00
Ashod Ayanyan 28cb030ffe Add support for multiple DDL/SQL statements in one migration file 2017-09-18 15:43:46 -07:00
Matthias KadenbachandGitHub be1b075605 add go 1.9 for tests 2017-09-18 14:55:49 -07:00
Matthias KadenbachandGitHub 62cddb9e74 Merge pull request #278 from JensRantil/cassandra-auth
feat(cassandra): support for user/pw authentication
2017-09-18 10:08:58 -07:00
Matthias KadenbachandGitHub eabba5b316 Merge pull request #277 from JensRantil/cleanup
Various `gofmt`ing
2017-09-18 10:08:34 -07:00
Jens Rantil a5e584ce22 feat(cassandra): support for user/pw authentication 2017-09-17 13:32:00 +02:00
Jens Rantil d25732a53a sty: avoid unnecessary parenthesis
Golang best-practise. Cleanup made by `gofmt`.
2017-09-17 13:30:08 +02:00
Jens Rantil 701bd68731 style(cassandra): whitespace fixes
According to `gofmt`.
2017-09-17 13:26:30 +02:00
Jens Rantil 51acd3f3ad style: order imports lexicographically
Best-practise according to `gofmt`.
2017-09-17 13:26:23 +02:00
Matthias KadenbachandGitHub 804f9c2d4f Merge pull request #273 from maknahar/master
Fixed a couple of typos
2017-08-14 14:01:06 -07:00
maknahar 81c0a17f20 Fixed a couple of typos 2017-08-14 17:55:33 +05:30
Matthias KadenbachandGitHub 2570d5866d Merge pull request #272 from twrobel3/bugfix/cockroachTxnSig
Update cockroachDB Txn signatures
2017-08-11 17:11:41 -07:00
Taylor Wrobel 2c0f4f588e Update cockroachDB Txn signatures
cockroach-go made a backwards-incompatible change to their transaction
function signatures, which was causing the cockroach instrumentation
to fail.

Updates the signature to match the cockraoch-go change.
2017-08-10 22:56:41 -07:00
Matthias KadenbachandGitHub 8b5365e62c Merge pull request #267 from nicolas33/FAQ-extension
Faq extension
2017-07-30 15:05:05 -07:00
Nicolas Sebrecht d837bcc60c FAQ: add required extension
Github-fix: #265
Signed-off-by: Nicolas Sebrecht <nicolas.sebrecht@1001pneus.fr>
2017-07-28 14:30:10 +02:00
Nicolas Sebrecht bfe502654c FAQ: update format from dos to unix
Signed-off-by: Nicolas Sebrecht <nicolas.sebrecht@1001pneus.fr>
2017-07-28 14:24:46 +02:00
Matthias KadenbachandGitHub 6632d110b6 Merge pull request #264 from twrobel3/feature/migrations-readme
Fill out MIGRATIONS.md with migration format and best practice notes
2017-07-25 23:05:42 -07:00
Taylor Wrobel 341c35b1b8 Fill out MIGRATIONS.md with migration format and best practice notes 2017-07-19 00:18:02 -07:00
Matthias KadenbachandGitHub 8e6084bc9c Merge pull request #263 from twrobel3/feature/crdb-readme
Add cockroachdb README and add force lock functionality
2017-07-12 08:48:03 -07:00
Taylor Wrobel 40a40613cb Add cockroachdb README and add ForceLock functionality
Adds a readme to the cockroachdb database package, following the
postgres readme style.

Also adds the ability to force acquisition of the migration lock
via a connect URL parameter/WithInstance config, to allow for fixing
cases where an implementation error causes the schema lock to not
be released.

Lastly, tweaks the CLI readme to include information on building a
CLI for databases other than postgres.
2017-07-11 21:52:02 -07:00
Watanabe MasaruandMatthias Kadenbach 23a2745a3a fix spanner ensure migration table 2017-07-09 10:57:10 -07:00
Matthias KadenbachandGitHub 88c6a0a752 Merge pull request #253 from kshvakov/master
Add ClickHouse driver
2017-07-08 10:11:23 -07:00
Kirill ShvakovandGitHub 1f62e3f142 Merge branch 'master' into master 2017-07-08 13:50:51 +03:00
Kirill Shvakov 8424e946e4 remove deprecated group from Travis file 2017-07-08 13:49:23 +03:00
Matthias KadenbachandGitHub 66a0448698 Merge pull request #255 from twrobel3/feature/cockroachdb
Add CockroachDB Support
2017-07-08 00:26:12 -07:00
Taylor Wrobel 2cab359222 Remove debugging output from cockroachDB tests 2017-07-07 23:13:55 -07:00
Taylor Wrobel aebc0c3cdd Merge port mapping helper functions into one 2017-07-07 23:12:07 -07:00
Taylor Wrobel c2925c40c7 Add tests for CockroachDB 2017-07-07 23:12:07 -07:00
Taylor Wrobel 88115dedbf Support running docker with commands and handling multiple exposed ports
Adds the ability to specify a series of commands to run as part
of the docker image execution, and allows for retrieving a mapping
of an exposed via the port bound within the container.
2017-07-07 23:12:07 -07:00
Taylor Wrobel 1d8a881941 Add CockroachDB Support
Adds support for CockroachDB.  Cockroach uses the postges wire
protocol and has a large amount of common SQL functionality shared
with Postgres, so much of the postgres code was able to be copied
and modified.

Since the protocol is used in determining the driver, and the
Postgres protocol is also used by Cockroach, new connect string
prefixes were added: cockroach:// cockroachdb:// and
crdb-postgres://.  These fake protocol strings are replaced in
the connect function with the correct `postgres://` protocol.

TODO: Tests needed (Cockroach has a docker image, so this shouldn't
be too hard)
2017-07-07 23:12:07 -07:00
Matthias KadenbachandGitHub 8439d7195f Merge pull request #262 from twrobel3/feature/fixTravisBuild
Update docker dependency version to fix Tavis builds
2017-07-07 22:45:46 -07:00
Matthias KadenbachandGitHub f0f8b2c85c Merge pull request #260 from martin-magakian/patch-1
Update README.md
2017-07-03 22:21:55 -07:00
Martin MagakianandGitHub 35af42a0dd Update README.md 2017-07-03 15:45:57 +02:00
Martin MagakianandGitHub 56a56527c6 Update README.md
Late edit I promise...
2017-07-03 15:45:41 +02:00
Martin MagakianandGitHub 1485e567f1 Update README.md
Some format :-p
2017-07-03 15:35:43 +02:00
Martin MagakianandGitHub 180c57747d Update README.md
Forget package
2017-07-03 15:34:43 +02:00
Martin MagakianandGitHub 2361a7aa24 Update README.md
Code snippet don't compile.
It's missing a step (see https://github.com/mattes/migrate/blob/master/source/go-bindata/go-bindata_test.go)
2017-07-03 15:29:15 +02:00
Taylor Wrobel 4f9422ec85 Update docker dependency version to fix Tavis builds
Tavis has updated its docker images, and as part of the update,
no longer includes support for the (deprevated) docker-engine
package, moving instead to docker-ce.

See https://blog.travis-ci.com/2017-06-21-trusty-updates-2017-Q2-launch
for details of the package change.

This updates the travis configuration to pin the docker library
version to 17.05.0 and installs the corresponding version of
docker-ce in the install script.
2017-06-24 10:37:08 -07:00
kshvakov 9d17987b8c Travis CI: using the previous Ubuntu Trusty images (https://blog.travis-ci.com/2017-06-21-trusty-updates-2017-Q2-launch) 2017-06-23 12:16:14 +03:00
kshvakov 94056e51cc postgres: fix SetVersion. All requests were executed in different connections so commit/rollback doesn't work correctly 2017-06-23 11:13:54 +03:00
Kirill Shvakov 8cd9761d29 fix: set current database 2017-06-21 23:10:43 +03:00
kshvakov e86f4a201c change include path from github.com/kshvakov to github.com/mattes 2017-06-21 17:24:55 +03:00
kshvakov 9341404de0 move ensureVersionTable to init 2017-06-21 17:14:43 +03:00
kshvakov b031b22c80 Add WithInstance 2017-06-21 17:12:05 +03:00
kshvakov 1440a070c3 Add WithInstance 2017-06-21 17:06:04 +03:00
kshvakov 14636b77fb Add ClickHouse driver 2017-06-21 15:58:46 +03:00
Matthias KadenbachandGitHub da4160ce04 Merge pull request #251 from christianklotz/spanner-driver
Add Spanner driver
2017-06-20 21:52:00 -07:00
Christian Klotz eccc3a26d2 Skip test if SPANNER_DATABASE isn’t set 2017-06-20 17:53:41 +01:00
Christian KlotzandGitHub 51afcccb6b Merge branch 'master' into spanner-driver 2017-06-14 21:02:59 +01:00
Stephanie NgandChristian Klotz 8f7d3efb1a add create command to cli 2017-06-14 21:59:11 +02:00
BenandChristian Klotz 13a818e3b5 Fix typo forgotton => forgotten 2017-06-14 21:59:11 +02:00
Matthias KadenbachandChristian Klotz 5e049b9fb7 enable cgo 2017-06-14 21:59:11 +02:00
maxvwandChristian Klotz 33d5038bd3 adapted the sqlite driver for v3 (#165) 2017-06-14 21:58:37 +02:00
Fuyuan BieandChristian Klotz 39bb76224d Fix parenthesis problem. 2017-06-14 21:57:37 +02:00
Fuyuan BieandChristian Klotz 9632882b69 Use both db name and migration table name for lock hash 2017-06-14 21:57:37 +02:00
Christian Klotz 803598293e Add basic Google Spanner test 2017-06-14 19:46:10 +02:00
Matthias KadenbachandGitHub 50068870a1 Merge pull request #249 from recurza/master
Add create command to CLI
2017-06-10 10:25:06 -07:00
Stephanie Ng 9a19439e31 add create command to cli 2017-06-10 20:20:35 +08:00
Matthias KadenbachandGitHub d8dd1bc427 Merge pull request #248 from biefy/master
Solve lock contention problem when upgrading multiple logical MySQL databases backed by a single physical database.
2017-06-09 17:08:54 -07:00
Matthias KadenbachandGitHub c1134be427 Merge pull request #242 from benmoss/master
Fix typo forgotton => forgotten
2017-06-05 13:11:58 -07:00
Matthias KadenbachandGitHub 2aab3394cf Merge pull request #243 from mattes/mattes-patch-1
enable cgo
2017-06-05 13:11:27 -07:00
Ben 39a5a37822 Fix typo forgotton => forgotten 2017-06-05 12:14:10 -04:00
Jolan MalassigneandChristian Klotz 89879968bb add cassandra driver and function to retrieve networkSettings to get port bound to 9042 2017-06-05 11:03:49 +01:00
Pavlina DrososandChristian Klotz 6ecd671cfc Add code sample for use with existing MySQL client with multistatements=true 2017-06-05 11:02:51 +01:00
MaxandChristian Klotz 2f50c9c52b added file cli/build_sqlite3.go 2017-06-05 11:02:51 +01:00
maxvwandChristian Klotz 386ce008d9 adapted the sqlite driver for v3 (#165) 2017-06-05 11:02:51 +01:00
Christian Klotz 3d6c78875f Add example migrations 2017-06-05 10:50:14 +01:00
Matthias KadenbachandGitHub a9bcd83ff0 enable cgo 2017-06-05 00:59:53 -07:00
Matthias KadenbachandGitHub bf3f0f133e Merge pull request #240 from tinysquare/master
Add cassandra driver
2017-06-04 21:27:48 -07:00
Matthias KadenbachandGitHub 2aa16f5f66 Merge branch 'master' into master 2017-06-04 21:27:38 -07:00
Matthias KadenbachandGitHub 63f788f07e Merge pull request #239 from pdrosos/update-mysql-documentation-for-use-with-existing-db-client
Add documentation for use with existing MySQL client with multiStatements=true
2017-06-04 21:23:57 -07:00
Matthias KadenbachandGitHub c3d61852ee Merge pull request #238 from maxvw/v3-sqlite
adapted the sqlite driver for v3 (#165)
2017-06-04 21:21:55 -07:00
Christian Klotz 2742b9c467 Provide WithInstance method
Also includes some refactor around package naming, treats own repo as first-class and uses alternative package names for third party imports, Google spanner libraries in this case.
2017-06-04 21:53:33 +01:00
Christian Klotz 748ae8f06a Add Spanner driver
Support for Google Cloud Spanner, closes #172. Includes example migrations ported over from the PostgreSQL driver.

In all examples provided by Google I’ve come across, camel-case is used for table names, columns, etc. Hence the examples use this naming convention.
2017-06-04 21:13:49 +01:00
Jolan Malassigne 28524d977c add cassandra driver and function to retrieve networkSettings to get port bound to 9042 2017-05-31 10:05:56 +02:00
Pavlina Drosos b5c2f0f2bb Add code sample for use with existing MySQL client with multistatements=true 2017-05-30 18:52:26 +03:00
Max decee4abb2 added file cli/build_sqlite3.go 2017-05-30 14:06:59 +02:00
maxvw 8b4ce58a1d adapted the sqlite driver for v3 (#165) 2017-05-27 08:58:01 +02:00
Matthias KadenbachandGitHub 3682bcf9d2 Update README.md 2017-05-18 19:47:23 -07:00
Matthias KadenbachandGitHub 857dc4b862 Update README.md 2017-05-18 19:45:53 -07:00
Matthias KadenbachandGitHub 247ca90b33 Merge pull request #234 from augustzf/master
Add command to make CLI example with Go toolchain work
2017-05-18 10:29:15 -07:00
Matthias KadenbachandGitHub 00b22e527d one line 2017-05-18 10:28:39 -07:00
August Z. Flatby 83035ec925 Add command to make CLI example with Go toolchain work 2017-05-18 09:27:39 +02:00
Matthias KadenbachandGitHub 035c07716c Merge pull request #231 from mattes/cli-updates
build cli with all source and database drivers
2017-05-17 19:37:49 -07:00
Fuyuan BieandGitHub bcdbe0f018 Fix parenthesis problem. 2017-05-15 22:01:45 -07:00
Fuyuan BieandGitHub 5f2a8b7f30 Use both db name and migration table name for lock hash 2017-05-15 21:50:41 -07:00
307 changed files with 14844 additions and 1561 deletions
+12
View File
@@ -0,0 +1,12 @@
# Project
FAQ.md
README.md
LICENSE
.gitignore
.travis.yml
CONTRIBUTING.md
MIGRATIONS.md
docker-deploy.sh
# Golang
testing
+39
View File
@@ -0,0 +1,39 @@
---
name: Bug report
about: Create a report to help us improve
---
**Describe the Bug**
A clear and concise description of what the bug is.
**Steps to Reproduce**
Steps to reproduce the behavior:
1. My migrations look like '...'
2. I ran migrate with the following options '....'
3. See error
**Expected Behavior**
A clear and concise description of what you expected to happen.
**Migrate Version**
e.g. v3.4.0
Obtained by running: `migrate -version`
**Loaded Source Drivers**
e.g. s3, github, go-bindata, gcs, file
Obtained by running: `migrate -help`
**Loaded Database Drivers**
e.g. spanner, stub, clickhouse, cockroachdb, crdb-postgres, postgres, postgresql, redshift, cassandra, cockroach, mysql
Obtained by running: `migrate -help`
**Go Version**
e.g. go version go1.11 linux/amd64
Obtained by running: `go version`
**Stacktrace**
Please provide if available
**Additional context**
Add any other context about the problem here.
+17
View File
@@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+3
View File
@@ -4,3 +4,6 @@ cli/cli
cli/migrate
.coverage
.godoc.pid
vendor/
.vscode/
.idea/
+27
View File
@@ -0,0 +1,27 @@
run:
# timeout for analysis, e.g. 30s, 5m, default is 1m
timeout: 2m
linters:
enable:
#- golint
- interfacer
- unconvert
#- dupl
- goconst
- gofmt
- misspell
- maligned
- unparam
- nakedret
- prealloc
#- gosec
linters-settings:
misspell:
locale: US
issues:
max-same: 0
max-per-linter: 0
exclude-use-default: false
exclude:
# gosec: Duplicated errcheck checks
- G104
+102 -25
View File
@@ -1,61 +1,138 @@
language: go
sudo: required
go:
- 1.7
- 1.8
matrix:
allow_failures:
- go: master
include:
# Supported versions of Go: https://golang.org/dl/
- go: "1.14.x"
- go: "1.15.x"
- go: master
go_import_path: github.com/golang-migrate/migrate
env:
- MIGRATE_TEST_CONTAINER_BOOT_DELAY=10
global:
- GO111MODULE=on
- MIGRATE_TEST_CONTAINER_BOOT_TIMEOUT=60
- DOCKER_USERNAME=golangmigrate
- secure: "oSOznzUrgr5h45qW4PONkREpisPAt40tnM+KFWtS/Ggu5UI2Ie0CmyYXWuBjbt7B97a4yN9Qzmn8FxJHJ7kk+ABOi3muhkxeIhr6esXbzHhX/Jhv0mj1xkzX7KoVN9oHBz3cOI/QeRyEAO68xjDHNE2kby4RTT9VBt6TQUakKVkqI5qkqLBTADepCjVC+9XhxVxUNyeWKU8ormaUfJBjoNVoDlwXekUPnJenfmfZqXxUInvBCfUyp7Pq+kurBORmg4yc6qOlRYuK67Xw+i5xpjbZouNlXPk0rq7pPy5zjhmZQ3kImoFPvNMeKViDcI6kSIJKtjdhms9/g/6MgXS9HlL5kFy8tYKbsyiHnHB1BsvaLAKXctbUZFDPstgMPADfnad2kZXPrNqIhfWKZrGRWidawCYJ1sKKwYxLMKrtA0umqgMoL90MmBOELhuGmvMV0cFJB+zo+K2YWjEiMGd8xRb5mC5aAy0ZcCehO46jGtpr217EJmMF8Ywr7cFqM2Shg5U2jev9qUpYiXwmPnJKDuoT2ZHuHmPgFIkYiWC5yeJnnmG5bed1sKBp93AFrJX+1Rx5oC4BpNegewmBZKpOSwls/D1uMAeQK3dPmQHLsT6o2VBLfeDGr+zY0R85ywwPZCv00vGol02zYoTqN7eFqr6Qhjr/qx5K1nnxJdFK3Ts="
# TODO: https://docs.docker.com/engine/installation/linux/ubuntu/
# pre-provision with travis docker setup and pin down docker version in install step
services:
- docker
- docker
cache:
directories:
- $GOPATH/pkg
before_install:
# Update docker to latest version: https://docs.travis-ci.com/user/docker/#installing-a-newer-docker-version
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
- sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
- sudo apt-get update
- sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce
# Install golangci-lint
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.30.0
- echo "TRAVIS_GO_VERSION=${TRAVIS_GO_VERSION}"
install:
- make deps
- (cd $GOPATH/src/github.com/docker/docker && git fetch --all --tags --prune && git checkout v1.13.0)
- sudo apt-get update && sudo apt-get install docker-engine=1.13.0*
- go get github.com/mattn/goveralls
- go get github.com/mattn/goveralls
script:
- make test
- golangci-lint run
- make test COVERAGE_DIR=/tmp/coverage
after_success:
- goveralls -service=travis-ci -coverprofile .coverage/combined.txt
- goveralls -service=travis-ci -coverprofile /tmp/coverage/combined.txt
- make list-external-deps > dependency_tree.txt && cat dependency_tree.txt
before_deploy:
- make build-cli
- gem install --no-ri --no-rdoc fpm
- fpm -s dir -t deb -n migrate -v "$(git describe --tags 2>/dev/null | cut -c 2-)" --license MIT -m matthias.kadenbach@gmail.com --url https://github.com/mattes/migrate --description='Database migrations' -a amd64 -p migrate.$(git describe --tags 2>/dev/null | cut -c 2-).deb --deb-no-default-config-files -f -C cli/build migrate.linux-amd64=/usr/bin/migrate
- gem install --no-document fpm
- fpm -s dir -t deb -n migrate -v "$(git describe --tags 2>/dev/null | cut -c 2-)" --license MIT -m dhui@users.noreply.github.com --url https://github.com/golang-migrate/migrate --description='Database migrations' -a amd64 -p migrate.$(git describe --tags 2>/dev/null | cut -c 2-).deb --deb-no-default-config-files -f -C cli/build migrate.linux-amd64=/usr/local/bin/migrate
deploy:
- provider: releases
api_key:
secure: EFow50BI448HVb/uQ1Kk2Kq0xzmwIYq3V67YyymXIuqSCodvXEsMiBPUoLrxEknpPEIc67LEQTNdfHBgvyHk6oRINWAfie+7pr5tKrpOTF9ghyxoN1PlO8WKQCqwCvGMBCnc5ur5rvzp0bqfpV2rs5q9/nngy3kBuEvs12V7iho=
secure: hWH1HLPpzpfA8pXQ93T1qKQVFSpQp0as/JLQ7D91jHuJ8p+RxVeqblDrR6HQY/95R/nyiE9GJmvUolSuw5h449LSrGxPtVWhdh6EnkxlQHlen5XeMhVjRjFV0sE9qGe8v7uAkiTfRO61ktTWHrEAvw5qpyqnNISodmZS78XIasPODQbNlzwINhWhDTHIjXGb4FpizYaL3OGCanrxfR9fQyCaqKGGBjRq3Mfq8U6Yd4mApmsE+uJxgaZV8K5zBqpkSzQRWhcVGNL5DuLsU3gfSJOo7kZeA2G71SHffH577dBoqtCZ4VFv169CoUZehLWCb+7XKJZmHXVujCURATSySLGUOPc6EoLFAn3YtsCA04mS4bZVo5FZPWVwfhjmkhtDR4f6wscKp7r1HsFHSOgm59QfETQdrn4MnZ44H2Jd39axqndn5DvK9EcZVjPHynOPnueXP2u6mTuUgh2VyyWBCDO3CNo0fGlo7VJI69IkIWNSD87K9cHZWYMClyKZkUzS+PmRAhHRYbVd+9ZjKOmnU36kUHNDG/ft1D4ogsY+rhVtXB4lgWDM5adri+EIScYdYnB1/pQexLBigcJY9uE7nQTR0U6QgVNYvun7uRNs40E0c4voSfmPdFO0FlOD2y1oQhnaXfWLbu9nMcTcs4RFGrcC7NzkUN4/WjG8s285V6w=
skip_cleanup: true
on:
go: 1.8
repo: mattes/migrate
go: "1.15.x"
repo: golang-migrate/migrate
tags: true
file:
- cli/build/migrate.linux-amd64.tar.gz
- cli/build/migrate.linux-armv7.tar.gz
- cli/build/migrate.linux-arm64.tar.gz
- cli/build/migrate.darwin-amd64.tar.gz
- cli/build/migrate.windows-amd64.exe.tar.gz
- cli/build/migrate.windows-386.exe.tar.gz
- cli/build/sha256sum.txt
- dependency_tree.txt
- provider: packagecloud
repository: migrate
username: mattes
username: golang-migrate
token:
secure: RiHJ/+J9DvXUah/APYdWySWZ5uOOISYJ0wS7xddc7/BNStRVjzFzvJ9zmb67RkyZZrvGuVjPiL4T8mtDyCJCj47RmU/56wPdEHbar/FjsiUCgwvR19RlulkgbV4okBCePbwzMw6HNHRp14TzfQCPtnN4kef0lOI4gZJkImN7rtQ=
secure: aICwu3gJ1sJ1QVCD3elpg+Jxzt4P+Zj1uoh5f0sOwnjDNIZ4FwUT1cMrWloP8P2KD0iyCOawuZER27o/kQ21oX2OxHvQbYPReA2znLm7lHzCmypAAOHPxpgnQ4rMGHHJXd+OsxtdclGs67c+EbdBfoRRbK400Qz/vjPJEDeH4mh02ZHC2nw4Nk/wV4jjBIkIt9dGEx6NgOA17FCMa3MaPHlHeFIzU7IfTlDHbS0mCCYbg/wafWBWcbGqtZLWAYtJDmfjrAStmDLdAX5J5PsB7taGSGPZHmPmpGoVgrKt/tb9Xz1rFBGslTpGROOiO4CiMAvkEKFn8mxrBGjfSBqp7Dp3eeSalKXB1DJAbEXx2sEbMcvmnoR9o43meaAn+ZRts8lRL8S/skBloe6Nk8bx3NlJCGB9WPK1G56b7c/fZnJxQbrCw6hxDfbZwm8S2YPviFTo/z1BfZDhRsL74reKsN2kgnGo2W/k38vvzIpsssQ9DHN1b0TLCxolCNPtQ7oHcQ1ohcjP2UgYXk0FhqDoL+9LQva/DU4N9sKH0UbAaqsMVSErLeG8A4aauuFcVrWRBaDYyTag4dQqzTulEy7iru2kDDIBgSQ1gMW/yoBOIPK4oi6MtbTf1X39fzXFLS1cDd3LW61yAu3YrbjAetpfx2frIvrRAiL9TxWA1gnrs5o=
dist: ubuntu/xenial
package_glob: '*.deb'
skip_cleanup: true
on:
go: 1.8
repo: mattes/migrate
go: "1.15.x"
repo: golang-migrate/migrate
tags: true
- provider: packagecloud
repository: migrate
username: golang-migrate
token:
secure: aICwu3gJ1sJ1QVCD3elpg+Jxzt4P+Zj1uoh5f0sOwnjDNIZ4FwUT1cMrWloP8P2KD0iyCOawuZER27o/kQ21oX2OxHvQbYPReA2znLm7lHzCmypAAOHPxpgnQ4rMGHHJXd+OsxtdclGs67c+EbdBfoRRbK400Qz/vjPJEDeH4mh02ZHC2nw4Nk/wV4jjBIkIt9dGEx6NgOA17FCMa3MaPHlHeFIzU7IfTlDHbS0mCCYbg/wafWBWcbGqtZLWAYtJDmfjrAStmDLdAX5J5PsB7taGSGPZHmPmpGoVgrKt/tb9Xz1rFBGslTpGROOiO4CiMAvkEKFn8mxrBGjfSBqp7Dp3eeSalKXB1DJAbEXx2sEbMcvmnoR9o43meaAn+ZRts8lRL8S/skBloe6Nk8bx3NlJCGB9WPK1G56b7c/fZnJxQbrCw6hxDfbZwm8S2YPviFTo/z1BfZDhRsL74reKsN2kgnGo2W/k38vvzIpsssQ9DHN1b0TLCxolCNPtQ7oHcQ1ohcjP2UgYXk0FhqDoL+9LQva/DU4N9sKH0UbAaqsMVSErLeG8A4aauuFcVrWRBaDYyTag4dQqzTulEy7iru2kDDIBgSQ1gMW/yoBOIPK4oi6MtbTf1X39fzXFLS1cDd3LW61yAu3YrbjAetpfx2frIvrRAiL9TxWA1gnrs5o=
dist: ubuntu/bionic
package_glob: '*.deb'
skip_cleanup: true
on:
go: "1.15.x"
repo: golang-migrate/migrate
tags: true
- provider: packagecloud
repository: migrate
username: golang-migrate
token:
secure: aICwu3gJ1sJ1QVCD3elpg+Jxzt4P+Zj1uoh5f0sOwnjDNIZ4FwUT1cMrWloP8P2KD0iyCOawuZER27o/kQ21oX2OxHvQbYPReA2znLm7lHzCmypAAOHPxpgnQ4rMGHHJXd+OsxtdclGs67c+EbdBfoRRbK400Qz/vjPJEDeH4mh02ZHC2nw4Nk/wV4jjBIkIt9dGEx6NgOA17FCMa3MaPHlHeFIzU7IfTlDHbS0mCCYbg/wafWBWcbGqtZLWAYtJDmfjrAStmDLdAX5J5PsB7taGSGPZHmPmpGoVgrKt/tb9Xz1rFBGslTpGROOiO4CiMAvkEKFn8mxrBGjfSBqp7Dp3eeSalKXB1DJAbEXx2sEbMcvmnoR9o43meaAn+ZRts8lRL8S/skBloe6Nk8bx3NlJCGB9WPK1G56b7c/fZnJxQbrCw6hxDfbZwm8S2YPviFTo/z1BfZDhRsL74reKsN2kgnGo2W/k38vvzIpsssQ9DHN1b0TLCxolCNPtQ7oHcQ1ohcjP2UgYXk0FhqDoL+9LQva/DU4N9sKH0UbAaqsMVSErLeG8A4aauuFcVrWRBaDYyTag4dQqzTulEy7iru2kDDIBgSQ1gMW/yoBOIPK4oi6MtbTf1X39fzXFLS1cDd3LW61yAu3YrbjAetpfx2frIvrRAiL9TxWA1gnrs5o=
dist: ubuntu/focal
package_glob: '*.deb'
skip_cleanup: true
on:
go: "1.15.x"
repo: golang-migrate/migrate
tags: true
- provider: packagecloud
repository: migrate
username: golang-migrate
token:
secure: aICwu3gJ1sJ1QVCD3elpg+Jxzt4P+Zj1uoh5f0sOwnjDNIZ4FwUT1cMrWloP8P2KD0iyCOawuZER27o/kQ21oX2OxHvQbYPReA2znLm7lHzCmypAAOHPxpgnQ4rMGHHJXd+OsxtdclGs67c+EbdBfoRRbK400Qz/vjPJEDeH4mh02ZHC2nw4Nk/wV4jjBIkIt9dGEx6NgOA17FCMa3MaPHlHeFIzU7IfTlDHbS0mCCYbg/wafWBWcbGqtZLWAYtJDmfjrAStmDLdAX5J5PsB7taGSGPZHmPmpGoVgrKt/tb9Xz1rFBGslTpGROOiO4CiMAvkEKFn8mxrBGjfSBqp7Dp3eeSalKXB1DJAbEXx2sEbMcvmnoR9o43meaAn+ZRts8lRL8S/skBloe6Nk8bx3NlJCGB9WPK1G56b7c/fZnJxQbrCw6hxDfbZwm8S2YPviFTo/z1BfZDhRsL74reKsN2kgnGo2W/k38vvzIpsssQ9DHN1b0TLCxolCNPtQ7oHcQ1ohcjP2UgYXk0FhqDoL+9LQva/DU4N9sKH0UbAaqsMVSErLeG8A4aauuFcVrWRBaDYyTag4dQqzTulEy7iru2kDDIBgSQ1gMW/yoBOIPK4oi6MtbTf1X39fzXFLS1cDd3LW61yAu3YrbjAetpfx2frIvrRAiL9TxWA1gnrs5o=
dist: debian/stretch
package_glob: '*.deb'
skip_cleanup: true
on:
go: "1.15.x"
repo: golang-migrate/migrate
tags: true
- provider: packagecloud
repository: migrate
username: golang-migrate
token:
secure: aICwu3gJ1sJ1QVCD3elpg+Jxzt4P+Zj1uoh5f0sOwnjDNIZ4FwUT1cMrWloP8P2KD0iyCOawuZER27o/kQ21oX2OxHvQbYPReA2znLm7lHzCmypAAOHPxpgnQ4rMGHHJXd+OsxtdclGs67c+EbdBfoRRbK400Qz/vjPJEDeH4mh02ZHC2nw4Nk/wV4jjBIkIt9dGEx6NgOA17FCMa3MaPHlHeFIzU7IfTlDHbS0mCCYbg/wafWBWcbGqtZLWAYtJDmfjrAStmDLdAX5J5PsB7taGSGPZHmPmpGoVgrKt/tb9Xz1rFBGslTpGROOiO4CiMAvkEKFn8mxrBGjfSBqp7Dp3eeSalKXB1DJAbEXx2sEbMcvmnoR9o43meaAn+ZRts8lRL8S/skBloe6Nk8bx3NlJCGB9WPK1G56b7c/fZnJxQbrCw6hxDfbZwm8S2YPviFTo/z1BfZDhRsL74reKsN2kgnGo2W/k38vvzIpsssQ9DHN1b0TLCxolCNPtQ7oHcQ1ohcjP2UgYXk0FhqDoL+9LQva/DU4N9sKH0UbAaqsMVSErLeG8A4aauuFcVrWRBaDYyTag4dQqzTulEy7iru2kDDIBgSQ1gMW/yoBOIPK4oi6MtbTf1X39fzXFLS1cDd3LW61yAu3YrbjAetpfx2frIvrRAiL9TxWA1gnrs5o=
dist: debian/buster
package_glob: '*.deb'
skip_cleanup: true
on:
go: "1.15.x"
repo: golang-migrate/migrate
tags: true
- provider: script
script: ./docker-deploy.sh
skip_cleanup: true
on:
go: "1.15.x"
repo: golang-migrate/migrate
tags: true
+15 -13
View File
@@ -2,21 +2,23 @@
1. Make sure you have a running Docker daemon
(Install for [MacOS](https://docs.docker.com/docker-for-mac/))
2. Fork this repo and `git clone` somewhere to `$GOPATH/src/github.com/%you%/migrate`
3. `make rewrite-import-paths` to update imports to your local fork
4. Confirm tests are working: `make test-short`
5. Write awesome code ...
6. `make test` to run all tests against all database versions
7. `make restore-import-paths` to restore import paths
8. Push code and open Pull Request
1. Use a version of Go that supports [modules](https://golang.org/cmd/go/#hdr-Modules__module_versions__and_more) (e.g. Go 1.11+)
1. Fork this repo and `git clone` somewhere to `$GOPATH/src/github.com/golang-migrate/migrate`
* Ensure that [Go modules are enabled](https://golang.org/cmd/go/#hdr-Preliminary_module_support) (e.g. your repo path or the `GO111MODULE` environment variable are set correctly)
1. Install [golangci-lint](https://github.com/golangci/golangci-lint#install)
1. Run the linter: `golangci-lint run`
1. Confirm tests are working: `make test-short`
1. Write awesome code ...
1. `make test` to run all tests against all database versions
1. Push code and open Pull Request
Some more helpful commands:
* You can specify which database/ source tests to run:
`make test-short SOURCE='file go-bindata' DATABASE='postgres cassandra'`
* After `make test`, run `make html-coverage` which opens a shiny test coverage overview.
* Missing imports? `make deps`
* You can specify which database/ source tests to run:
`make test-short SOURCE='file go_bindata' DATABASE='postgres cassandra'`
* After `make test`, run `make html-coverage` which opens a shiny test coverage overview.
* `make build-cli` builds the CLI in directory `cli/build/`.
* `make list-external-deps` lists all external dependencies for each package
* `make docs && make open-docs` opens godoc in your browser, `make kill-docs` kills the godoc server.
Repeatedly call `make docs` to refresh the server.
* `make docs && make open-docs` opens godoc in your browser, `make kill-docs` kills the godoc server.
Repeatedly call `make docs` to refresh the server.
* Set the `DOCKER_API_VERSION` environment variable to the latest supported version if you get errors regarding the docker client API version being too new.
+26
View File
@@ -0,0 +1,26 @@
FROM golang:1.15-alpine3.12 AS builder
ARG VERSION
RUN apk add --no-cache git gcc musl-dev make
WORKDIR /go/src/github.com/golang-migrate/migrate
ENV GO111MODULE=on
COPY go.mod go.sum ./
RUN go mod download
COPY . ./
RUN make build-docker
FROM alpine:3.12
RUN apk add --no-cache ca-certificates
COPY --from=builder /go/src/github.com/golang-migrate/migrate/build/migrate.linux-386 /usr/local/bin/migrate
RUN ln -s /usr/local/bin/migrate /migrate
ENTRYPOINT ["migrate"]
CMD ["--help"]
+79 -67
View File
@@ -1,67 +1,79 @@
# FAQ
#### How is the code base structured?
```
/ package migrate (the heart of everything)
/cli the CLI wrapper
/database database driver and sub directories have the actual driver implementations
/source source driver and sub directories have the actual driver implementations
```
#### Why is there no `source/driver.go:Last()`?
It's not needed. And unless the source has a "native" way to read a directory in reversed order,
it might be expensive to do a full directory scan in order to get the last element.
#### What is a NilMigration? NilVersion?
NilMigration defines a migration without a body. NilVersion is defined as const -1.
#### What is the difference between uint(version) and int(targetVersion)?
version refers to an existing migration version coming from a source and therefor can never be negative.
targetVersion can either be a version OR represent a NilVersion, which equals -1.
#### What's the difference between Next/Previous and Up/Down?
```
1_first_migration.up next -> 2_second_migration.up ...
1_first_migration.down <- previous 2_second_migration.down ...
```
#### Why two separate files (up and down) for a migration?
It makes all of our lives easier. No new markup/syntax to learn for users
and existing database utility tools continue to work as expected.
#### How many migrations can migrate handle?
Whatever the maximum positive signed integer value is for your platform.
For 32bit it would be 2,147,483,647 migrations. Migrate only keeps references to
the currently run and pre-fetched migrations in memory. Please note that some
source drivers need to do build a full "directory" tree first, which puts some
heat on the memory consumption.
#### Are the table tests in migrate_test.go bloated?
Yes and no. There are duplicate test cases for sure but they don't hurt here. In fact
the tests are very visual now and might help new users understand expected behaviors quickly.
Migrate from version x to y and y is the last migration? Just check out the test for
that particular case and know what's going on instantly.
#### What is Docker being used for?
Only for testing. See [testing/docker.go](testing/docker.go)
#### Why not just use docker-compose?
It doesn't give us enough runtime control for testing. We want to be able to bring up containers fast
and whenever we want, not just once at the beginning of all tests.
#### Can I maintain my driver in my own repository?
Yes, technically thats possible. We want to encourage you to contribute your driver to this respository though.
The driver's functionality is dictated by migrate's interfaces. That means there should really
just be one driver for a database/ source. We want to prevent a future where several drivers doing the exact same thing,
just implemented a bit differently, co-exist somewhere on Github. If users have to do research first to find the
"best" available driver for a database in order to get started, we would have failed as an open source community.
#### Can I mix multiple sources during a batch of migrations?
No.
#### What does "dirty" database mean?
Before a migration runs, each database sets a dirty flag. Execution stops if a migration fails and the dirty state persists,
which prevents attempts to run more migrations on top of a failed migration. You need to manually fix the error
and then "force" the expected version.
# FAQ
#### How is the code base structured?
```
/ package migrate (the heart of everything)
/cli the CLI wrapper
/database database driver and sub directories have the actual driver implementations
/source source driver and sub directories have the actual driver implementations
```
#### Why is there no `source/driver.go:Last()`?
It's not needed. And unless the source has a "native" way to read a directory in reversed order,
it might be expensive to do a full directory scan in order to get the last element.
#### What is a NilMigration? NilVersion?
NilMigration defines a migration without a body. NilVersion is defined as const -1.
#### What is the difference between uint(version) and int(targetVersion)?
version refers to an existing migration version coming from a source and therefore can never be negative.
targetVersion can either be a version OR represent a NilVersion, which equals -1.
#### What's the difference between Next/Previous and Up/Down?
```
1_first_migration.up.extension next -> 2_second_migration.up.extension ...
1_first_migration.down.extension <- previous 2_second_migration.down.extension ...
```
#### Why two separate files (up and down) for a migration?
It makes all of our lives easier. No new markup/syntax to learn for users
and existing database utility tools continue to work as expected.
#### How many migrations can migrate handle?
Whatever the maximum positive signed integer value is for your platform.
For 32bit it would be 2,147,483,647 migrations. Migrate only keeps references to
the currently run and pre-fetched migrations in memory. Please note that some
source drivers need to do build a full "directory" tree first, which puts some
heat on the memory consumption.
#### Are the table tests in migrate_test.go bloated?
Yes and no. There are duplicate test cases for sure but they don't hurt here. In fact
the tests are very visual now and might help new users understand expected behaviors quickly.
Migrate from version x to y and y is the last migration? Just check out the test for
that particular case and know what's going on instantly.
#### What is Docker being used for?
Only for testing. See [testing/docker.go](testing/docker.go)
#### Why not just use docker-compose?
It doesn't give us enough runtime control for testing. We want to be able to bring up containers fast
and whenever we want, not just once at the beginning of all tests.
#### Can I maintain my driver in my own repository?
Yes, technically thats possible. We want to encourage you to contribute your driver to this respository though.
The driver's functionality is dictated by migrate's interfaces. That means there should really
just be one driver for a database/ source. We want to prevent a future where several drivers doing the exact same thing,
just implemented a bit differently, co-exist somewhere on GitHub. If users have to do research first to find the
"best" available driver for a database in order to get started, we would have failed as an open source community.
#### Can I mix multiple sources during a batch of migrations?
No.
#### What does "dirty" database mean?
Before a migration runs, each database sets a dirty flag. Execution stops if a migration fails and the dirty state persists,
which prevents attempts to run more migrations on top of a failed migration. You need to manually fix the error
and then "force" the expected version.
#### What happens if two programs try and update the database at the same time?
Database-specific locking features are used by *some* database drivers to prevent multiple instances of migrate from running migrations at the same time
the same database at the same time. For example, the MySQL driver uses the `GET_LOCK` function, while the Postgres driver uses
the `pg_advisory_lock` function.
#### Do I need to create a table for tracking migration version used?
No, it is done automatically.
#### Can I use migrate with a non-Go project?
Yes, you can use the migrate CLI in a non-Go project, but there are probably other libraries/frameworks available that offer better test and deploy integrations in that language/framework.
#### I have got an error `Dirty database version 1. Fix and force version`. What should I do?
Keep calm and refer to [the getting started docs](GETTING_STARTED.md#forcing-your-database-version).
+53
View File
@@ -0,0 +1,53 @@
# Getting started
Before you start, you should understand the concept of forward/up and reverse/down database migrations.
Configure a database for your application. Make sure that your database driver is supported [here](README.md#databases)
## Create migrations
Create some migrations using migrate CLI. Here is an example:
```
migrate create -ext sql -dir db/migrations -seq create_users_table
```
Once you create your files, you should fill them.
**IMPORTANT:** In a project developed by more than one person there is a chance of migrations inconsistency - e.g. two developers can create conflicting migrations, and the developer that created his migration later gets it merged to the repository first.
Developers and Teams should keep an eye on such cases (especially during code review).
[Here](https://github.com/golang-migrate/migrate/issues/179#issuecomment-475821264) is the issue summary if you would like to read more.
Consider making your migrations idempotent - we can run the same sql code twice in a row with the same result. This makes our migrations more robust. On the other hand, it causes slightly less control over database schema - e.g. let's say you forgot to drop the table in down migration. You run down migration - the table is still there. When you run up migration again - `CREATE TABLE` would return an error, helping you find an issue in down migration, while `CREATE TABLE IF NOT EXISTS` would not. Use those conditions wisely.
In case you would like to run several commands/queries in one migration, you should wrap them in a transaction (if your database supports it).
This way if one of commands fails, our database will remain unchanged.
## Run migrations
Run your migrations through the CLI or your app and check if they applied expected changes.
Just to give you an idea:
```
migrate -database YOUR_DATABASE_URL -path PATH_TO_YOUR_MIGRATIONS up
```
Just add the code to your app and you're ready to go!
Before commiting your migrations you should run your migrations up, down, and then up again to see if migrations are working properly both ways.
(e.g. if you created a table in a migration but reverse migration did not delete it, you will encounter an error when running the forward migration again)
It's also worth checking your migrations in a separate, containerized environment. You can find some tools in the end of this document.
**IMPORTANT:** If you would like to run multiple instances of your app on different machines be sure to use a database that supports locking when running migrations. Otherwise you may encounter issues.
## Forcing your database version
In case you run a migration that contained an error, migrate will not let you run other migrations on the same database. You will see an error like `Dirty database version 1. Fix and force version`, even when you fix the erred migration. This means your database was marked as 'dirty'.
You need to investigate the migration error - was your migration applied partially, or was it not applied at all? Once you know, you should force your database to a version reflecting it's real state. You can do so with `force` command:
```
migrate -path PATH_TO_YOUR_MIGRATIONS -database YOUR_DATABASE_URL force VERSION
```
Once you force the version and your migration was fixed, your database is 'clean' again and you can proceed with your migrations.
For details and example of usage see [this comment](https://github.com/golang-migrate/migrate/issues/282#issuecomment-530743258).
## Further reading:
- [PostgreSQL tutorial](database/postgres/TUTORIAL.md)
- [Best practices](MIGRATIONS.md)
- [FAQ](FAQ.md)
- Tools for testing your migrations in a container:
- https://github.com/dhui/dktest
- https://github.com/ory/dockertest
+6 -1
View File
@@ -1,9 +1,14 @@
The MIT License (MIT)
Original Work
Copyright (c) 2016 Matthias Kadenbach
https://github.com/mattes/migrate
Modified Work
Copyright (c) 2018 Dale Hui
https://github.com/golang-migrate/migrate
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
+86 -5
View File
@@ -1,5 +1,86 @@
# Migrations
## Best practices: How to write migrations.
@TODO
# Migrations
## Migration Filename Format
A single logical migration is represented as two separate migration files, one
to migrate "up" to the specified version from the previous version, and a second
to migrate back "down" to the previous version. These migrations can be provided
by any one of the supported [migration sources](./README.md#migration-sources).
The ordering and direction of the migration files is determined by the filenames
used for them. `migrate` expects the filenames of migrations to have the format:
{version}_{title}.up.{extension}
{version}_{title}.down.{extension}
The `title` of each migration is unused, and is only for readability. Similarly,
the `extension` of the migration files is not checked by the library, and should
be an appropriate format for the database in use (`.sql` for SQL variants, for
instance).
Versions of migrations may be represented as any 64 bit unsigned integer.
All migrations are applied upward in order of increasing version number, and
downward by decreasing version number.
Common versioning schemes include incrementing integers:
1_initialize_schema.down.sql
1_initialize_schema.up.sql
2_add_table.down.sql
2_add_table.up.sql
...
Or timestamps at an appropriate resolution:
1500360784_initialize_schema.down.sql
1500360784_initialize_schema.up.sql
1500445949_add_table.down.sql
1500445949_add_table.up.sql
...
But any scheme resulting in distinct, incrementing integers as versions is valid.
It is suggested that the version number of corresponding `up` and `down` migration
files be equivalent for clarity, but they are allowed to differ so long as the
relative ordering of the migrations is preserved.
The migration files are permitted to be "empty", in the event that a migration
is a no-op or is irreversible. It is recommended to still include both migration
files by making the whole migration file consist of a comment.
If your database does not support comments, then deleting the migration file will also work.
Note, an actual empty file (e.g. a 0 byte file) may cause issues with your database since migrate
will attempt to run an empty query. In this case, deleting the migration file will also work.
For the rational of this behavior see:
[#244 (comment)](https://github.com/golang-migrate/migrate/issues/244#issuecomment-510758270)
## Migration Content Format
The format of the migration files themselves varies between database systems.
Different databases have different semantics around schema changes and when and
how they are allowed to occur
(for instance, [if schema changes can occur within a transaction](https://wiki.postgresql.org/wiki/Transactional_DDL_in_PostgreSQL:_A_Competitive_Analysis)).
As such, the `migrate` library has little to no checking around the format of
migration sources. The migration files are generally processed directly by the
drivers as raw operations.
## Reversibility of Migrations
Best practice for writing schema migration is that all migrations should be
reversible. It should in theory be possible for run migrations down and back up
through any and all versions with the state being fully cleaned and recreated
by doing so.
By adhering to this recommended practice, development and deployment of new code
is cleaner and easier (cleaning database state for a new feature should be as
easy as migrating down to a prior version, and back up to the latest).
As opposed to some other migration libraries, `migrate` represents up and down
migrations as separate files. This prevents any non-standard file syntax from
being introduced which may result in unintended behavior or errors, depending
on what database is processing the file.
While it is technically possible for an up or down migration to exist on its own
without an equivalently versioned counterpart, it is strongly recommended to
always include a down migration which cleans up the state of the corresponding
up migration.
+25 -34
View File
@@ -1,15 +1,25 @@
SOURCE ?= file go-bindata github aws-s3 google-cloud-storage
DATABASE ?= postgres mysql redshift
SOURCE ?= file go_bindata github github_ee bitbucket aws_s3 google_cloud_storage godoc_vfs gitlab
DATABASE ?= postgres mysql redshift cassandra spanner cockroachdb clickhouse mongodb sqlserver firebird neo4j
DATABASE_TEST ?= $(DATABASE) sqlite sqlcipher
VERSION ?= $(shell git describe --tags 2>/dev/null | cut -c 2-)
TEST_FLAGS ?=
REPO_OWNER ?= $(shell cd .. && basename "$$(pwd)")
COVERAGE_DIR ?= .coverage
build:
CGO_ENABLED=0 go build -ldflags='-X main.Version=$(VERSION)' -tags '$(DATABASE) $(SOURCE)' ./cmd/migrate
build-docker:
CGO_ENABLED=0 go build -a -o build/migrate.linux-386 -ldflags="-s -w -X main.Version=${VERSION}" -tags "$(DATABASE) $(SOURCE)" ./cmd/migrate
build-cli: clean
-mkdir ./cli/build
cd ./cli && GOOS=linux GOARCH=amd64 go build -a -o build/migrate.linux-amd64 -ldflags='-X main.Version=$(VERSION)' -tags '$(DATABASE) $(SOURCE)' .
cd ./cli && GOOS=darwin GOARCH=amd64 go build -a -o build/migrate.darwin-amd64 -ldflags='-X main.Version=$(VERSION)' -tags '$(DATABASE) $(SOURCE)' .
cd ./cli && GOOS=windows GOARCH=amd64 go build -a -o build/migrate.windows-amd64.exe -ldflags='-X main.Version=$(VERSION)' -tags '$(DATABASE) $(SOURCE)' .
cd ./cmd/migrate && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o ../../cli/build/migrate.linux-amd64 -ldflags='-X main.Version=$(VERSION) -extldflags "-static"' -tags '$(DATABASE) $(SOURCE)' .
cd ./cmd/migrate && CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -a -o ../../cli/build/migrate.linux-armv7 -ldflags='-X main.Version=$(VERSION) -extldflags "-static"' -tags '$(DATABASE) $(SOURCE)' .
cd ./cmd/migrate && CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -a -o ../../cli/build/migrate.linux-arm64 -ldflags='-X main.Version=$(VERSION) -extldflags "-static"' -tags '$(DATABASE) $(SOURCE)' .
cd ./cmd/migrate && CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -a -o ../../cli/build/migrate.darwin-amd64 -ldflags='-X main.Version=$(VERSION) -extldflags "-static"' -tags '$(DATABASE) $(SOURCE)' .
cd ./cmd/migrate && CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -a -o ../../cli/build/migrate.windows-386.exe -ldflags='-X main.Version=$(VERSION) -extldflags "-static"' -tags '$(DATABASE) $(SOURCE)' .
cd ./cmd/migrate && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -a -o ../../cli/build/migrate.windows-amd64.exe -ldflags='-X main.Version=$(VERSION) -extldflags "-static"' -tags '$(DATABASE) $(SOURCE)' .
cd ./cli/build && find . -name 'migrate*' | xargs -I{} tar czf {}.tar.gz {}
cd ./cli/build && shasum -a 256 * > sha256sum.txt
cat ./cli/build/sha256sum.txt
@@ -24,28 +34,16 @@ test-short:
test:
@-rm -r .coverage
@mkdir .coverage
make test-with-flags TEST_FLAGS='-v -race -covermode atomic -coverprofile .coverage/_$$(RAND).txt -bench=. -benchmem'
@echo 'mode: atomic' > .coverage/combined.txt
@cat .coverage/*.txt | grep -v 'mode: atomic' >> .coverage/combined.txt
@-rm -r $(COVERAGE_DIR)
@mkdir $(COVERAGE_DIR)
make test-with-flags TEST_FLAGS='-v -race -covermode atomic -coverprofile $$(COVERAGE_DIR)/combined.txt -bench=. -benchmem -timeout 20m'
test-with-flags:
@echo SOURCE: $(SOURCE)
@echo DATABASE: $(DATABASE)
@echo SOURCE: $(SOURCE)
@echo DATABASE_TEST: $(DATABASE_TEST)
@go test $(TEST_FLAGS) .
@go test $(TEST_FLAGS) ./cli/...
@go test $(TEST_FLAGS) ./testing/...
@echo -n '$(SOURCE)' | tr -s ' ' '\n' | xargs -I{} go test $(TEST_FLAGS) ./source/{}
@go test $(TEST_FLAGS) ./source/testing/...
@go test $(TEST_FLAGS) ./source/stub/...
@echo -n '$(DATABASE)' | tr -s ' ' '\n' | xargs -I{} go test $(TEST_FLAGS) ./database/{}
@go test $(TEST_FLAGS) ./database/testing/...
@go test $(TEST_FLAGS) ./database/stub/...
@go test $(TEST_FLAGS) ./...
kill-orphaned-docker-containers:
@@ -53,14 +51,7 @@ kill-orphaned-docker-containers:
html-coverage:
go tool cover -html=.coverage/combined.txt
deps:
-go get -v -u ./...
-go test -v -i ./...
# TODO: why is this not being fetched with the command above?
-go get -u github.com/fsouza/fake-gcs-server/fakestorage
go tool cover -html=$(COVERAGE_DIR)/combined.txt
list-external-deps:
@@ -89,7 +80,7 @@ rewrite-import-paths:
docs:
-make kill-docs
nohup godoc -play -http=127.0.0.1:6064 </dev/null >/dev/null 2>&1 & echo $$! > .godoc.pid
cat .godoc.pid
cat .godoc.pid
kill-docs:
@@ -114,10 +105,10 @@ define external_deps
endef
.PHONY: build-cli clean test-short test test-with-flags deps html-coverage \
.PHONY: build build-docker build-cli clean test-short test test-with-flags html-coverage \
restore-import-paths rewrite-import-paths list-external-deps release \
docs kill-docs open-docs kill-orphaned-docker-containers
SHELL = /bin/bash
SHELL = /bin/sh
RAND = $(shell echo $$RANDOM)
+100 -59
View File
@@ -1,86 +1,116 @@
[![Build Status](https://travis-ci.org/mattes/migrate.svg?branch=master)](https://travis-ci.org/mattes/migrate)
[![GoDoc](https://godoc.org/github.com/mattes/migrate?status.svg)](https://godoc.org/github.com/mattes/migrate)
[![Coverage Status](https://coveralls.io/repos/github/mattes/migrate/badge.svg?branch=v3.0-prev)](https://coveralls.io/github/mattes/migrate?branch=v3.0-prev)
[![packagecloud.io](https://img.shields.io/badge/deb-packagecloud.io-844fec.svg)](https://packagecloud.io/mattes/migrate?filter=debs)
[![Build Status](https://img.shields.io/travis/com/golang-migrate/migrate/master.svg)](https://travis-ci.com/golang-migrate/migrate)
[![GoDoc](https://godoc.org/github.com/golang-migrate/migrate?status.svg)](https://godoc.org/github.com/golang-migrate/migrate)
[![Coverage Status](https://img.shields.io/coveralls/github/golang-migrate/migrate/master.svg)](https://coveralls.io/github/golang-migrate/migrate?branch=master)
[![packagecloud.io](https://img.shields.io/badge/deb-packagecloud.io-844fec.svg)](https://packagecloud.io/golang-migrate/migrate?filter=debs)
[![Docker Pulls](https://img.shields.io/docker/pulls/migrate/migrate.svg)](https://hub.docker.com/r/migrate/migrate/)
![Supported Go Versions](https://img.shields.io/badge/Go-1.14%2C%201.15-lightgrey.svg)
[![GitHub Release](https://img.shields.io/github/release/golang-migrate/migrate.svg)](https://github.com/golang-migrate/migrate/releases)
[![Go Report Card](https://goreportcard.com/badge/github.com/golang-migrate/migrate)](https://goreportcard.com/report/github.com/golang-migrate/migrate)
# migrate
__Database migrations written in Go. Use as [CLI](#cli-usage) or import as [library](#use-in-your-go-project).__
* Migrate reads migrations from [sources](#migration-sources)
* Migrate reads migrations from [sources](#migration-sources)
and applies them in correct order to a [database](#databases).
* Drivers are "dumb", migrate glues everything together and makes sure the logic is bulletproof.
* Drivers are "dumb", migrate glues everything together and makes sure the logic is bulletproof.
(Keeps the drivers lightweight, too.)
* Database drivers don't assume things or try to correct user input. When in doubt, fail.
Looking for [v1](https://github.com/mattes/migrate/tree/v1)?
* Database drivers don't assume things or try to correct user input. When in doubt, fail.
Forked from [mattes/migrate](https://github.com/mattes/migrate)
## Databases
Database drivers run migrations. [Add a new database?](database/driver.go)
* [PostgreSQL](database/postgres)
* [Redshift](database/redshift)
* [Ql](database/ql)
* [Cassandra](database/cassandra) ([todo #164](https://github.com/mattes/migrate/issues/164))
* [SQLite](database/sqlite) ([todo #165](https://github.com/mattes/migrate/issues/165))
* [MySQL/ MariaDB](database/mysql)
* [Neo4j](database/neo4j) ([todo #167](https://github.com/mattes/migrate/issues/167))
* [MongoDB](database/mongodb) ([todo #169](https://github.com/mattes/migrate/issues/169))
* [CrateDB](database/crate) ([todo #170](https://github.com/mattes/migrate/issues/170))
* [Shell](database/shell) ([todo #171](https://github.com/mattes/migrate/issues/171))
* [Google Cloud Spanner](database/spanner) ([todo #172](https://github.com/mattes/migrate/issues/172))
* [PostgreSQL](database/postgres)
* [Redshift](database/redshift)
* [Ql](database/ql)
* [Cassandra](database/cassandra)
* [SQLite](database/sqlite3) ([todo #165](https://github.com/mattes/migrate/issues/165))
* [SQLCipher](database/sqlcipher)
* [MySQL/ MariaDB](database/mysql)
* [Neo4j](database/neo4j)
* [MongoDB](database/mongodb)
* [CrateDB](database/crate) ([todo #170](https://github.com/mattes/migrate/issues/170))
* [Shell](database/shell) ([todo #171](https://github.com/mattes/migrate/issues/171))
* [Google Cloud Spanner](database/spanner)
* [CockroachDB](database/cockroachdb)
* [ClickHouse](database/clickhouse)
* [Firebird](database/firebird)
* [MS SQL Server](database/sqlserver)
### Database URLs
Database connection strings are specified via URLs. The URL format is driver dependent but generally has the form: `dbdriver://username:password@host:port/dbname?param1=true&param2=false`
Any [reserved URL characters](https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_reserved_characters) need to be escaped. Note, the `%` character also [needs to be escaped](https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_the_percent_character)
Explicitly, the following characters need to be escaped:
`!`, `#`, `$`, `%`, `&`, `'`, `(`, `)`, `*`, `+`, `,`, `/`, `:`, `;`, `=`, `?`, `@`, `[`, `]`
It's easiest to always run the URL parts of your DB connection URL (e.g. username, password, etc) through an URL encoder. See the example Python snippets below:
```bash
$ python3 -c 'import urllib.parse; print(urllib.parse.quote(input("String to encode: "), ""))'
String to encode: FAKEpassword!#$%&'()*+,/:;=?@[]
FAKEpassword%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D
$ python2 -c 'import urllib; print urllib.quote(raw_input("String to encode: "), "")'
String to encode: FAKEpassword!#$%&'()*+,/:;=?@[]
FAKEpassword%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D
$
```
## Migration Sources
Source drivers read migrations from local or remote sources. [Add a new source?](source/driver.go)
* [Filesystem](source/file) - read from fileystem (always included)
* [Go-Bindata](source/go-bindata) - read from embedded binary data ([jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata))
* [Github](source/github) - read from remote Github repositories
* [AWS S3](source/aws-s3) - read from Amazon Web Services S3
* [Google Cloud Storage](source/google-cloud-storage) - read from Google Cloud Platform Storage
* [Filesystem](source/file) - read from filesystem
* [Go-Bindata](source/go_bindata) - read from embedded binary data ([jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata))
* [GitHub](source/github) - read from remote GitHub repositories
* [GitHub Enterprise](source/github_ee) - read from remote GitHub Enterprise repositories
* [Bitbucket](source/bitbucket) - read from remote Bitbucket repositories
* [Gitlab](source/gitlab) - read from remote Gitlab repositories
* [AWS S3](source/aws_s3) - read from Amazon Web Services S3
* [Google Cloud Storage](source/google_cloud_storage) - read from Google Cloud Platform Storage
## CLI usage
* Simple wrapper around this library.
* Handles ctrl+c (SIGINT) gracefully.
* No config search paths, no config files, no magic ENV var injections.
* Simple wrapper around this library.
* Handles ctrl+c (SIGINT) gracefully.
* No config search paths, no config files, no magic ENV var injections.
__[CLI Documentation](cli)__
__[CLI Documentation](cmd/migrate)__
([brew todo #156](https://github.com/mattes/migrate/issues/156))
### Basic usage
```
$ brew install migrate --with-postgres
$ migrate -database postgres://localhost:5432/database up 2
```bash
$ migrate -source file://path/to/migrations -database postgres://localhost:5432/database up 2
```
### Docker usage
```bash
$ docker run -v {{ migration dir }}:/migrations --network host migrate/migrate
-path=/migrations/ -database postgres://localhost:5432/database up 2
```
## Use in your Go project
* API is stable and frozen for this release (v3.x).
* Package migrate has no external dependencies.
* Only import the drivers you need.
(check [dependency_tree.txt](https://github.com/mattes/migrate/releases) for each driver)
* To help prevent database corruptions, it supports graceful stops via `GracefulStop chan bool`.
* Bring your own logger.
* Uses `io.Reader` streams internally for low memory overhead.
* Thread-safe and no goroutine leaks.
* API is stable and frozen for this release (v3 & v4).
* Uses [Go modules](https://golang.org/cmd/go/#hdr-Modules__module_versions__and_more) to manage dependencies.
* To help prevent database corruptions, it supports graceful stops via `GracefulStop chan bool`.
* Bring your own logger.
* Uses `io.Reader` streams internally for low memory overhead.
* Thread-safe and no goroutine leaks.
__[Go Documentation](https://godoc.org/github.com/mattes/migrate)__
__[Go Documentation](https://godoc.org/github.com/golang-migrate/migrate)__
```go
import (
"github.com/mattes/migrate"
_ "github.com/mattes/migrate/database/postgres"
_ "github.com/mattes/migrate/source/github"
"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/github"
)
func main() {
@@ -97,9 +127,9 @@ Want to use an existing database client?
import (
"database/sql"
_ "github.com/lib/pq"
"github.com/mattes/migrate"
"github.com/mattes/migrate/database/postgres"
_ "github.com/mattes/migrate/source/file"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
func main() {
@@ -112,18 +142,35 @@ func main() {
}
```
## Getting started
Go to [getting started](GETTING_STARTED.md)
## Tutorials
* [CockroachDB](database/cockroachdb/TUTORIAL.md)
* [PostgreSQL](database/postgres/TUTORIAL.md)
(more tutorials to come)
## Migration files
Each migration has an up and down migration. [Why?](FAQ.md#why-two-separate-files-up-and-down-for-a-migration)
```
```bash
1481574547_create_users_table.up.sql
1481574547_create_users_table.down.sql
```
[Best practices: How to write migrations.](MIGRATIONS.md)
## Versions
Version | Supported? | Import | Notes
--------|------------|--------|------
**master** | :white_check_mark: | `import "github.com/golang-migrate/migrate/v4"` | New features and bug fixes arrive here first |
**v4** | :white_check_mark: | `import "github.com/golang-migrate/migrate/v4"` | Used for stable releases |
**v3** | :x: | `import "github.com/golang-migrate/migrate"` (with package manager) or `import "gopkg.in/golang-migrate/migrate.v3"` (not recommended) | **DO NOT USE** - No longer supported |
## Development and Contributing
@@ -132,12 +179,6 @@ read the [development guide](CONTRIBUTING.md).
Also have a look at the [FAQ](FAQ.md).
---
__Alternatives__
https://bitbucket.org/liamstask/goose, https://github.com/tanel/dbmigrate,
https://github.com/BurntSushi/migration, https://github.com/DavidHuie/gomigrate,
https://github.com/rubenv/sql-migrate
Looking for alternatives? [https://awesome-go.com/#database](https://awesome-go.com/#database).
+2 -104
View File
@@ -1,105 +1,3 @@
# migrate CLI
# Deprecated
## Installation
#### With Go toolchain
```
$ go get -u -d github.com/mattes/migrate/cli
$ go build -tags 'postgres' -o /usr/local/bin/migrate github.com/mattes/migrate/cli
```
#### MacOS
([todo #156](https://github.com/mattes/migrate/issues/156))
```
$ brew install migrate --with-postgres
```
#### Linux (*.deb package)
```
$ curl -L https://packagecloud.io/mattes/migrate/gpgkey | apt-key add -
$ echo "deb https://packagecloud.io/mattes/migrate/ubuntu/ xenial main" > /etc/apt/sources.list.d/migrate.list
$ apt-get update
$ apt-get install -y migrate
```
#### Download pre-build binary (Windows, MacOS, or Linux)
[Release Downloads](https://github.com/mattes/migrate/releases)
```
$ curl -L https://github.com/mattes/migrate/releases/download/$version/migrate.$platform-amd64.tar.gz | tar xvz
```
## Usage
```
$ migrate -help
Usage: migrate OPTIONS COMMAND [arg...]
migrate [ -version | -help ]
Options:
-source Location of the migrations (driver://url)
-path Shorthand for -source=file://path
-database Run migrations against this database (driver://url)
-prefetch N Number of migrations to load in advance before executing (default 10)
-lock-timeout N Allow N seconds to acquire database lock (default 15)
-verbose Print verbose logging
-version Print version
-help Print usage
Commands:
goto V Migrate to version V
up [N] Apply all or N up migrations
down [N] Apply all or N down migrations
drop Drop everyting inside database
force V Set version V but don't run migration (ignores dirty state)
version Print current migration version
```
So let's say you want to run the first two migrations
```
$ migrate -database postgres://localhost:5432/database up 2
```
If your migrations are hosted on github
```
$ migrate -source github://mattes:personal-access-token@mattes/migrate_test \
-database postgres://localhost:5432/database down 2
```
The CLI will gracefully stop at a safe point when SIGINT (ctrl+c) is received.
Send SIGKILL for immediate halt.
## Reading CLI arguments from somewhere else
##### ENV variables
```
$ migrate -database "$MY_MIGRATE_DATABASE"
```
##### JSON files
Check out https://stedolan.github.io/jq/
```
$ migrate -database "$(cat config.json | jq '.database')"
```
##### YAML files
````
$ migrate -database "$(cat config/database.yml | ruby -ryaml -e "print YAML.load(STDIN.read)['database']")"
$ migrate -database "$(cat config/database.yml | python -c 'import yaml,sys;print yaml.safe_load(sys.stdin)["database"]')"
```
Use [cmd/migrate](../cmd/migrate) instead
-7
View File
@@ -1,7 +0,0 @@
// +build aws-s3
package main
import (
_ "github.com/mattes/migrate/source/aws-s3"
)
-7
View File
@@ -1,7 +0,0 @@
// +build github
package main
import (
_ "github.com/mattes/migrate/source/github"
)
-7
View File
@@ -1,7 +0,0 @@
// +build go-bindata
package main
import (
_ "github.com/mattes/migrate/source/go-bindata"
)
-7
View File
@@ -1,7 +0,0 @@
// +build google-cloud-storage
package main
import (
_ "github.com/mattes/migrate/source/google-cloud-storage"
)
-7
View File
@@ -1,7 +0,0 @@
// +build mysql
package main
import (
_ "github.com/mattes/migrate/database/mysql"
)
-7
View File
@@ -1,7 +0,0 @@
// +build postgres
package main
import (
_ "github.com/mattes/migrate/database/postgres"
)
-7
View File
@@ -1,7 +0,0 @@
// +build ql
package main
import (
_ "github.com/mattes/migrate/database/ql"
)
-7
View File
@@ -1,7 +0,0 @@
// +build redshift
package main
import (
_ "github.com/mattes/migrate/database/redshift"
)
-81
View File
@@ -1,81 +0,0 @@
package main
import (
"github.com/mattes/migrate"
_ "github.com/mattes/migrate/database/stub" // TODO remove again
_ "github.com/mattes/migrate/source/file"
)
func gotoCmd(m *migrate.Migrate, v uint) {
if err := m.Migrate(v); err != nil {
if err != migrate.ErrNoChange {
log.fatalErr(err)
} else {
log.Println(err)
}
}
}
func upCmd(m *migrate.Migrate, limit int) {
if limit >= 0 {
if err := m.Steps(limit); err != nil {
if err != migrate.ErrNoChange {
log.fatalErr(err)
} else {
log.Println(err)
}
}
} else {
if err := m.Up(); err != nil {
if err != migrate.ErrNoChange {
log.fatalErr(err)
} else {
log.Println(err)
}
}
}
}
func downCmd(m *migrate.Migrate, limit int) {
if limit >= 0 {
if err := m.Steps(-limit); err != nil {
if err != migrate.ErrNoChange {
log.fatalErr(err)
} else {
log.Println(err)
}
}
} else {
if err := m.Down(); err != nil {
if err != migrate.ErrNoChange {
log.fatalErr(err)
} else {
log.Println(err)
}
}
}
}
func dropCmd(m *migrate.Migrate) {
if err := m.Drop(); err != nil {
log.fatalErr(err)
}
}
func forceCmd(m *migrate.Migrate, v int) {
if err := m.Force(v); err != nil {
log.fatalErr(err)
}
}
func versionCmd(m *migrate.Migrate) {
v, dirty, err := m.Version()
if err != nil {
log.fatalErr(err)
}
if dirty {
log.Printf("%v (dirty)\n", v)
} else {
log.Println(v)
}
}
-12
View File
@@ -1,12 +0,0 @@
FROM ubuntu:xenial
RUN apt-get update && \
apt-get install -y curl apt-transport-https
RUN curl -L https://packagecloud.io/mattes/migrate/gpgkey | apt-key add - && \
echo "deb https://packagecloud.io/mattes/migrate/ubuntu/ xenial main" > /etc/apt/sources.list.d/migrate.list && \
apt-get update && \
apt-get install -y migrate
RUN migrate -version
+3 -205
View File
@@ -1,210 +1,8 @@
package main
import (
"flag"
"fmt"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"github.com/mattes/migrate"
)
// set main log
var log = &Log{}
import "github.com/golang-migrate/migrate/v4/internal/cli"
// Deprecated, please use cmd/migrate
func main() {
helpPtr := flag.Bool("help", false, "")
versionPtr := flag.Bool("version", false, "")
verbosePtr := flag.Bool("verbose", false, "")
prefetchPtr := flag.Uint("prefetch", 10, "")
lockTimeoutPtr := flag.Uint("lock-timeout", 15, "")
pathPtr := flag.String("path", "", "")
databasePtr := flag.String("database", "", "")
sourcePtr := flag.String("source", "", "")
flag.Usage = func() {
fmt.Fprint(os.Stderr,
`Usage: migrate OPTIONS COMMAND [arg...]
migrate [ -version | -help ]
Options:
-source Location of the migrations (driver://url)
-path Shorthand for -source=file://path
-database Run migrations against this database (driver://url)
-prefetch N Number of migrations to load in advance before executing (default 10)
-lock-timeout N Allow N seconds to acquire database lock (default 15)
-verbose Print verbose logging
-version Print version
-help Print usage
Commands:
goto V Migrate to version V
up [N] Apply all or N up migrations
down [N] Apply all or N down migrations
drop Drop everyting inside database
force V Set version V but don't run migration (ignores dirty state)
version Print current migration version
`)
}
flag.Parse()
// initialize logger
log.verbose = *verbosePtr
// show cli version
if *versionPtr {
fmt.Fprintln(os.Stderr, Version)
os.Exit(0)
}
// show help
if *helpPtr {
flag.Usage()
os.Exit(0)
}
// translate -path into -source if given
if *sourcePtr == "" && *pathPtr != "" {
*sourcePtr = fmt.Sprintf("file://%v", *pathPtr)
}
// initialize migrate
// don't catch migraterErr here and let each command decide
// how it wants to handle the error
migrater, migraterErr := migrate.New(*sourcePtr, *databasePtr)
defer func() {
if migraterErr == nil {
migrater.Close()
}
}()
if migraterErr == nil {
migrater.Log = log
migrater.PrefetchMigrations = *prefetchPtr
migrater.LockTimeout = time.Duration(int64(*lockTimeoutPtr)) * time.Second
// handle Ctrl+c
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT)
go func() {
for range signals {
log.Println("Stopping after this running migration ...")
migrater.GracefulStop <- true
return
}
}()
}
startTime := time.Now()
switch flag.Arg(0) {
case "goto":
if migraterErr != nil {
log.fatalErr(migraterErr)
}
if flag.Arg(1) == "" {
log.fatal("error: please specify version argument V")
}
v, err := strconv.ParseUint(flag.Arg(1), 10, 64)
if err != nil {
log.fatal("error: can't read version argument V")
}
gotoCmd(migrater, uint(v))
if log.verbose {
log.Println("Finished after", time.Now().Sub(startTime))
}
case "up":
if migraterErr != nil {
log.fatalErr(migraterErr)
}
limit := -1
if flag.Arg(1) != "" {
n, err := strconv.ParseUint(flag.Arg(1), 10, 64)
if err != nil {
log.fatal("error: can't read limit argument N")
}
limit = int(n)
}
upCmd(migrater, limit)
if log.verbose {
log.Println("Finished after", time.Now().Sub(startTime))
}
case "down":
if migraterErr != nil {
log.fatalErr(migraterErr)
}
limit := -1
if flag.Arg(1) != "" {
n, err := strconv.ParseUint(flag.Arg(1), 10, 64)
if err != nil {
log.fatal("error: can't read limit argument N")
}
limit = int(n)
}
downCmd(migrater, limit)
if log.verbose {
log.Println("Finished after", time.Now().Sub(startTime))
}
case "drop":
if migraterErr != nil {
log.fatalErr(migraterErr)
}
dropCmd(migrater)
if log.verbose {
log.Println("Finished after", time.Now().Sub(startTime))
}
case "force":
if migraterErr != nil {
log.fatalErr(migraterErr)
}
if flag.Arg(1) == "" {
log.fatal("error: please specify version argument V")
}
v, err := strconv.ParseInt(flag.Arg(1), 10, 64)
if err != nil {
log.fatal("error: can't read version argument V")
}
if v < -1 {
log.fatal("error: argument V must be >= -1")
}
forceCmd(migrater, int(v))
if log.verbose {
log.Println("Finished after", time.Now().Sub(startTime))
}
case "version":
if migraterErr != nil {
log.fatalErr(migraterErr)
}
versionCmd(migrater)
default:
flag.Usage()
os.Exit(0)
}
cli.Main(Version)
}
+133
View File
@@ -0,0 +1,133 @@
# migrate CLI
## Installation
### Download pre-built binary (Windows, MacOS, or Linux)
[Release Downloads](https://github.com/golang-migrate/migrate/releases)
```bash
$ curl -L https://github.com/golang-migrate/migrate/releases/download/$version/migrate.$platform-amd64.tar.gz | tar xvz
```
### MacOS
```bash
$ brew install golang-migrate
```
### Windows
Using [scoop](https://scoop.sh/)
```bash
$ scoop install migrate
```
### Linux (*.deb package)
```bash
$ curl -L https://packagecloud.io/golang-migrate/migrate/gpgkey | apt-key add -
$ echo "deb https://packagecloud.io/golang-migrate/migrate/ubuntu/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/migrate.list
$ apt-get update
$ apt-get install -y migrate
```
### With Go toolchain
#### Versioned
```bash
$ go get -u -d github.com/golang-migrate/migrate/cmd/migrate
$ cd $GOPATH/src/github.com/golang-migrate/migrate/cmd/migrate
$ git checkout $TAG # e.g. v4.1.0
$ go build -tags 'postgres' -ldflags="-X main.Version=$(git describe --tags)" -o $GOPATH/bin/migrate $GOPATH/src/github.com/golang-migrate/migrate/cmd/migrate
```
#### Unversioned
```bash
$ go get -tags 'postgres' -u github.com/golang-migrate/migrate/cmd/migrate
```
[Make sure](https://github.com/golang-migrate/migrate/pull/257#issuecomment-705249902) you're not installing the `migrate` CLI from a module. e.g. there should not be any `go.mod` files in your current directory or any directory from your current one to the root
#### Notes
1. Requires a version of Go that [supports modules](https://golang.org/cmd/go/#hdr-Preliminary_module_support). e.g. Go 1.11+
1. These examples build the cli which will only work with postgres. In order
to build the cli for use with other databases, replace the `postgres` build tag
with the appropriate database tag(s) for the databases desired. The tags
correspond to the names of the sub-packages underneath the
[`database`](../database) package.
1. Similarly to the database build tags, if you need to support other sources, use the appropriate build tag(s).
1. Support for build constraints will be removed in the future: https://github.com/golang-migrate/migrate/issues/60
## Usage
```bash
$ migrate -help
Usage: migrate OPTIONS COMMAND [arg...]
migrate [ -version | -help ]
Options:
-source Location of the migrations (driver://url)
-path Shorthand for -source=file://path
-database Run migrations against this database (driver://url)
-prefetch N Number of migrations to load in advance before executing (default 10)
-lock-timeout N Allow N seconds to acquire database lock (default 15)
-verbose Print verbose logging
-version Print version
-help Print usage
Commands:
create [-ext E] [-dir D] [-seq] [-digits N] [-format] NAME
Create a set of timestamped up/down migrations titled NAME, in directory D with extension E.
Use -seq option to generate sequential up/down migrations with N digits.
Use -format option to specify a Go time format string.
goto V Migrate to version V
up [N] Apply all or N up migrations
down [N] Apply all or N down migrations
drop Drop everything inside database
force V Set version V but don't run migration (ignores dirty state)
version Print current migration version
```
So let's say you want to run the first two migrations
```bash
$ migrate -source file://path/to/migrations -database postgres://localhost:5432/database up 2
```
If your migrations are hosted on github
```bash
$ migrate -source github://mattes:personal-access-token@mattes/migrate_test \
-database postgres://localhost:5432/database down 2
```
The CLI will gracefully stop at a safe point when SIGINT (ctrl+c) is received.
Send SIGKILL for immediate halt.
## Reading CLI arguments from somewhere else
### ENV variables
```bash
$ migrate -database "$MY_MIGRATE_DATABASE"
```
### JSON files
Check out https://stedolan.github.io/jq/
```bash
$ migrate -database "$(cat config.json | jq '.database')"
```
### YAML files
```bash
$ migrate -database "$(cat config/database.yml | ruby -ryaml -e "print YAML.load(STDIN.read)['database']")"
$ migrate -database "$(cat config/database.yml | python -c 'import yaml,sys;print yaml.safe_load(sys.stdin)["database"]')"
```
+15
View File
@@ -0,0 +1,15 @@
FROM ubuntu:bionic
RUN apt-get update && \
apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent
RUN curl -sSL https://packagecloud.io/golang-migrate/migrate/gpgkey | apt-key add -
RUN echo "deb https://packagecloud.io/golang-migrate/migrate/ubuntu/ bionic main" > /etc/apt/sources.list.d/migrate.list
RUN apt-get update && \
apt-get install -y migrate
RUN migrate -version
+7
View File
@@ -0,0 +1,7 @@
package main
import "github.com/golang-migrate/migrate/v4/internal/cli"
func main() {
cli.Main(Version)
}
+4
View File
@@ -0,0 +1,4 @@
package main
// Version is set in Makefile with build flags
var Version = "dev"
+38
View File
@@ -0,0 +1,38 @@
# Cassandra
* Drop command will not work on Cassandra 2.X because it rely on
system_schema table which comes with 3.X
* Other commands should work properly but are **not tested**
* The Cassandra driver (gocql) does not natively support executing multiple statements in a single query. To allow for multiple statements in a single migration, you can use the `x-multi-statement` param. There are two important caveats:
* This mode splits the migration text into separately-executed statements by a semi-colon `;`. Thus `x-multi-statement` cannot be used when a statement in the migration contains a string with a semi-colon.
* The queries are not executed in any sort of transaction/batch, meaning you are responsible for fixing partial migrations.
## Usage
`cassandra://host:port/keyspace?param1=value&param2=value2`
| URL Query | Default value | Description |
|------------|-------------|-----------|
| `x-migrations-table` | schema_migrations | Name of the migrations table |
| `x-multi-statement` | false | Enable multiple statements to be ran in a single migration (See note above) |
| `port` | 9042 | The port to bind to |
| `consistency` | ALL | Migration consistency
| `protocol` | | Cassandra protocol version (3 or 4)
| `timeout` | 1 minute | Migration timeout
| `username` | nil | Username to use when authenticating. |
| `password` | nil | Password to use when authenticating. |
| `sslcert` | | Cert file location. The file must contain PEM encoded data. |
| `sslkey` | | Key file location. The file must contain PEM encoded data. |
| `sslrootcert` | | The location of the root certificate file. The file must contain PEM encoded data. |
| `sslmode` | | Whether or not to use SSL (disable\|require\|verify-ca\|verify-full) |
`timeout` is parsed using [time.ParseDuration(s string)](https://golang.org/pkg/time/#ParseDuration)
## Upgrading from v1
1. Write down the current migration version from schema_migrations
2. `DROP TABLE schema_migrations`
4. Download and install the latest migrate version.
5. Force the current migration version with `migrate force <current_version>`.
+324
View File
@@ -0,0 +1,324 @@
package cassandra
import (
"errors"
"fmt"
"io"
"io/ioutil"
nurl "net/url"
"strconv"
"strings"
"time"
"github.com/gocql/gocql"
"github.com/golang-migrate/migrate/v4/database"
"github.com/golang-migrate/migrate/v4/database/multistmt"
"github.com/hashicorp/go-multierror"
)
func init() {
db := new(Cassandra)
database.Register("cassandra", db)
}
var (
multiStmtDelimiter = []byte(";")
DefaultMultiStatementMaxSize = 10 * 1 << 20 // 10 MB
)
var DefaultMigrationsTable = "schema_migrations"
var (
ErrNilConfig = errors.New("no config")
ErrNoKeyspace = errors.New("no keyspace provided")
ErrDatabaseDirty = errors.New("database is dirty")
ErrClosedSession = errors.New("session is closed")
)
type Config struct {
MigrationsTable string
KeyspaceName string
MultiStatementEnabled bool
MultiStatementMaxSize int
}
type Cassandra struct {
session *gocql.Session
isLocked bool
// Open and WithInstance need to guarantee that config is never nil
config *Config
}
func WithInstance(session *gocql.Session, config *Config) (database.Driver, error) {
if config == nil {
return nil, ErrNilConfig
} else if len(config.KeyspaceName) == 0 {
return nil, ErrNoKeyspace
}
if session.Closed() {
return nil, ErrClosedSession
}
if len(config.MigrationsTable) == 0 {
config.MigrationsTable = DefaultMigrationsTable
}
if config.MultiStatementMaxSize <= 0 {
config.MultiStatementMaxSize = DefaultMultiStatementMaxSize
}
c := &Cassandra{
session: session,
config: config,
}
if err := c.ensureVersionTable(); err != nil {
return nil, err
}
return c, nil
}
func (c *Cassandra) Open(url string) (database.Driver, error) {
u, err := nurl.Parse(url)
if err != nil {
return nil, err
}
// Check for missing mandatory attributes
if len(u.Path) == 0 {
return nil, ErrNoKeyspace
}
cluster := gocql.NewCluster(u.Host)
cluster.Keyspace = strings.TrimPrefix(u.Path, "/")
cluster.Consistency = gocql.All
cluster.Timeout = 1 * time.Minute
if len(u.Query().Get("username")) > 0 && len(u.Query().Get("password")) > 0 {
authenticator := gocql.PasswordAuthenticator{
Username: u.Query().Get("username"),
Password: u.Query().Get("password"),
}
cluster.Authenticator = authenticator
}
// Retrieve query string configuration
if len(u.Query().Get("consistency")) > 0 {
var consistency gocql.Consistency
consistency, err = parseConsistency(u.Query().Get("consistency"))
if err != nil {
return nil, err
}
cluster.Consistency = consistency
}
if len(u.Query().Get("protocol")) > 0 {
var protoversion int
protoversion, err = strconv.Atoi(u.Query().Get("protocol"))
if err != nil {
return nil, err
}
cluster.ProtoVersion = protoversion
}
if len(u.Query().Get("timeout")) > 0 {
var timeout time.Duration
timeout, err = time.ParseDuration(u.Query().Get("timeout"))
if err != nil {
return nil, err
}
cluster.Timeout = timeout
}
if len(u.Query().Get("sslmode")) > 0 {
if u.Query().Get("sslmode") != "disable" {
sslOpts := &gocql.SslOptions{}
if len(u.Query().Get("sslrootcert")) > 0 {
sslOpts.CaPath = u.Query().Get("sslrootcert")
}
if len(u.Query().Get("sslcert")) > 0 {
sslOpts.CertPath = u.Query().Get("sslcert")
}
if len(u.Query().Get("sslkey")) > 0 {
sslOpts.KeyPath = u.Query().Get("sslkey")
}
if u.Query().Get("sslmode") == "verify-full" {
sslOpts.EnableHostVerification = true
}
cluster.SslOpts = sslOpts
}
}
session, err := cluster.CreateSession()
if err != nil {
return nil, err
}
multiStatementMaxSize := DefaultMultiStatementMaxSize
if s := u.Query().Get("x-multi-statement-max-size"); len(s) > 0 {
multiStatementMaxSize, err = strconv.Atoi(s)
if err != nil {
return nil, err
}
}
return WithInstance(session, &Config{
KeyspaceName: strings.TrimPrefix(u.Path, "/"),
MigrationsTable: u.Query().Get("x-migrations-table"),
MultiStatementEnabled: u.Query().Get("x-multi-statement") == "true",
MultiStatementMaxSize: multiStatementMaxSize,
})
}
func (c *Cassandra) Close() error {
c.session.Close()
return nil
}
func (c *Cassandra) Lock() error {
if c.isLocked {
return database.ErrLocked
}
c.isLocked = true
return nil
}
func (c *Cassandra) Unlock() error {
c.isLocked = false
return nil
}
func (c *Cassandra) Run(migration io.Reader) error {
if c.config.MultiStatementEnabled {
var err error
if e := multistmt.Parse(migration, multiStmtDelimiter, c.config.MultiStatementMaxSize, func(m []byte) bool {
tq := strings.TrimSpace(string(m))
if tq == "" {
return true
}
if e := c.session.Query(tq).Exec(); e != nil {
err = database.Error{OrigErr: e, Err: "migration failed", Query: m}
return false
}
return true
}); e != nil {
return e
}
return err
}
migr, err := ioutil.ReadAll(migration)
if err != nil {
return err
}
// run migration
if err := c.session.Query(string(migr)).Exec(); err != nil {
// TODO: cast to Cassandra error and get line number
return database.Error{OrigErr: err, Err: "migration failed", Query: migr}
}
return nil
}
func (c *Cassandra) SetVersion(version int, dirty bool) error {
query := `TRUNCATE "` + c.config.MigrationsTable + `"`
if err := c.session.Query(query).Exec(); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
// Also re-write the schema version for nil dirty versions to prevent
// empty schema version for failed down migration on the first migration
// See: https://github.com/golang-migrate/migrate/issues/330
if version >= 0 || (version == database.NilVersion && dirty) {
query = `INSERT INTO "` + c.config.MigrationsTable + `" (version, dirty) VALUES (?, ?)`
if err := c.session.Query(query, version, dirty).Exec(); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
}
return nil
}
// Return current keyspace version
func (c *Cassandra) Version() (version int, dirty bool, err error) {
query := `SELECT version, dirty FROM "` + c.config.MigrationsTable + `" LIMIT 1`
err = c.session.Query(query).Scan(&version, &dirty)
switch {
case err == gocql.ErrNotFound:
return database.NilVersion, false, nil
case err != nil:
if _, ok := err.(*gocql.Error); ok {
return database.NilVersion, false, nil
}
return 0, false, &database.Error{OrigErr: err, Query: []byte(query)}
default:
return version, dirty, nil
}
}
func (c *Cassandra) Drop() error {
// select all tables in current schema
query := fmt.Sprintf(`SELECT table_name from system_schema.tables WHERE keyspace_name='%s'`, c.config.KeyspaceName)
iter := c.session.Query(query).Iter()
var tableName string
for iter.Scan(&tableName) {
err := c.session.Query(fmt.Sprintf(`DROP TABLE %s`, tableName)).Exec()
if err != nil {
return err
}
}
return nil
}
// ensureVersionTable checks if versions table exists and, if not, creates it.
// Note that this function locks the database, which deviates from the usual
// convention of "caller locks" in the Cassandra type.
func (c *Cassandra) ensureVersionTable() (err error) {
if err = c.Lock(); err != nil {
return err
}
defer func() {
if e := c.Unlock(); e != nil {
if err == nil {
err = e
} else {
err = multierror.Append(err, e)
}
}
}()
err = c.session.Query(fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (version bigint, dirty boolean, PRIMARY KEY(version))", c.config.MigrationsTable)).Exec()
if err != nil {
return err
}
if _, _, err = c.Version(); err != nil {
return err
}
return nil
}
// ParseConsistency wraps gocql.ParseConsistency
// to return an error instead of a panicking.
func parseConsistency(consistencyStr string) (consistency gocql.Consistency, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("Failed to parse consistency \"%s\": %v", consistencyStr, r)
}
}
}()
consistency = gocql.ParseConsistency(consistencyStr)
return consistency, nil
}
+106
View File
@@ -0,0 +1,106 @@
package cassandra
import (
"context"
"fmt"
"github.com/golang-migrate/migrate/v4"
"strconv"
"testing"
)
import (
"github.com/dhui/dktest"
"github.com/gocql/gocql"
)
import (
dt "github.com/golang-migrate/migrate/v4/database/testing"
"github.com/golang-migrate/migrate/v4/dktesting"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
var (
opts = dktest.Options{PortRequired: true, ReadyFunc: isReady}
// Supported versions: http://cassandra.apache.org/download/
// Although Cassandra 2.x is supported by the Apache Foundation,
// the migrate db driver only supports Cassandra 3.x since it uses
// the system_schema keyspace.
specs = []dktesting.ContainerSpec{
{ImageName: "cassandra:3.0", Options: opts},
{ImageName: "cassandra:3.11", Options: opts},
}
)
func isReady(ctx context.Context, c dktest.ContainerInfo) bool {
// Cassandra exposes 5 ports (7000, 7001, 7199, 9042 & 9160)
// We only need the port bound to 9042
ip, portStr, err := c.Port(9042)
if err != nil {
return false
}
port, err := strconv.Atoi(portStr)
if err != nil {
return false
}
cluster := gocql.NewCluster(ip)
cluster.Port = port
cluster.Consistency = gocql.All
p, err := cluster.CreateSession()
if err != nil {
return false
}
defer p.Close()
// Create keyspace for tests
if err = p.Query("CREATE KEYSPACE testks WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor':1}").Exec(); err != nil {
return false
}
return true
}
func Test(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.Port(9042)
if err != nil {
t.Fatal("Unable to get mapped port:", err)
}
addr := fmt.Sprintf("cassandra://%v:%v/testks", ip, port)
p := &Cassandra{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
dt.Test(t, d, []byte("SELECT table_name from system_schema.tables"))
})
}
func TestMigrate(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.Port(9042)
if err != nil {
t.Fatal("Unable to get mapped port:", err)
}
addr := fmt.Sprintf("cassandra://%v:%v/testks", ip, port)
p := &Cassandra{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
m, err := migrate.NewWithDatabaseInstance("file://./examples/migrations", "testks", d)
if err != nil {
t.Fatal(err)
}
dt.TestMigrate(t, m)
})
}
@@ -0,0 +1 @@
SELECT table_name from system_schema.tables
@@ -0,0 +1 @@
SELECT table_name from system_schema.tables
+19
View File
@@ -0,0 +1,19 @@
# ClickHouse
`clickhouse://host:port?username=user&password=qwerty&database=clicks&x-multi-statement=true`
| URL Query | Description |
|------------|-------------|
| `x-migrations-table`| Name of the migrations table |
| `database` | The name of the database to connect to |
| `username` | The user to sign in as |
| `password` | The user's password |
| `host` | The host to connect to. |
| `port` | The port to bind to. |
| `x-multi-statement` | false | Enable multiple statements to be ran in a single migration (See note below) |
## Notes
* The Clickhouse driver does not natively support executing multipe statements in a single query. To allow for multiple statements in a single migration, you can use the `x-multi-statement` param. There are two important caveats:
* This mode splits the migration text into separately-executed statements by a semi-colon `;`. Thus `x-multi-statement` cannot be used when a statement in the migration contains a string with a semi-colon.
* The queries are not executed in any sort of transaction/batch, meaning you are responsible for fixing partial migrations.
+265
View File
@@ -0,0 +1,265 @@
package clickhouse
import (
"database/sql"
"fmt"
"io"
"io/ioutil"
"net/url"
"strconv"
"strings"
"time"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database"
"github.com/golang-migrate/migrate/v4/database/multistmt"
"github.com/hashicorp/go-multierror"
)
var (
multiStmtDelimiter = []byte(";")
DefaultMigrationsTable = "schema_migrations"
DefaultMultiStatementMaxSize = 10 * 1 << 20 // 10 MB
ErrNilConfig = fmt.Errorf("no config")
)
type Config struct {
DatabaseName string
MigrationsTable string
MultiStatementEnabled bool
MultiStatementMaxSize int
}
func init() {
database.Register("clickhouse", &ClickHouse{})
}
func WithInstance(conn *sql.DB, config *Config) (database.Driver, error) {
if config == nil {
return nil, ErrNilConfig
}
if err := conn.Ping(); err != nil {
return nil, err
}
ch := &ClickHouse{
conn: conn,
config: config,
}
if err := ch.init(); err != nil {
return nil, err
}
return ch, nil
}
type ClickHouse struct {
conn *sql.DB
config *Config
}
func (ch *ClickHouse) Open(dsn string) (database.Driver, error) {
purl, err := url.Parse(dsn)
if err != nil {
return nil, err
}
q := migrate.FilterCustomQuery(purl)
q.Scheme = "tcp"
conn, err := sql.Open("clickhouse", q.String())
if err != nil {
return nil, err
}
multiStatementMaxSize := DefaultMultiStatementMaxSize
if s := purl.Query().Get("x-multi-statement-max-size"); len(s) > 0 {
multiStatementMaxSize, err = strconv.Atoi(s)
if err != nil {
return nil, err
}
}
ch = &ClickHouse{
conn: conn,
config: &Config{
MigrationsTable: purl.Query().Get("x-migrations-table"),
DatabaseName: purl.Query().Get("database"),
MultiStatementEnabled: purl.Query().Get("x-multi-statement") == "true",
MultiStatementMaxSize: multiStatementMaxSize,
},
}
if err := ch.init(); err != nil {
return nil, err
}
return ch, nil
}
func (ch *ClickHouse) init() error {
if len(ch.config.DatabaseName) == 0 {
if err := ch.conn.QueryRow("SELECT currentDatabase()").Scan(&ch.config.DatabaseName); err != nil {
return err
}
}
if len(ch.config.MigrationsTable) == 0 {
ch.config.MigrationsTable = DefaultMigrationsTable
}
if ch.config.MultiStatementMaxSize <= 0 {
ch.config.MultiStatementMaxSize = DefaultMultiStatementMaxSize
}
return ch.ensureVersionTable()
}
func (ch *ClickHouse) Run(r io.Reader) error {
if ch.config.MultiStatementEnabled {
var err error
if e := multistmt.Parse(r, multiStmtDelimiter, ch.config.MultiStatementMaxSize, func(m []byte) bool {
tq := strings.TrimSpace(string(m))
if tq == "" {
return true
}
if _, e := ch.conn.Exec(string(m)); e != nil {
err = database.Error{OrigErr: e, Err: "migration failed", Query: m}
return false
}
return true
}); e != nil {
return e
}
return err
}
migration, err := ioutil.ReadAll(r)
if err != nil {
return err
}
if _, err := ch.conn.Exec(string(migration)); err != nil {
return database.Error{OrigErr: err, Err: "migration failed", Query: migration}
}
return nil
}
func (ch *ClickHouse) Version() (int, bool, error) {
var (
version int
dirty uint8
query = "SELECT version, dirty FROM `" + ch.config.MigrationsTable + "` ORDER BY sequence DESC LIMIT 1"
)
if err := ch.conn.QueryRow(query).Scan(&version, &dirty); err != nil {
if err == sql.ErrNoRows {
return database.NilVersion, false, nil
}
return 0, false, &database.Error{OrigErr: err, Query: []byte(query)}
}
return version, dirty == 1, nil
}
func (ch *ClickHouse) SetVersion(version int, dirty bool) error {
var (
bool = func(v bool) uint8 {
if v {
return 1
}
return 0
}
tx, err = ch.conn.Begin()
)
if err != nil {
return err
}
query := "INSERT INTO " + ch.config.MigrationsTable + " (version, dirty, sequence) VALUES (?, ?, ?)"
if _, err := tx.Exec(query, version, bool(dirty), time.Now().UnixNano()); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
return tx.Commit()
}
// ensureVersionTable checks if versions table exists and, if not, creates it.
// Note that this function locks the database, which deviates from the usual
// convention of "caller locks" in the ClickHouse type.
func (ch *ClickHouse) ensureVersionTable() (err error) {
if err = ch.Lock(); err != nil {
return err
}
defer func() {
if e := ch.Unlock(); e != nil {
if err == nil {
err = e
} else {
err = multierror.Append(err, e)
}
}
}()
var (
table string
query = "SHOW TABLES FROM " + ch.config.DatabaseName + " LIKE '" + ch.config.MigrationsTable + "'"
)
// check if migration table exists
if err := ch.conn.QueryRow(query).Scan(&table); err != nil {
if err != sql.ErrNoRows {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
} else {
return nil
}
// if not, create the empty migration table
query = `
CREATE TABLE ` + ch.config.MigrationsTable + ` (
version Int64,
dirty UInt8,
sequence UInt64
) Engine=TinyLog
`
if _, err := ch.conn.Exec(query); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
return nil
}
func (ch *ClickHouse) Drop() (err error) {
query := "SHOW TABLES FROM " + ch.config.DatabaseName
tables, err := ch.conn.Query(query)
if err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
defer func() {
if errClose := tables.Close(); errClose != nil {
err = multierror.Append(err, errClose)
}
}()
for tables.Next() {
var table string
if err := tables.Scan(&table); err != nil {
return err
}
query = "DROP TABLE IF EXISTS " + ch.config.DatabaseName + "." + table
if _, err := ch.conn.Exec(query); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
}
if err := tables.Err(); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
return nil
}
func (ch *ClickHouse) Lock() error { return nil }
func (ch *ClickHouse) Unlock() error { return nil }
func (ch *ClickHouse) Close() error { return ch.conn.Close() }
@@ -0,0 +1 @@
DROP TABLE IF EXISTS test_1;
@@ -0,0 +1,3 @@
CREATE TABLE test_1 (
Date Date
) Engine=Memory;
@@ -0,0 +1 @@
DROP TABLE IF EXISTS test_2;
@@ -0,0 +1,3 @@
CREATE TABLE test_2 (
Date Date
) Engine=Memory;
+19
View File
@@ -0,0 +1,19 @@
# cockroachdb
`cockroachdb://user:password@host:port/dbname?query` (`cockroach://`, and `crdb-postgres://` work, too)
| URL Query | WithInstance Config | Description |
|------------|---------------------|-------------|
| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |
| `x-lock-table` | `LockTable` | Name of the table which maintains the migration lock |
| `x-force-lock` | `ForceLock` | Force lock acquisition to fix faulty migrations which may not have released the schema lock (Boolean, default is `false`) |
| `dbname` | `DatabaseName` | The name of the database to connect to |
| `user` | | The user to sign in as |
| `password` | | The user's password |
| `host` | | The host to connect to. Values that start with / are for unix domain sockets. (default is localhost) |
| `port` | | The port to bind to. (default is 5432) |
| `connect_timeout` | | Maximum wait for connection, in seconds. Zero or not specified means wait indefinitely. |
| `sslcert` | | Cert file location. The file must contain PEM encoded data. |
| `sslkey` | | Key file location. The file must contain PEM encoded data. |
| `sslrootcert` | | The location of the root certificate file. The file must contain PEM encoded data. |
| `sslmode` | | Whether or not to use SSL (disable\|require\|verify-ca\|verify-full) |
+142
View File
@@ -0,0 +1,142 @@
# CockroachDB tutorial for beginners (insecure cluster)
## Create/configure database
First, let's start a local cluster - follow step 1. and 2. from [the docs](https://www.cockroachlabs.com/docs/stable/start-a-local-cluster.html#step-1-start-the-first-node).
Once you have it, create a database. Here I am going to create a database called `example`.
Our user here is `cockroach`. We are not going to use a password, since it's not supported for insecure cluster.
```
cockroach sql --insecure --host=localhost:26257
```
```
CREATE DATABASE example;
CREATE USER IF NOT EXISTS cockroach;
GRANT ALL ON DATABASE example TO cockroach;
```
When using Migrate CLI we need to pass to database URL. Let's export it to a variable for convienience:
```
export COCKROACHDB_URL='cockroachdb://cockroach:@localhost:26257/example?sslmode=disable'
```
`sslmode=disable` means that the connection with our database will not be encrypted. This is needed to connect to an insecure node.
**NOTE:** Do not use COCKROACH_URL as a variable name here, it's already in use for discrete parameters and you may run into connection problems. For more info check out [docs](https://www.cockroachlabs.com/docs/stable/connection-parameters.html#connect-using-discrete-parameters).
You can find further description of database URLs [here](README.md#database-urls).
## Create migrations
Let's create a table called `users`:
```
migrate create -ext sql -dir db/migrations -seq create_users_table
```
If there were no errors, we should have two files available under `db/migrations` folder:
- 000001_create_users_table.down.sql
- 000001_create_users_table.up.sql
Note the `sql` extension that we provided.
In the `.up.sql` file let's create the table:
```
CREATE TABLE IF NOT EXISTS example.users
(
user_id INT PRIMARY KEY,
username VARCHAR (50) UNIQUE NOT NULL,
password VARCHAR (50) NOT NULL,
email VARCHAR (300) UNIQUE NOT NULL
);
```
And in the `.down.sql` let's delete it:
```
DROP TABLE IF EXISTS example.users;
```
By adding `IF EXISTS/IF NOT EXISTS` we are making migrations idempotent - you can read more about idempotency in [getting started](GETTING_STARTED.md#create-migrations)
## Run migrations
```
migrate -database ${COCKROACHDB_URL} -path db/migrations up
```
Let's check if the table was created properly by running `cockroach sql --insecure --host=localhost:26257 -e "show columns from example.users;"`.
The output you are supposed to see:
```
column_name | data_type | is_nullable | column_default | generation_expression | indices | is_hidden
+-------------+--------------+-------------+----------------+-----------------------+----------------------------------------------+-----------+
user_id | INT8 | false | NULL | | {primary,users_username_key,users_email_key} | false
username | VARCHAR(50) | false | NULL | | {users_username_key} | false
password | VARCHAR(50) | false | NULL | | {} | false
email | VARCHAR(300) | false | NULL | | {users_email_key} | false
(4 rows)
```
Now let's check if running reverse migration also works:
```
migrate -database ${COCKROACHDB_URL} -path db/migrations down
```
Make sure to check if your database changed as expected in this case as well.
## Database transactions
To show database transactions usage, let's create another set of migrations by running:
```
migrate create -ext sql -dir db/migrations -seq add_mood_to_users
```
Again, it should create for us two migrations files:
- 000002_add_mood_to_users.down.sql
- 000002_add_mood_to_users.up.sql
In Cockroach, when we want our queries to be done in a transaction, we need to wrap it with `BEGIN` and `COMMIT` commands, similar to PostgreSQL.
In our example, we are going to add a column to our database that can only accept enumerable values or NULL.
Migration up:
```
BEGIN;
ALTER TABLE example.users ADD COLUMN mood STRING;
ALTER TABLE example.users ADD CONSTRAINT check_mood CHECK (mood IN ('happy', 'sad', 'neutral'));
COMMIT;
```
Migration down:
```
ALTER TABLE example.users DROP COLUMN mood;
```
Now we can run our new migration and check the database:
```
migrate -database ${COCKROACHDB_URL} -path db/migrations up
cockroach sql --insecure --host=localhost:26257 -e "show columns from example.users;"
```
Expected output:
```
column_name | data_type | is_nullable | column_default | generation_expression | indices | is_hidden
+-------------+--------------+-------------+----------------+-----------------------+----------------------------------------------+-----------+
user_id | INT8 | false | NULL | | {primary,users_username_key,users_email_key} | false
username | VARCHAR(50) | false | NULL | | {users_username_key} | false
password | VARCHAR(50) | false | NULL | | {} | false
email | VARCHAR(300) | false | NULL | | {users_email_key} | false
mood | STRING | true | NULL | | {} | false
(5 rows)
```
## Optional: Run migrations within your Go app
Here is a very simple app running migrations for the above configuration:
```
import (
"log"
"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/cockroachdb"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
func main() {
m, err := migrate.New(
"file://db/migrations",
"cockroachdb://cockroach:@localhost:26257/example?sslmode=disable")
if err != nil {
log.Fatal(err)
}
if err := m.Up(); err != nil {
log.Fatal(err)
}
}
```
You can find details [here](README.md#use-in-your-go-project)
+374
View File
@@ -0,0 +1,374 @@
package cockroachdb
import (
"context"
"database/sql"
"fmt"
"io"
"io/ioutil"
nurl "net/url"
"regexp"
"strconv"
)
import (
"github.com/cockroachdb/cockroach-go/crdb"
"github.com/hashicorp/go-multierror"
"github.com/lib/pq"
)
import (
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database"
)
func init() {
db := CockroachDb{}
database.Register("cockroach", &db)
database.Register("cockroachdb", &db)
database.Register("crdb-postgres", &db)
}
var DefaultMigrationsTable = "schema_migrations"
var DefaultLockTable = "schema_lock"
var (
ErrNilConfig = fmt.Errorf("no config")
ErrNoDatabaseName = fmt.Errorf("no database name")
)
type Config struct {
MigrationsTable string
LockTable string
ForceLock bool
DatabaseName string
}
type CockroachDb struct {
db *sql.DB
isLocked bool
// Open and WithInstance need to guarantee that config is never nil
config *Config
}
func WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {
if config == nil {
return nil, ErrNilConfig
}
if err := instance.Ping(); err != nil {
return nil, err
}
if config.DatabaseName == "" {
query := `SELECT current_database()`
var databaseName string
if err := instance.QueryRow(query).Scan(&databaseName); err != nil {
return nil, &database.Error{OrigErr: err, Query: []byte(query)}
}
if len(databaseName) == 0 {
return nil, ErrNoDatabaseName
}
config.DatabaseName = databaseName
}
if len(config.MigrationsTable) == 0 {
config.MigrationsTable = DefaultMigrationsTable
}
if len(config.LockTable) == 0 {
config.LockTable = DefaultLockTable
}
px := &CockroachDb{
db: instance,
config: config,
}
// ensureVersionTable is a locking operation, so we need to ensureLockTable before we ensureVersionTable.
if err := px.ensureLockTable(); err != nil {
return nil, err
}
if err := px.ensureVersionTable(); err != nil {
return nil, err
}
return px, nil
}
func (c *CockroachDb) Open(url string) (database.Driver, error) {
purl, err := nurl.Parse(url)
if err != nil {
return nil, err
}
// As Cockroach uses the postgres protocol, and 'postgres' is already a registered database, we need to replace the
// connect prefix, with the actual protocol, so that the library can differentiate between the implementations
re := regexp.MustCompile("^(cockroach(db)?|crdb-postgres)")
connectString := re.ReplaceAllString(migrate.FilterCustomQuery(purl).String(), "postgres")
db, err := sql.Open("postgres", connectString)
if err != nil {
return nil, err
}
migrationsTable := purl.Query().Get("x-migrations-table")
if len(migrationsTable) == 0 {
migrationsTable = DefaultMigrationsTable
}
lockTable := purl.Query().Get("x-lock-table")
if len(lockTable) == 0 {
lockTable = DefaultLockTable
}
forceLockQuery := purl.Query().Get("x-force-lock")
forceLock, err := strconv.ParseBool(forceLockQuery)
if err != nil {
forceLock = false
}
px, err := WithInstance(db, &Config{
DatabaseName: purl.Path,
MigrationsTable: migrationsTable,
LockTable: lockTable,
ForceLock: forceLock,
})
if err != nil {
return nil, err
}
return px, nil
}
func (c *CockroachDb) Close() error {
return c.db.Close()
}
// Locking is done manually with a separate lock table. Implementing advisory locks in CRDB is being discussed
// See: https://github.com/cockroachdb/cockroach/issues/13546
func (c *CockroachDb) Lock() error {
err := crdb.ExecuteTx(context.Background(), c.db, nil, func(tx *sql.Tx) (err error) {
aid, err := database.GenerateAdvisoryLockId(c.config.DatabaseName)
if err != nil {
return err
}
query := "SELECT * FROM " + c.config.LockTable + " WHERE lock_id = $1"
rows, err := tx.Query(query, aid)
if err != nil {
return database.Error{OrigErr: err, Err: "failed to fetch migration lock", Query: []byte(query)}
}
defer func() {
if errClose := rows.Close(); errClose != nil {
err = multierror.Append(err, errClose)
}
}()
// If row exists at all, lock is present
locked := rows.Next()
if locked && !c.config.ForceLock {
return database.ErrLocked
}
query = "INSERT INTO " + c.config.LockTable + " (lock_id) VALUES ($1)"
if _, err := tx.Exec(query, aid); err != nil {
return database.Error{OrigErr: err, Err: "failed to set migration lock", Query: []byte(query)}
}
return nil
})
if err != nil {
return err
} else {
c.isLocked = true
return nil
}
}
// Locking is done manually with a separate lock table. Implementing advisory locks in CRDB is being discussed
// See: https://github.com/cockroachdb/cockroach/issues/13546
func (c *CockroachDb) Unlock() error {
aid, err := database.GenerateAdvisoryLockId(c.config.DatabaseName)
if err != nil {
return err
}
// In the event of an implementation (non-migration) error, it is possible for the lock to not be released. Until
// a better locking mechanism is added, a manual purging of the lock table may be required in such circumstances
query := "DELETE FROM " + c.config.LockTable + " WHERE lock_id = $1"
if _, err := c.db.Exec(query, aid); err != nil {
if e, ok := err.(*pq.Error); ok {
// 42P01 is "UndefinedTableError" in CockroachDB
// https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/pgwire/pgerror/codes.go
if e.Code == "42P01" {
// On drops, the lock table is fully removed; This is fine, and is a valid "unlocked" state for the schema
c.isLocked = false
return nil
}
}
return database.Error{OrigErr: err, Err: "failed to release migration lock", Query: []byte(query)}
}
c.isLocked = false
return nil
}
func (c *CockroachDb) Run(migration io.Reader) error {
migr, err := ioutil.ReadAll(migration)
if err != nil {
return err
}
// run migration
query := string(migr[:])
if _, err := c.db.Exec(query); err != nil {
return database.Error{OrigErr: err, Err: "migration failed", Query: migr}
}
return nil
}
func (c *CockroachDb) SetVersion(version int, dirty bool) error {
return crdb.ExecuteTx(context.Background(), c.db, nil, func(tx *sql.Tx) error {
if _, err := tx.Exec(`DELETE FROM "` + c.config.MigrationsTable + `"`); err != nil {
return err
}
// Also re-write the schema version for nil dirty versions to prevent
// empty schema version for failed down migration on the first migration
// See: https://github.com/golang-migrate/migrate/issues/330
if version >= 0 || (version == database.NilVersion && dirty) {
if _, err := tx.Exec(`INSERT INTO "`+c.config.MigrationsTable+`" (version, dirty) VALUES ($1, $2)`, version, dirty); err != nil {
return err
}
}
return nil
})
}
func (c *CockroachDb) Version() (version int, dirty bool, err error) {
query := `SELECT version, dirty FROM "` + c.config.MigrationsTable + `" LIMIT 1`
err = c.db.QueryRow(query).Scan(&version, &dirty)
switch {
case err == sql.ErrNoRows:
return database.NilVersion, false, nil
case err != nil:
if e, ok := err.(*pq.Error); ok {
// 42P01 is "UndefinedTableError" in CockroachDB
// https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/pgwire/pgerror/codes.go
if e.Code == "42P01" {
return database.NilVersion, false, nil
}
}
return 0, false, &database.Error{OrigErr: err, Query: []byte(query)}
default:
return version, dirty, nil
}
}
func (c *CockroachDb) Drop() (err error) {
// select all tables in current schema
query := `SELECT table_name FROM information_schema.tables WHERE table_schema=(SELECT current_schema())`
tables, err := c.db.Query(query)
if err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
defer func() {
if errClose := tables.Close(); errClose != nil {
err = multierror.Append(err, errClose)
}
}()
// delete one table after another
tableNames := make([]string, 0)
for tables.Next() {
var tableName string
if err := tables.Scan(&tableName); err != nil {
return err
}
if len(tableName) > 0 {
tableNames = append(tableNames, tableName)
}
}
if err := tables.Err(); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
if len(tableNames) > 0 {
// delete one by one ...
for _, t := range tableNames {
query = `DROP TABLE IF EXISTS ` + t + ` CASCADE`
if _, err := c.db.Exec(query); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
}
}
return nil
}
// ensureVersionTable checks if versions table exists and, if not, creates it.
// Note that this function locks the database, which deviates from the usual
// convention of "caller locks" in the CockroachDb type.
func (c *CockroachDb) ensureVersionTable() (err error) {
if err = c.Lock(); err != nil {
return err
}
defer func() {
if e := c.Unlock(); e != nil {
if err == nil {
err = e
} else {
err = multierror.Append(err, e)
}
}
}()
// check if migration table exists
var count int
query := `SELECT COUNT(1) FROM information_schema.tables WHERE table_name = $1 AND table_schema = (SELECT current_schema()) LIMIT 1`
if err := c.db.QueryRow(query, c.config.MigrationsTable).Scan(&count); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
if count == 1 {
return nil
}
// if not, create the empty migration table
query = `CREATE TABLE "` + c.config.MigrationsTable + `" (version INT NOT NULL PRIMARY KEY, dirty BOOL NOT NULL)`
if _, err := c.db.Exec(query); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
return nil
}
func (c *CockroachDb) ensureLockTable() error {
// check if lock table exists
var count int
query := `SELECT COUNT(1) FROM information_schema.tables WHERE table_name = $1 AND table_schema = (SELECT current_schema()) LIMIT 1`
if err := c.db.QueryRow(query, c.config.LockTable).Scan(&count); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
if count == 1 {
return nil
}
// if not, create the empty lock table
query = `CREATE TABLE "` + c.config.LockTable + `" (lock_id INT NOT NULL PRIMARY KEY)`
if _, err := c.db.Exec(query); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
return nil
}
+174
View File
@@ -0,0 +1,174 @@
package cockroachdb
// error codes https://github.com/lib/pq/blob/master/error.go
import (
"context"
"database/sql"
"fmt"
"github.com/golang-migrate/migrate/v4"
"log"
"strings"
"testing"
)
import (
"github.com/dhui/dktest"
_ "github.com/lib/pq"
)
import (
dt "github.com/golang-migrate/migrate/v4/database/testing"
"github.com/golang-migrate/migrate/v4/dktesting"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
const defaultPort = 26257
var (
opts = dktest.Options{Cmd: []string{"start", "--insecure"}, PortRequired: true, ReadyFunc: isReady}
// Released versions: https://www.cockroachlabs.com/docs/releases/
specs = []dktesting.ContainerSpec{
{ImageName: "cockroachdb/cockroach:v1.0.7", Options: opts},
{ImageName: "cockroachdb/cockroach:v1.1.9", Options: opts},
{ImageName: "cockroachdb/cockroach:v2.0.7", Options: opts},
{ImageName: "cockroachdb/cockroach:v2.1.3", Options: opts},
}
)
func isReady(ctx context.Context, c dktest.ContainerInfo) bool {
ip, port, err := c.Port(defaultPort)
if err != nil {
log.Println("port error:", err)
return false
}
db, err := sql.Open("postgres", fmt.Sprintf("postgres://root@%v:%v?sslmode=disable", ip, port))
if err != nil {
log.Println("open error:", err)
return false
}
if err := db.PingContext(ctx); err != nil {
log.Println("ping error:", err)
return false
}
if err := db.Close(); err != nil {
log.Println("close error:", err)
}
return true
}
func createDB(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.Port(defaultPort)
if err != nil {
t.Fatal(err)
}
db, err := sql.Open("postgres", fmt.Sprintf("postgres://root@%v:%v?sslmode=disable", ip, port))
if err != nil {
t.Fatal(err)
}
if err = db.Ping(); err != nil {
t.Fatal(err)
}
defer func() {
if err := db.Close(); err != nil {
t.Error(err)
}
}()
if _, err = db.Exec("CREATE DATABASE migrate"); err != nil {
t.Fatal(err)
}
}
func Test(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, ci dktest.ContainerInfo) {
createDB(t, ci)
ip, port, err := ci.Port(26257)
if err != nil {
t.Fatal(err)
}
addr := fmt.Sprintf("cockroach://root@%v:%v/migrate?sslmode=disable", ip, port)
c := &CockroachDb{}
d, err := c.Open(addr)
if err != nil {
t.Fatal(err)
}
dt.Test(t, d, []byte("SELECT 1"))
})
}
func TestMigrate(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, ci dktest.ContainerInfo) {
createDB(t, ci)
ip, port, err := ci.Port(26257)
if err != nil {
t.Fatal(err)
}
addr := fmt.Sprintf("cockroach://root@%v:%v/migrate?sslmode=disable", ip, port)
c := &CockroachDb{}
d, err := c.Open(addr)
if err != nil {
t.Fatal(err)
}
m, err := migrate.NewWithDatabaseInstance("file://./examples/migrations", "migrate", d)
if err != nil {
t.Fatal(err)
}
dt.TestMigrate(t, m)
})
}
func TestMultiStatement(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, ci dktest.ContainerInfo) {
createDB(t, ci)
ip, port, err := ci.Port(26257)
if err != nil {
t.Fatal(err)
}
addr := fmt.Sprintf("cockroach://root@%v:%v/migrate?sslmode=disable", ip, port)
c := &CockroachDb{}
d, err := c.Open(addr)
if err != nil {
t.Fatal(err)
}
if err := d.Run(strings.NewReader("CREATE TABLE foo (foo text); CREATE TABLE bar (bar text);")); err != nil {
t.Fatalf("expected err to be nil, got %v", err)
}
// make sure second table exists
var exists bool
if err := d.(*CockroachDb).db.QueryRow("SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'bar' AND table_schema = (SELECT current_schema()))").Scan(&exists); err != nil {
t.Fatal(err)
}
if !exists {
t.Fatalf("expected table bar to exist")
}
})
}
func TestFilterCustomQuery(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, ci dktest.ContainerInfo) {
createDB(t, ci)
ip, port, err := ci.Port(26257)
if err != nil {
t.Fatal(err)
}
addr := fmt.Sprintf("cockroach://root@%v:%v/migrate?sslmode=disable&x-custom=foobar", ip, port)
c := &CockroachDb{}
_, err = c.Open(addr)
if err != nil {
t.Fatal(err)
}
})
}
@@ -0,0 +1 @@
DROP TABLE IF EXISTS users;
@@ -0,0 +1,5 @@
CREATE TABLE users (
user_id INT UNIQUE,
name STRING(40),
email STRING(40)
);
@@ -0,0 +1 @@
ALTER TABLE users DROP COLUMN IF EXISTS city;
@@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN city TEXT;
@@ -0,0 +1 @@
DROP INDEX IF EXISTS users_email_index;
@@ -0,0 +1,3 @@
CREATE UNIQUE INDEX IF NOT EXISTS users_email_index ON users (email);
-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.
@@ -0,0 +1 @@
DROP TABLE IF EXISTS books;
@@ -0,0 +1,5 @@
CREATE TABLE books (
user_id INT,
name STRING(40),
author STRING(40)
);
@@ -0,0 +1 @@
DROP TABLE IF EXISTS movies;
@@ -0,0 +1,5 @@
CREATE TABLE movies (
user_id INT,
name STRING(40),
director STRING(40)
);
@@ -0,0 +1 @@
-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.
@@ -0,0 +1 @@
-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.
@@ -0,0 +1 @@
-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.
@@ -0,0 +1 @@
-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.
+24 -11
View File
@@ -7,12 +7,14 @@ package database
import (
"fmt"
"io"
nurl "net/url"
"sync"
iurl "github.com/golang-migrate/migrate/v4/internal/url"
)
var (
ErrLocked = fmt.Errorf("can't acquire lock")
ErrLocked = fmt.Errorf("can't acquire lock")
ErrNotLocked = fmt.Errorf("can't unlock, as not currently locked")
)
const NilVersion int = -1
@@ -32,6 +34,8 @@ var drivers = make(map[string]Driver)
// All other functions are tested by tests in database/testing.
// Saves you some time and makes sure all database drivers behave the same way.
// 5. Call Register in init().
// 6. Create a internal/cli/build_<driver-name>.go file
// 7. Add driver name in 'DATABASE' variable in Makefile
//
// Guidelines:
// * Don't try to correct user input. Don't assume things.
@@ -58,7 +62,7 @@ type Driver interface {
// all migrations have been run.
Unlock() error
// Run applies a migration to the database. migration is garantueed to be not nil.
// Run applies a migration to the database. migration is guaranteed to be not nil.
Run(migration io.Reader) error
// SetVersion saves version and dirty state.
@@ -71,26 +75,24 @@ type Driver interface {
// Dirty means, a previous migration failed and user interaction is required.
Version() (version int, dirty bool, err error)
// Drop deletes everyting in the database.
// Drop deletes everything in the database.
// Note that this is a breaking action, a new call to Open() is necessary to
// ensure subsequent calls work as expected.
Drop() error
}
// Open returns a new driver instance.
func Open(url string) (Driver, error) {
u, err := nurl.Parse(url)
scheme, err := iurl.SchemeFromURL(url)
if err != nil {
return nil, err
}
if u.Scheme == "" {
return nil, fmt.Errorf("database driver: invalid URL scheme")
}
driversMu.RLock()
d, ok := drivers[u.Scheme]
d, ok := drivers[scheme]
driversMu.RUnlock()
if !ok {
return nil, fmt.Errorf("database driver: unknown driver %v (forgotton import?)", u.Scheme)
return nil, fmt.Errorf("database driver: unknown driver %v (forgotten import?)", scheme)
}
return d.Open(url)
@@ -108,3 +110,14 @@ func Register(name string, driver Driver) {
}
drivers[name] = driver
}
// List lists the registered drivers
func List() []string {
driversMu.RLock()
defer driversMu.RUnlock()
names := make([]string, 0, len(drivers))
for n := range drivers {
names = append(names, n)
}
return names
}
+107
View File
@@ -1,8 +1,115 @@
package database
import (
"io"
"testing"
)
func ExampleDriver() {
// see database/stub for an example
// database/stub/stub.go has the driver implementation
// database/stub/stub_test.go runs database/testing/test.go:Test
}
// Using database/stub here is not possible as it
// results in an import cycle.
type mockDriver struct {
url string
}
func (m *mockDriver) Open(url string) (Driver, error) {
return &mockDriver{
url: url,
}, nil
}
func (m *mockDriver) Close() error {
return nil
}
func (m *mockDriver) Lock() error {
return nil
}
func (m *mockDriver) Unlock() error {
return nil
}
func (m *mockDriver) Run(migration io.Reader) error {
return nil
}
func (m *mockDriver) SetVersion(version int, dirty bool) error {
return nil
}
func (m *mockDriver) Version() (version int, dirty bool, err error) {
return 0, false, nil
}
func (m *mockDriver) Drop() error {
return nil
}
func TestRegisterTwice(t *testing.T) {
Register("mock", &mockDriver{})
var err interface{}
func() {
defer func() {
err = recover()
}()
Register("mock", &mockDriver{})
}()
if err == nil {
t.Fatal("expected a panic when calling Register twice")
}
}
func TestOpen(t *testing.T) {
// Make sure the driver is registered.
// But if the previous test already registered it just ignore the panic.
// If we don't do this it will be impossible to run this test standalone.
func() {
defer func() {
_ = recover()
}()
Register("mock", &mockDriver{})
}()
cases := []struct {
url string
err bool
}{
{
"mock://user:pass@tcp(host:1337)/db",
false,
},
{
"unknown://bla",
true,
},
}
for _, c := range cases {
t.Run(c.url, func(t *testing.T) {
d, err := Open(c.url)
if err == nil {
if c.err {
t.Fatal("expected an error for an unknown driver")
} else {
if md, ok := d.(*mockDriver); !ok {
t.Fatalf("expected *mockDriver got %T", d)
} else if md.url != c.url {
t.Fatalf("expected %q got %q", c.url, md.url)
}
}
} else if !c.err {
t.Fatalf("did not expect %q", err)
}
})
}
}
+12
View File
@@ -0,0 +1,12 @@
# firebird
`firebirdsql://user:password@servername[:port_number]/database_name_or_file[?params1=value1[&param2=value2]...]`
| URL Query | WithInstance Config | Description |
|------------|---------------------|-------------|
| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |
| `auth_plugin_name` | | Authentication plugin name. Srp256/Srp/Legacy_Auth are available. (default is Srp) |
| `column_name_to_lower` | | Force column name to lower. (default is false) |
| `role` | | Role name |
| `tzname` | | Time Zone name. (For Firebird 4.0+) |
| `wire_crypt` | | Enable wire data encryption or not. For Firebird 3.0+ (default is true) |
@@ -0,0 +1 @@
DROP TABLE users;
@@ -0,0 +1,5 @@
CREATE TABLE users (
user_id integer unique,
name varchar(40),
email varchar(40)
);
@@ -0,0 +1 @@
ALTER TABLE users DROP city;
@@ -0,0 +1,3 @@
ALTER TABLE users ADD city varchar(100);
@@ -0,0 +1 @@
DROP INDEX users_email_index;
@@ -0,0 +1,3 @@
CREATE UNIQUE INDEX users_email_index ON users (email);
-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.
@@ -0,0 +1 @@
DROP TABLE books;
@@ -0,0 +1,5 @@
CREATE TABLE books (
user_id integer,
name varchar(40),
author varchar(40)
);
@@ -0,0 +1 @@
DROP TABLE movies;
@@ -0,0 +1,5 @@
CREATE TABLE movies (
user_id integer,
name varchar(40),
director varchar(40)
);
+256
View File
@@ -0,0 +1,256 @@
// +build go1.9
package firebird
import (
"context"
"database/sql"
"fmt"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database"
"github.com/hashicorp/go-multierror"
_ "github.com/nakagami/firebirdsql"
"io"
"io/ioutil"
nurl "net/url"
)
func init() {
db := Firebird{}
database.Register("firebird", &db)
database.Register("firebirdsql", &db)
}
var DefaultMigrationsTable = "schema_migrations"
var (
ErrNilConfig = fmt.Errorf("no config")
)
type Config struct {
DatabaseName string
MigrationsTable string
}
type Firebird struct {
// Locking and unlocking need to use the same connection
conn *sql.Conn
db *sql.DB
isLocked bool
// Open and WithInstance need to guarantee that config is never nil
config *Config
}
func WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {
if config == nil {
return nil, ErrNilConfig
}
if err := instance.Ping(); err != nil {
return nil, err
}
if len(config.MigrationsTable) == 0 {
config.MigrationsTable = DefaultMigrationsTable
}
conn, err := instance.Conn(context.Background())
if err != nil {
return nil, err
}
fb := &Firebird{
conn: conn,
db: instance,
config: config,
}
if err := fb.ensureVersionTable(); err != nil {
return nil, err
}
return fb, nil
}
func (f *Firebird) Open(dsn string) (database.Driver, error) {
purl, err := nurl.Parse(dsn)
if err != nil {
return nil, err
}
db, err := sql.Open("firebirdsql", migrate.FilterCustomQuery(purl).String())
if err != nil {
return nil, err
}
px, err := WithInstance(db, &Config{
MigrationsTable: purl.Query().Get("x-migrations-table"),
DatabaseName: purl.Path,
})
if err != nil {
return nil, err
}
return px, nil
}
func (f *Firebird) Close() error {
connErr := f.conn.Close()
dbErr := f.db.Close()
if connErr != nil || dbErr != nil {
return fmt.Errorf("conn: %v, db: %v", connErr, dbErr)
}
return nil
}
func (f *Firebird) Lock() error {
if f.isLocked {
return database.ErrLocked
}
f.isLocked = true
return nil
}
func (f *Firebird) Unlock() error {
f.isLocked = false
return nil
}
func (f *Firebird) Run(migration io.Reader) error {
migr, err := ioutil.ReadAll(migration)
if err != nil {
return err
}
// run migration
query := string(migr[:])
if _, err := f.conn.ExecContext(context.Background(), query); err != nil {
return database.Error{OrigErr: err, Err: "migration failed", Query: migr}
}
return nil
}
func (f *Firebird) SetVersion(version int, dirty bool) error {
// Always re-write the schema version to prevent empty schema version
// for failed down migration on the first migration
// See: https://github.com/golang-migrate/migrate/issues/330
// TODO: parameterize this SQL statement
// https://firebirdsql.org/refdocs/langrefupd20-execblock.html
// VALUES (?, ?) doesn't work
query := fmt.Sprintf(`EXECUTE BLOCK AS BEGIN
DELETE FROM "%v";
INSERT INTO "%v" (version, dirty) VALUES (%v, %v);
END;`,
f.config.MigrationsTable, f.config.MigrationsTable, version, btoi(dirty))
if _, err := f.conn.ExecContext(context.Background(), query); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
return nil
}
func (f *Firebird) Version() (version int, dirty bool, err error) {
var d int
query := fmt.Sprintf(`SELECT FIRST 1 version, dirty FROM "%v"`, f.config.MigrationsTable)
err = f.conn.QueryRowContext(context.Background(), query).Scan(&version, &d)
switch {
case err == sql.ErrNoRows:
return database.NilVersion, false, nil
case err != nil:
return 0, false, &database.Error{OrigErr: err, Query: []byte(query)}
default:
return version, itob(d), nil
}
}
func (f *Firebird) Drop() (err error) {
// select all tables
query := `SELECT rdb$relation_name FROM rdb$relations WHERE rdb$view_blr IS NULL AND (rdb$system_flag IS NULL OR rdb$system_flag = 0);`
tables, err := f.conn.QueryContext(context.Background(), query)
if err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
defer func() {
if errClose := tables.Close(); errClose != nil {
err = multierror.Append(err, errClose)
}
}()
// delete one table after another
tableNames := make([]string, 0)
for tables.Next() {
var tableName string
if err := tables.Scan(&tableName); err != nil {
return err
}
if len(tableName) > 0 {
tableNames = append(tableNames, tableName)
}
}
if err := tables.Err(); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
// delete one by one ...
for _, t := range tableNames {
query := fmt.Sprintf(`EXECUTE BLOCK AS BEGIN
if (not exists(select 1 from rdb$relations where rdb$relation_name = '%v')) then
execute statement 'drop table "%v"';
END;`,
t, t)
if _, err := f.conn.ExecContext(context.Background(), query); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
}
return nil
}
// ensureVersionTable checks if versions table exists and, if not, creates it.
func (f *Firebird) ensureVersionTable() (err error) {
if err = f.Lock(); err != nil {
return err
}
defer func() {
if e := f.Unlock(); e != nil {
if err == nil {
err = e
} else {
err = multierror.Append(err, e)
}
}
}()
query := fmt.Sprintf(`EXECUTE BLOCK AS BEGIN
if (not exists(select 1 from rdb$relations where rdb$relation_name = '%v')) then
execute statement 'create table "%v" (version bigint not null primary key, dirty smallint not null)';
END;`,
f.config.MigrationsTable, f.config.MigrationsTable)
if _, err = f.conn.ExecContext(context.Background(), query); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
return nil
}
// btoi converts bool to int
func btoi(v bool) int {
if v {
return 1
}
return 0
}
// itob converts int to bool
func itob(v int) bool {
return v != 0
}
+226
View File
@@ -0,0 +1,226 @@
package firebird
import (
"context"
"database/sql"
sqldriver "database/sql/driver"
"fmt"
"log"
"github.com/golang-migrate/migrate/v4"
"io"
"strings"
"testing"
"github.com/dhui/dktest"
dt "github.com/golang-migrate/migrate/v4/database/testing"
"github.com/golang-migrate/migrate/v4/dktesting"
_ "github.com/golang-migrate/migrate/v4/source/file"
_ "github.com/nakagami/firebirdsql"
)
const (
user = "test_user"
password = "123456"
dbName = "test.fdb"
)
var (
opts = dktest.Options{
PortRequired: true,
ReadyFunc: isReady,
Env: map[string]string{
"FIREBIRD_DATABASE": dbName,
"FIREBIRD_USER": user,
"FIREBIRD_PASSWORD": password,
},
}
specs = []dktesting.ContainerSpec{
{ImageName: "jacobalberty/firebird:2.5-ss", Options: opts},
{ImageName: "jacobalberty/firebird:3.0", Options: opts},
}
)
func fbConnectionString(host, port string) string {
//firebird://user:password@servername[:port_number]/database_name_or_file[?params1=value1[&param2=value2]...]
return fmt.Sprintf("firebird://%s:%s@%s:%s//firebird/data/%s", user, password, host, port, dbName)
}
func isReady(ctx context.Context, c dktest.ContainerInfo) bool {
ip, port, err := c.FirstPort()
if err != nil {
return false
}
db, err := sql.Open("firebirdsql", fbConnectionString(ip, port))
if err != nil {
log.Println("open error:", err)
return false
}
defer func() {
if err := db.Close(); err != nil {
log.Println("close error:", err)
}
}()
if err = db.PingContext(ctx); err != nil {
switch err {
case sqldriver.ErrBadConn, io.EOF:
return false
default:
log.Println(err)
}
return false
}
return true
}
func Test(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.FirstPort()
if err != nil {
t.Fatal(err)
}
addr := fbConnectionString(ip, port)
p := &Firebird{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
dt.Test(t, d, []byte("SELECT Count(*) FROM rdb$relations"))
})
}
func TestMigrate(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.FirstPort()
if err != nil {
t.Fatal(err)
}
addr := fbConnectionString(ip, port)
p := &Firebird{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
m, err := migrate.NewWithDatabaseInstance("file://./examples/migrations", "firebirdsql", d)
if err != nil {
t.Fatal(err)
}
dt.TestMigrate(t, m)
})
}
func TestErrorParsing(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.FirstPort()
if err != nil {
t.Fatal(err)
}
addr := fbConnectionString(ip, port)
p := &Firebird{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
wantErr := `migration failed in line 0: CREATE TABLEE foo (foo varchar(40)); (details: Dynamic SQL Error
SQL error code = -104
Token unknown - line 1, column 8
TABLEE
)`
if err := d.Run(strings.NewReader("CREATE TABLEE foo (foo varchar(40));")); err == nil {
t.Fatal("expected err but got nil")
} else if err.Error() != wantErr {
msg := err.Error()
t.Fatalf("expected '%s' but got '%s'", wantErr, msg)
}
})
}
func TestFilterCustomQuery(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.FirstPort()
if err != nil {
t.Fatal(err)
}
addr := fbConnectionString(ip, port) + "?sslmode=disable&x-custom=foobar"
p := &Firebird{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
})
}
func Test_Lock(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.FirstPort()
if err != nil {
t.Fatal(err)
}
addr := fbConnectionString(ip, port)
p := &Firebird{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
dt.Test(t, d, []byte("SELECT Count(*) FROM rdb$relations"))
ps := d.(*Firebird)
err = ps.Lock()
if err != nil {
t.Fatal(err)
}
err = ps.Unlock()
if err != nil {
t.Fatal(err)
}
err = ps.Lock()
if err != nil {
t.Fatal(err)
}
err = ps.Unlock()
if err != nil {
t.Fatal(err)
}
})
}
+24
View File
@@ -0,0 +1,24 @@
# MongoDB
* Driver work with mongo through [db.runCommands](https://docs.mongodb.com/manual/reference/command/)
* Migrations support json format. It contains array of commands for `db.runCommand`. Every command is executed in separate request to database
* All keys have to be in quotes `"`
* [Examples](./examples)
# Usage
`mongodb://user:password@host:port/dbname?query` (`mongodb+srv://` also works, but behaves a bit differently. See [docs](https://docs.mongodb.com/manual/reference/connection-string/#dns-seedlist-connection-format) for more information)
| URL Query | WithInstance Config | Description |
|------------|---------------------|-------------|
| `x-migrations-collection` | `MigrationsCollection` | Name of the migrations collection |
| `x-transaction-mode` | `TransactionMode` | If set to `true` wrap commands in [transaction](https://docs.mongodb.com/manual/core/transactions). Available only for replica set. Driver is using [strconv.ParseBool](https://golang.org/pkg/strconv/#ParseBool) for parsing|
| `x-advisory-locking` | `true` | Feature flag for advisory locking, if set to false, disable advisory locking |
| `x-advisory-lock-collection` | `migrate_advisory_lock` | The name of the collection to use for advisory locking.|
| `x-advisory-lock-timout` | `15` | The max time in seconds that the advisory lock will wait if the db is already locked. |
| `x-advisory-lock-timout-interval` | `10` | The max timeout in seconds interval that the advisory lock will wait if the db is already locked. |
| `dbname` | `DatabaseName` | The name of the database to connect to |
| `user` | | The user to sign in as. Can be omitted |
| `password` | | The user's password. Can be omitted |
| `host` | | The host to connect to |
| `port` | | The port to bind to |
@@ -0,0 +1,5 @@
[
{
"dropUser": "deminem"
}
]
@@ -0,0 +1,12 @@
[
{
"createUser": "deminem",
"pwd": "gogo",
"roles": [
{
"role": "readWrite",
"db": "testMigration"
}
]
}
]
@@ -0,0 +1,10 @@
[
{
"dropIndexes": "mycollection",
"index": "username_sort_by_asc_created"
},
{
"dropIndexes": "mycollection",
"index": "unique_email"
}
]
@@ -0,0 +1,21 @@
[{
"createIndexes": "mycollection",
"indexes": [
{
"key": {
"username": 1,
"created": -1
},
"name": "username_sort_by_asc_created",
"background": true
},
{
"key": {
"email": 1
},
"name": "unique_email",
"unique": true,
"background": true
}
]
}]
@@ -0,0 +1,16 @@
[
{
"update": "users",
"updates": [
{
"q": {},
"u": {
"$unset": {
"status": ""
}
},
"multi": true
}
]
}
]
@@ -0,0 +1,16 @@
[
{
"update": "users",
"updates": [
{
"q": {},
"u": {
"$set": {
"status": "active"
}
},
"multi": true
}
]
}
]
@@ -0,0 +1,14 @@
[
{
"update": "users",
"updates": [
{
"q": {},
"u": {
"fullname": ""
},
"multi": true
}
]
}
]
@@ -0,0 +1,23 @@
[
{
"aggregate": "users",
"pipeline": [
{
"$project": {
"_id": 1,
"firstname": 1,
"lastname": 1,
"username": 1,
"password": 1,
"email": 1,
"active": 1,
"fullname": { "$concat": ["$firstname", " ", "$lastname"] }
}
},
{
"$out": "users"
}
],
"cursor": {}
}
]
+381
View File
@@ -0,0 +1,381 @@
package mongodb
import (
"context"
"fmt"
"github.com/cenkalti/backoff/v4"
"github.com/golang-migrate/migrate/v4/database"
"github.com/hashicorp/go-multierror"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/x/mongo/driver/connstring"
"io"
"io/ioutil"
"net/url"
os "os"
"strconv"
"time"
)
func init() {
db := Mongo{}
database.Register("mongodb", &db)
database.Register("mongodb+srv", &db)
}
var DefaultMigrationsCollection = "schema_migrations"
const DefaultLockingCollection = "migrate_advisory_lock" // the collection to use for advisory locking by default.
const lockKeyUniqueValue = 0 // the unique value to lock on. If multiple clients try to insert the same key, it will fail (locked).
const DefaultLockTimeout = 15 // the default maximum time to wait for a lock to be released.
const DefaultLockTimeoutInterval = 10 // the default maximum intervals time for the locking timout.
const DefaultAdvisoryLockingFlag = true // the default value for the advisory locking feature flag. Default is true.
const LockIndexName = "lock_unique_key" // the name of the index which adds unique constraint to the locking_key field.
const contextWaitTimeout = 5 * time.Second // how long to wait for the request to mongo to block/wait for.
var (
ErrNoDatabaseName = fmt.Errorf("no database name")
ErrNilConfig = fmt.Errorf("no config")
)
type Mongo struct {
client *mongo.Client
db *mongo.Database
config *Config
}
type Locking struct {
CollectionName string
Timeout int
Enabled bool
Interval int
}
type Config struct {
DatabaseName string
MigrationsCollection string
TransactionMode bool
Locking Locking
}
type versionInfo struct {
Version int `bson:"version"`
Dirty bool `bson:"dirty"`
}
type lockObj struct {
Key int `bson:"locking_key"`
Pid int `bson:"pid"`
Hostname string `bson:"hostname"`
CreatedAt time.Time `bson:"created_at"`
}
type findFilter struct {
Key int `bson:"locking_key"`
}
func WithInstance(instance *mongo.Client, config *Config) (database.Driver, error) {
if config == nil {
return nil, ErrNilConfig
}
if len(config.DatabaseName) == 0 {
return nil, ErrNoDatabaseName
}
if len(config.MigrationsCollection) == 0 {
config.MigrationsCollection = DefaultMigrationsCollection
}
if len(config.Locking.CollectionName) == 0 {
config.Locking.CollectionName = DefaultLockingCollection
}
if config.Locking.Timeout <= 0 {
config.Locking.Timeout = DefaultLockTimeout
}
if config.Locking.Interval <= 0 {
config.Locking.Interval = DefaultLockTimeoutInterval
}
mc := &Mongo{
client: instance,
db: instance.Database(config.DatabaseName),
config: config,
}
if mc.config.Locking.Enabled {
if err := mc.ensureLockTable(); err != nil {
return nil, err
}
}
if err := mc.ensureVersionTable(); err != nil {
return nil, err
}
return mc, nil
}
func (m *Mongo) Open(dsn string) (database.Driver, error) {
//connstring is experimental package, but it used for parse connection string in mongo.Connect function
uri, err := connstring.Parse(dsn)
if err != nil {
return nil, err
}
if len(uri.Database) == 0 {
return nil, ErrNoDatabaseName
}
unknown := url.Values(uri.UnknownOptions)
migrationsCollection := unknown.Get("x-migrations-collection")
lockCollection := unknown.Get("x-advisory-lock-collection")
transactionMode, err := parseBoolean(unknown.Get("x-transaction-mode"), false)
if err != nil {
return nil, err
}
advisoryLockingFlag, err := parseBoolean(unknown.Get("x-advisory-locking"), DefaultAdvisoryLockingFlag)
if err != nil {
return nil, err
}
lockingTimout, err := parseInt(unknown.Get("x-advisory-lock-timeout"), DefaultLockTimeout)
if err != nil {
return nil, err
}
maxLockingIntervals, err := parseInt(unknown.Get("x-advisory-lock-timout-interval"), DefaultLockTimeoutInterval)
if err != nil {
return nil, err
}
client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(dsn))
if err != nil {
return nil, err
}
if err = client.Ping(context.TODO(), nil); err != nil {
return nil, err
}
mc, err := WithInstance(client, &Config{
DatabaseName: uri.Database,
MigrationsCollection: migrationsCollection,
TransactionMode: transactionMode,
Locking: Locking{
CollectionName: lockCollection,
Timeout: lockingTimout,
Enabled: advisoryLockingFlag,
Interval: maxLockingIntervals,
},
})
if err != nil {
return nil, err
}
return mc, nil
}
//Parse the url param, convert it to boolean
// returns error if param invalid. returns defaultValue if param not present
func parseBoolean(urlParam string, defaultValue bool) (bool, error) {
// if parameter passed, parse it (otherwise return default value)
if urlParam != "" {
result, err := strconv.ParseBool(urlParam)
if err != nil {
return false, err
}
return result, nil
}
// if no url Param passed, return default value
return defaultValue, nil
}
//Parse the url param, convert it to int
// returns error if param invalid. returns defaultValue if param not present
func parseInt(urlParam string, defaultValue int) (int, error) {
// if parameter passed, parse it (otherwise return default value)
if urlParam != "" {
result, err := strconv.Atoi(urlParam)
if err != nil {
return -1, err
}
return result, nil
}
// if no url Param passed, return default value
return defaultValue, nil
}
func (m *Mongo) SetVersion(version int, dirty bool) error {
migrationsCollection := m.db.Collection(m.config.MigrationsCollection)
if err := migrationsCollection.Drop(context.TODO()); err != nil {
return &database.Error{OrigErr: err, Err: "drop migrations collection failed"}
}
_, err := migrationsCollection.InsertOne(context.TODO(), bson.M{"version": version, "dirty": dirty})
if err != nil {
return &database.Error{OrigErr: err, Err: "save version failed"}
}
return nil
}
func (m *Mongo) Version() (version int, dirty bool, err error) {
var versionInfo versionInfo
err = m.db.Collection(m.config.MigrationsCollection).FindOne(context.TODO(), bson.M{}).Decode(&versionInfo)
switch {
case err == mongo.ErrNoDocuments:
return database.NilVersion, false, nil
case err != nil:
return 0, false, &database.Error{OrigErr: err, Err: "failed to get migration version"}
default:
return versionInfo.Version, versionInfo.Dirty, nil
}
}
func (m *Mongo) Run(migration io.Reader) error {
migr, err := ioutil.ReadAll(migration)
if err != nil {
return err
}
var cmds []bson.D
err = bson.UnmarshalExtJSON(migr, true, &cmds)
if err != nil {
return fmt.Errorf("unmarshaling json error: %s", err)
}
if m.config.TransactionMode {
if err := m.executeCommandsWithTransaction(context.TODO(), cmds); err != nil {
return err
}
} else {
if err := m.executeCommands(context.TODO(), cmds); err != nil {
return err
}
}
return nil
}
func (m *Mongo) executeCommandsWithTransaction(ctx context.Context, cmds []bson.D) error {
err := m.db.Client().UseSession(ctx, func(sessionContext mongo.SessionContext) error {
if err := sessionContext.StartTransaction(); err != nil {
return &database.Error{OrigErr: err, Err: "failed to start transaction"}
}
if err := m.executeCommands(sessionContext, cmds); err != nil {
//When command execution is failed, it's aborting transaction
//If you tried to call abortTransaction, it`s return error that transaction already aborted
return err
}
if err := sessionContext.CommitTransaction(sessionContext); err != nil {
return &database.Error{OrigErr: err, Err: "failed to commit transaction"}
}
return nil
})
if err != nil {
return err
}
return nil
}
func (m *Mongo) executeCommands(ctx context.Context, cmds []bson.D) error {
for _, cmd := range cmds {
err := m.db.RunCommand(ctx, cmd).Err()
if err != nil {
return &database.Error{OrigErr: err, Err: fmt.Sprintf("failed to execute command:%v", cmd)}
}
}
return nil
}
func (m *Mongo) Close() error {
return m.client.Disconnect(context.TODO())
}
func (m *Mongo) Drop() error {
return m.db.Drop(context.TODO())
}
func (m *Mongo) ensureLockTable() error {
indexes := m.db.Collection(m.config.Locking.CollectionName).Indexes()
indexOptions := options.Index().SetUnique(true).SetName(LockIndexName)
_, err := indexes.CreateOne(context.TODO(), mongo.IndexModel{
Options: indexOptions,
Keys: findFilter{Key: -1},
})
if err != nil {
return err
}
return nil
}
// ensureVersionTable checks if versions table exists and, if not, creates it.
// Note that this function locks the database, which deviates from the usual
// convention of "caller locks" in the MongoDb type.
func (m *Mongo) ensureVersionTable() (err error) {
if err = m.Lock(); err != nil {
return err
}
defer func() {
if e := m.Unlock(); e != nil {
if err == nil {
err = e
} else {
err = multierror.Append(err, e)
}
}
}()
if err != nil {
return err
}
if _, _, err = m.Version(); err != nil {
return err
}
return nil
}
// Utilizes advisory locking on the config.LockingCollection collection
// This uses a unique index on the `locking_key` field.
func (m *Mongo) Lock() error {
if !m.config.Locking.Enabled {
return nil
}
pid := os.Getpid()
hostname, err := os.Hostname()
if err != nil {
hostname = fmt.Sprintf("Could not determine hostname. Error: %s", err.Error())
}
newLockObj := lockObj{
Key: lockKeyUniqueValue,
Pid: pid,
Hostname: hostname,
CreatedAt: time.Now(),
}
operation := func() error {
timeout, cancelFunc := context.WithTimeout(context.Background(), contextWaitTimeout)
_, err := m.db.Collection(m.config.Locking.CollectionName).InsertOne(timeout, newLockObj)
defer cancelFunc()
return err
}
exponentialBackOff := backoff.NewExponentialBackOff()
duration := time.Duration(m.config.Locking.Timeout) * time.Second
exponentialBackOff.MaxElapsedTime = duration
exponentialBackOff.MaxInterval = time.Duration(m.config.Locking.Interval) * time.Second
err = backoff.Retry(operation, exponentialBackOff)
if err != nil {
return database.ErrLocked
}
return nil
}
func (m *Mongo) Unlock() error {
if !m.config.Locking.Enabled {
return nil
}
filter := findFilter{
Key: lockKeyUniqueValue,
}
ctx, cancel := context.WithTimeout(context.Background(), contextWaitTimeout)
_, err := m.db.Collection(m.config.Locking.CollectionName).DeleteMany(ctx, filter)
defer cancel()
if err != nil {
return err
}
return nil
}
+416
View File
@@ -0,0 +1,416 @@
package mongodb
import (
"bytes"
"context"
"fmt"
"log"
"github.com/golang-migrate/migrate/v4"
"io"
"os"
"strconv"
"testing"
"time"
)
import (
"github.com/dhui/dktest"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
import (
dt "github.com/golang-migrate/migrate/v4/database/testing"
"github.com/golang-migrate/migrate/v4/dktesting"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
var (
opts = dktest.Options{PortRequired: true, ReadyFunc: isReady}
// Supported versions: https://www.mongodb.com/support-policy
specs = []dktesting.ContainerSpec{
{ImageName: "mongo:3.4", Options: opts},
{ImageName: "mongo:3.6", Options: opts},
{ImageName: "mongo:4.0", Options: opts},
{ImageName: "mongo:4.2", Options: opts},
}
)
func mongoConnectionString(host, port string) string {
// there is connect option for excluding serverConnection algorithm
// it's let avoid errors with mongo replica set connection in docker container
return fmt.Sprintf("mongodb://%s:%s/testMigration?connect=direct", host, port)
}
func isReady(ctx context.Context, c dktest.ContainerInfo) bool {
ip, port, err := c.FirstPort()
if err != nil {
return false
}
client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoConnectionString(ip, port)))
if err != nil {
return false
}
defer func() {
if err := client.Disconnect(ctx); err != nil {
log.Println("close error:", err)
}
}()
if err = client.Ping(ctx, nil); err != nil {
switch err {
case io.EOF:
return false
default:
log.Println(err)
}
return false
}
return true
}
func Test(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.FirstPort()
if err != nil {
t.Fatal(err)
}
addr := mongoConnectionString(ip, port)
p := &Mongo{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
dt.TestNilVersion(t, d)
dt.TestLockAndUnlock(t, d)
dt.TestRun(t, d, bytes.NewReader([]byte(`[{"insert":"hello","documents":[{"wild":"world"}]}]`)))
dt.TestSetVersion(t, d)
dt.TestDrop(t, d)
})
}
func TestMigrate(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.FirstPort()
if err != nil {
t.Fatal(err)
}
addr := mongoConnectionString(ip, port)
p := &Mongo{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
m, err := migrate.NewWithDatabaseInstance("file://./examples/migrations", "", d)
if err != nil {
t.Fatal(err)
}
dt.TestMigrate(t, m)
})
}
func TestWithAuth(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.FirstPort()
if err != nil {
t.Fatal(err)
}
addr := mongoConnectionString(ip, port)
p := &Mongo{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
createUserCMD := []byte(`[{"createUser":"deminem","pwd":"gogo","roles":[{"role":"readWrite","db":"testMigration"}]}]`)
err = d.Run(bytes.NewReader(createUserCMD))
if err != nil {
t.Fatal(err)
}
testcases := []struct {
name string
connectUri string
isErrorExpected bool
}{
{"right auth data", "mongodb://deminem:gogo@%s:%v/testMigration", false},
{"wrong auth data", "mongodb://wrong:auth@%s:%v/testMigration", true},
}
for _, tcase := range testcases {
t.Run(tcase.name, func(t *testing.T) {
mc := &Mongo{}
d, err := mc.Open(fmt.Sprintf(tcase.connectUri, ip, port))
if err == nil {
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
}
switch {
case tcase.isErrorExpected && err == nil:
t.Fatalf("no error when expected")
case !tcase.isErrorExpected && err != nil:
t.Fatalf("unexpected error: %v", err)
}
})
}
})
}
func TestLockWorks(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.FirstPort()
if err != nil {
t.Fatal(err)
}
addr := mongoConnectionString(ip, port)
p := &Mongo{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
dt.TestRun(t, d, bytes.NewReader([]byte(`[{"insert":"hello","documents":[{"wild":"world"}]}]`)))
mc := d.(*Mongo)
err = mc.Lock()
if err != nil {
t.Fatal(err)
}
err = mc.Unlock()
if err != nil {
t.Fatal(err)
}
err = mc.Lock()
if err != nil {
t.Fatal(err)
}
err = mc.Unlock()
if err != nil {
t.Fatal(err)
}
// disable locking, validate wer can lock twice
mc.config.Locking.Enabled = false
err = mc.Lock()
if err != nil {
t.Fatal(err)
}
err = mc.Lock()
if err != nil {
t.Fatal(err)
}
// re-enable locking,
//try to hit a lock conflict
mc.config.Locking.Enabled = true
mc.config.Locking.Timeout = 1
err = mc.Lock()
if err != nil {
t.Fatal(err)
}
err = mc.Lock()
if err == nil {
t.Fatal("should have failed, mongo should be locked already")
}
})
}
func TestTransaction(t *testing.T) {
transactionSpecs := []dktesting.ContainerSpec{
{ImageName: "mongo:4", Options: dktest.Options{PortRequired: true, ReadyFunc: isReady,
Cmd: []string{"mongod", "--bind_ip_all", "--replSet", "rs0"}}},
}
dktesting.ParallelTest(t, transactionSpecs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.FirstPort()
if err != nil {
t.Fatal(err)
}
client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(mongoConnectionString(ip, port)))
if err != nil {
t.Fatal(err)
}
err = client.Ping(context.TODO(), nil)
if err != nil {
t.Fatal(err)
}
//rs.initiate()
err = client.Database("admin").RunCommand(context.TODO(), bson.D{bson.E{Key: "replSetInitiate", Value: bson.D{}}}).Err()
if err != nil {
t.Fatal(err)
}
err = waitForReplicaInit(client)
if err != nil {
t.Fatal(err)
}
d, err := WithInstance(client, &Config{
DatabaseName: "testMigration",
})
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
//We have to create collection
//transactions don't support operations with creating new dbs, collections
//Unique index need for checking transaction aborting
insertCMD := []byte(`[
{"create":"hello"},
{"createIndexes": "hello",
"indexes": [{
"key": {
"wild": 1
},
"name": "unique_wild",
"unique": true,
"background": true
}]
}]`)
err = d.Run(bytes.NewReader(insertCMD))
if err != nil {
t.Fatal(err)
}
testcases := []struct {
name string
cmds []byte
documentsCount int64
isErrorExpected bool
}{
{
name: "success transaction",
cmds: []byte(`[{"insert":"hello","documents":[
{"wild":"world"},
{"wild":"west"},
{"wild":"natural"}
]
}]`),
documentsCount: 3,
isErrorExpected: false,
},
{
name: "failure transaction",
//transaction have to be failure - duplicate unique key wild:west
//none of the documents should be added
cmds: []byte(`[{"insert":"hello","documents":[{"wild":"flower"}]},
{"insert":"hello","documents":[
{"wild":"cat"},
{"wild":"west"}
]
}]`),
documentsCount: 3,
isErrorExpected: true,
},
}
for _, tcase := range testcases {
t.Run(tcase.name, func(t *testing.T) {
client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(mongoConnectionString(ip, port)))
if err != nil {
t.Fatal(err)
}
err = client.Ping(context.TODO(), nil)
if err != nil {
t.Fatal(err)
}
d, err := WithInstance(client, &Config{
DatabaseName: "testMigration",
TransactionMode: true,
})
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
runErr := d.Run(bytes.NewReader(tcase.cmds))
if runErr != nil {
if !tcase.isErrorExpected {
t.Fatal(runErr)
}
}
documentsCount, err := client.Database("testMigration").Collection("hello").CountDocuments(context.TODO(), bson.M{})
if err != nil {
t.Fatal(err)
}
if tcase.documentsCount != documentsCount {
t.Fatalf("expected %d and actual %d documents count not equal. run migration error:%s", tcase.documentsCount, documentsCount, runErr)
}
})
}
})
}
type isMaster struct {
IsMaster bool `bson:"ismaster"`
}
func waitForReplicaInit(client *mongo.Client) error {
ticker := time.NewTicker(time.Second * 1)
defer ticker.Stop()
timeout, err := strconv.Atoi(os.Getenv("MIGRATE_TEST_MONGO_REPLICA_SET_INIT_TIMEOUT"))
if err != nil {
timeout = 30
}
timeoutTimer := time.NewTimer(time.Duration(timeout) * time.Second)
defer timeoutTimer.Stop()
for {
select {
case <-ticker.C:
var status isMaster
//Check that node is primary because
//during replica set initialization, the first node first becomes a secondary and then becomes the primary
//should consider that initialization is completed only after the node has become the primary
result := client.Database("admin").RunCommand(context.TODO(), bson.D{bson.E{Key: "isMaster", Value: 1}})
r, err := result.DecodeBytes()
if err != nil {
return err
}
err = bson.Unmarshal(r, &status)
if err != nil {
return err
}
if status.IsMaster {
return nil
}
case <-timeoutTimer.C:
return fmt.Errorf("replica init timeout")
}
}
}
+46
View File
@@ -0,0 +1,46 @@
// Package multistmt provides methods for parsing multi-statement database migrations
package multistmt
import (
"bufio"
"bytes"
"io"
)
// StartBufSize is the default starting size of the buffer used to scan and parse multi-statement migrations
var StartBufSize = 4096
// Handler handles a single migration parsed from a multi-statement migration.
// It's given the single migration to handle and returns whether or not further statements
// from the multi-statement migration should be parsed and handled.
type Handler func(migration []byte) bool
func splitWithDelimiter(delimiter []byte) func(d []byte, atEOF bool) (int, []byte, error) {
return func(d []byte, atEOF bool) (int, []byte, error) {
// SplitFunc inspired by bufio.ScanLines() implementation
if atEOF {
if len(d) == 0 {
return 0, nil, nil
}
return len(d), d, nil
}
if i := bytes.Index(d, delimiter); i >= 0 {
return i + len(delimiter), d[:i+len(delimiter)], nil
}
return 0, nil, nil
}
}
// Parse parses the given multi-statement migration
func Parse(reader io.Reader, delimiter []byte, maxMigrationSize int, h Handler) error {
scanner := bufio.NewScanner(reader)
scanner.Buffer(make([]byte, 0, StartBufSize), maxMigrationSize)
scanner.Split(splitWithDelimiter(delimiter))
for scanner.Scan() {
cont := h(scanner.Bytes())
if !cont {
break
}
}
return scanner.Err()
}
+57
View File
@@ -0,0 +1,57 @@
package multistmt_test
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/golang-migrate/migrate/v4/database/multistmt"
)
const maxMigrationSize = 1024
func TestParse(t *testing.T) {
testCases := []struct {
name string
multiStmt string
delimiter string
expected []string
expectedErr error
}{
{name: "single statement, no delimiter", multiStmt: "single statement, no delimiter", delimiter: ";",
expected: []string{"single statement, no delimiter"}, expectedErr: nil},
{name: "single statement, one delimiter", multiStmt: "single statement, one delimiter;", delimiter: ";",
expected: []string{"single statement, one delimiter;"}, expectedErr: nil},
{name: "two statements, no trailing delimiter", multiStmt: "statement one; statement two", delimiter: ";",
expected: []string{"statement one;", " statement two"}, expectedErr: nil},
{name: "two statements, with trailing delimiter", multiStmt: "statement one; statement two;", delimiter: ";",
expected: []string{"statement one;", " statement two;"}, expectedErr: nil},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
stmts := make([]string, 0, len(tc.expected))
err := multistmt.Parse(strings.NewReader(tc.multiStmt), []byte(tc.delimiter), maxMigrationSize, func(b []byte) bool {
stmts = append(stmts, string(b))
return true
})
assert.Equal(t, tc.expectedErr, err)
assert.Equal(t, tc.expected, stmts)
})
}
}
func TestParseDiscontinue(t *testing.T) {
multiStmt := "statement one; statement two"
delimiter := ";"
expected := []string{"statement one;"}
stmts := make([]string, 0, len(expected))
err := multistmt.Parse(strings.NewReader(multiStmt), []byte(delimiter), maxMigrationSize, func(b []byte) bool {
stmts = append(stmts, string(b))
return false
})
assert.Nil(t, err)
assert.Equal(t, expected, stmts)
}
+55 -24
View File
@@ -1,24 +1,55 @@
# mysql
`mysql://user:password@tcp(host:port)/dbname?query`
| URL Query | WithInstance Config | Description |
|------------|---------------------|-------------|
| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |
| `dbname` | `DatabaseName` | The name of the database to connect to |
| `user` | | The user to sign in as |
| `password` | | The user's password |
| `host` | | The host to connect to. |
| `port` | | The port to bind to. |
| `x-tls-ca` | | The location of the root certificate file. |
| `x-tls-cert` | | Cert file location. |
| `x-tls-key` | | Key file location. |
| `x-tls-insecure-skip-verify` | | Whether or not to use SSL (true\|false) |
## Upgrading from v1
1. Write down the current migration version from schema_migrations
1. `DROP TABLE schema_migrations`
2. Wrap your existing migrations in transactions ([BEGIN/COMMIT](https://dev.mysql.com/doc/refman/5.7/en/commit.html)) if you use multiple statements within one migration.
3. Download and install the latest migrate version.
4. Force the current migration version with `migrate force <current_version>`.
# MySQL
`mysql://user:password@tcp(host:port)/dbname?query`
| URL Query | WithInstance Config | Description |
|------------|---------------------|-------------|
| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |
| `x-no-lock` | `NoLock` | Set to `true` to skip `GET_LOCK`/`RELEASE_LOCK` statements. Useful for [multi-master MySQL flavors](https://www.percona.com/doc/percona-xtradb-cluster/LATEST/features/pxc-strict-mode.html#explicit-table-locking). Only run migrations from one host when this is enabled. |
| `dbname` | `DatabaseName` | The name of the database to connect to |
| `user` | | The user to sign in as |
| `password` | | The user's password |
| `host` | | The host to connect to. |
| `port` | | The port to bind to. |
| `tls` | | TLS / SSL encrypted connection parameter; see [go-sql-driver](https://github.com/go-sql-driver/mysql#tls). Use any name (e.g. `migrate`) if you want to use a custom TLS config (`x-tls-` queries). |
| `x-tls-ca` | | The location of the CA (certificate authority) file. |
| `x-tls-cert` | | The location of the client certicicate file. Must be used with `x-tls-key`. |
| `x-tls-key` | | The location of the private key file. Must be used with `x-tls-cert`. |
| `x-tls-insecure-skip-verify` | | Whether or not to use SSL (true\|false) |
## Use with existing client
If you use the MySQL driver with existing database client, you must create the client with parameter `multiStatements=true`:
```go
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"github.com/golang-migrate/migrate"
"github.com/golang-migrate/migrate/database/mysql"
_ "github.com/golang-migrate/migrate/source/file"
)
func main() {
db, _ := sql.Open("mysql", "user:password@tcp(host:port)/dbname?multiStatements=true")
driver, _ := mysql.WithInstance(db, &mysql.Config{})
m, _ := migrate.NewWithDatabaseInstance(
"file:///migrations",
"mysql",
driver,
)
m.Steps(2)
}
```
## Upgrading from v1
1. Write down the current migration version from schema_migrations
1. `DROP TABLE schema_migrations`
2. Wrap your existing migrations in transactions ([BEGIN/COMMIT](https://dev.mysql.com/doc/refman/5.7/en/commit.html)) if you use multiple statements within one migration.
3. Download and install the latest migrate version.
4. Force the current migration version with `migrate force <current_version>`.
@@ -0,0 +1 @@
DROP TABLE IF EXISTS test;
@@ -0,0 +1,3 @@
CREATE TABLE IF NOT EXISTS test (
firstname VARCHAR(16)
);
+242 -87
View File
@@ -1,6 +1,9 @@
// +build go1.9
package mysql
import (
"context"
"crypto/tls"
"crypto/x509"
"database/sql"
@@ -10,10 +13,15 @@ import (
nurl "net/url"
"strconv"
"strings"
)
import (
"github.com/go-sql-driver/mysql"
"github.com/mattes/migrate"
"github.com/mattes/migrate/database"
"github.com/hashicorp/go-multierror"
)
import (
"github.com/golang-migrate/migrate/v4/database"
)
func init() {
@@ -23,18 +31,23 @@ func init() {
var DefaultMigrationsTable = "schema_migrations"
var (
ErrDatabaseDirty = fmt.Errorf("database is dirty")
ErrNilConfig = fmt.Errorf("no config")
ErrNoDatabaseName = fmt.Errorf("no database name")
ErrAppendPEM = fmt.Errorf("failed to append PEM")
ErrDatabaseDirty = fmt.Errorf("database is dirty")
ErrNilConfig = fmt.Errorf("no config")
ErrNoDatabaseName = fmt.Errorf("no database name")
ErrAppendPEM = fmt.Errorf("failed to append PEM")
ErrTLSCertKeyConfig = fmt.Errorf("To use TLS client authentication, both x-tls-cert and x-tls-key must not be empty")
)
type Config struct {
MigrationsTable string
DatabaseName string
NoLock bool
}
type Mysql struct {
// mysql RELEASE_LOCK must be called from the same conn, so
// just do everything over a single conn anyway.
conn *sql.Conn
db *sql.DB
isLocked bool
@@ -51,23 +64,31 @@ func WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {
return nil, err
}
query := `SELECT DATABASE()`
var databaseName sql.NullString
if err := instance.QueryRow(query).Scan(&databaseName); err != nil {
return nil, &database.Error{OrigErr: err, Query: []byte(query)}
}
if config.DatabaseName == "" {
query := `SELECT DATABASE()`
var databaseName sql.NullString
if err := instance.QueryRow(query).Scan(&databaseName); err != nil {
return nil, &database.Error{OrigErr: err, Query: []byte(query)}
}
if len(databaseName.String) == 0 {
return nil, ErrNoDatabaseName
}
if len(databaseName.String) == 0 {
return nil, ErrNoDatabaseName
}
config.DatabaseName = databaseName.String
config.DatabaseName = databaseName.String
}
if len(config.MigrationsTable) == 0 {
config.MigrationsTable = DefaultMigrationsTable
}
conn, err := instance.Conn(context.Background())
if err != nil {
return nil, err
}
mx := &Mysql{
conn: conn,
db: instance,
config: config,
}
@@ -79,66 +100,139 @@ func WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {
return mx, nil
}
func (m *Mysql) Open(url string) (database.Driver, error) {
purl, err := nurl.Parse(url)
if err != nil {
return nil, err
// extractCustomQueryParams extracts the custom query params (ones that start with "x-") from
// mysql.Config.Params (connection parameters) as to not interfere with connecting to MySQL
func extractCustomQueryParams(c *mysql.Config) (map[string]string, error) {
if c == nil {
return nil, ErrNilConfig
}
customQueryParams := map[string]string{}
q := purl.Query()
q.Set("multiStatements", "true")
purl.RawQuery = q.Encode()
db, err := sql.Open("mysql", strings.Replace(
migrate.FilterCustomQuery(purl).String(), "mysql://", "", 1))
if err != nil {
return nil, err
for k, v := range c.Params {
if strings.HasPrefix(k, "x-") {
customQueryParams[k] = v
delete(c.Params, k)
}
}
return customQueryParams, nil
}
migrationsTable := purl.Query().Get("x-migrations-table")
if len(migrationsTable) == 0 {
migrationsTable = DefaultMigrationsTable
}
func urlToMySQLConfig(url string) (*mysql.Config, error) {
// Need to parse out custom TLS parameters and call
// mysql.RegisterTLSConfig() before mysql.ParseDSN() is called
// which consumes the registered tls.Config
// Fixes: https://github.com/golang-migrate/migrate/issues/411
//
// Can't use url.Parse() since it fails to parse MySQL DSNs
// mysql.ParseDSN() also searches for "?" to find query parameters:
// https://github.com/go-sql-driver/mysql/blob/46351a8/dsn.go#L344
if idx := strings.LastIndex(url, "?"); idx > 0 {
rawParams := url[idx+1:]
parsedParams, err := nurl.ParseQuery(rawParams)
if err != nil {
return nil, err
}
// use custom TLS?
ctls := purl.Query().Get("tls")
if len(ctls) > 0 {
if _, isBool := readBool(ctls); !isBool && strings.ToLower(ctls) != "skip-verify" {
rootCertPool := x509.NewCertPool()
pem, err := ioutil.ReadFile(purl.Query().Get("x-tls-ca"))
if err != nil {
return nil, err
}
if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
return nil, ErrAppendPEM
}
certs, err := tls.LoadX509KeyPair(purl.Query().Get("x-tls-cert"), purl.Query().Get("x-tls-key"))
if err != nil {
return nil, err
}
insecureSkipVerify := false
if len(purl.Query().Get("x-tls-insecure-skip-verify")) > 0 {
x, err := strconv.ParseBool(purl.Query().Get("x-tls-insecure-skip-verify"))
ctls := parsedParams.Get("tls")
if len(ctls) > 0 {
if _, isBool := readBool(ctls); !isBool && strings.ToLower(ctls) != "skip-verify" {
rootCertPool := x509.NewCertPool()
pem, err := ioutil.ReadFile(parsedParams.Get("x-tls-ca"))
if err != nil {
return nil, err
}
insecureSkipVerify = x
}
mysql.RegisterTLSConfig(ctls, &tls.Config{
RootCAs: rootCertPool,
Certificates: []tls.Certificate{certs},
InsecureSkipVerify: insecureSkipVerify,
})
if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
return nil, ErrAppendPEM
}
clientCert := make([]tls.Certificate, 0, 1)
if ccert, ckey := parsedParams.Get("x-tls-cert"), parsedParams.Get("x-tls-key"); ccert != "" || ckey != "" {
if ccert == "" || ckey == "" {
return nil, ErrTLSCertKeyConfig
}
certs, err := tls.LoadX509KeyPair(ccert, ckey)
if err != nil {
return nil, err
}
clientCert = append(clientCert, certs)
}
insecureSkipVerify := false
insecureSkipVerifyStr := parsedParams.Get("x-tls-insecure-skip-verify")
if len(insecureSkipVerifyStr) > 0 {
x, err := strconv.ParseBool(insecureSkipVerifyStr)
if err != nil {
return nil, err
}
insecureSkipVerify = x
}
err = mysql.RegisterTLSConfig(ctls, &tls.Config{
RootCAs: rootCertPool,
Certificates: clientCert,
InsecureSkipVerify: insecureSkipVerify,
})
if err != nil {
return nil, err
}
}
}
}
config, err := mysql.ParseDSN(strings.TrimPrefix(url, "mysql://"))
if err != nil {
return nil, err
}
config.MultiStatements = true
// Keep backwards compatibility from when we used net/url.Parse() to parse the DSN.
// net/url.Parse() would automatically unescape it for us.
// See: https://play.golang.org/p/q9j1io-YICQ
user, err := nurl.QueryUnescape(config.User)
if err != nil {
return nil, err
}
config.User = user
password, err := nurl.QueryUnescape(config.Passwd)
if err != nil {
return nil, err
}
config.Passwd = password
return config, nil
}
func (m *Mysql) Open(url string) (database.Driver, error) {
config, err := urlToMySQLConfig(url)
if err != nil {
return nil, err
}
customParams, err := extractCustomQueryParams(config)
if err != nil {
return nil, err
}
noLockParam, noLock := customParams["x-no-lock"], false
if noLockParam != "" {
noLock, err = strconv.ParseBool(noLockParam)
if err != nil {
return nil, fmt.Errorf("could not parse x-no-lock as bool: %w", err)
}
}
db, err := sql.Open("mysql", config.FormatDSN())
if err != nil {
return nil, err
}
mx, err := WithInstance(db, &Config{
DatabaseName: purl.Path,
MigrationsTable: migrationsTable,
DatabaseName: config.DBName,
MigrationsTable: customParams["x-migrations-table"],
NoLock: noLock,
})
if err != nil {
return nil, err
@@ -148,7 +242,12 @@ func (m *Mysql) Open(url string) (database.Driver, error) {
}
func (m *Mysql) Close() error {
return m.db.Close()
connErr := m.conn.Close()
dbErr := m.db.Close()
if connErr != nil || dbErr != nil {
return fmt.Errorf("conn: %v, db: %v", connErr, dbErr)
}
return nil
}
func (m *Mysql) Lock() error {
@@ -156,14 +255,20 @@ func (m *Mysql) Lock() error {
return database.ErrLocked
}
aid, err := database.GenerateAdvisoryLockId(m.config.DatabaseName)
if m.config.NoLock {
m.isLocked = true
return nil
}
aid, err := database.GenerateAdvisoryLockId(
fmt.Sprintf("%s:%s", m.config.DatabaseName, m.config.MigrationsTable))
if err != nil {
return err
}
query := "SELECT GET_LOCK(?, 1)"
query := "SELECT GET_LOCK(?, 10)"
var success bool
if err := m.db.QueryRow(query, aid).Scan(&success); err != nil {
if err := m.conn.QueryRowContext(context.Background(), query, aid).Scan(&success); err != nil {
return &database.Error{OrigErr: err, Err: "try lock failed", Query: []byte(query)}
}
@@ -180,16 +285,26 @@ func (m *Mysql) Unlock() error {
return nil
}
aid, err := database.GenerateAdvisoryLockId(m.config.DatabaseName)
if m.config.NoLock {
m.isLocked = false
return nil
}
aid, err := database.GenerateAdvisoryLockId(
fmt.Sprintf("%s:%s", m.config.DatabaseName, m.config.MigrationsTable))
if err != nil {
return err
}
query := `SELECT RELEASE_LOCK(?)`
if _, err := m.db.Exec(query, aid); err != nil {
if _, err := m.conn.ExecContext(context.Background(), query, aid); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
// NOTE: RELEASE_LOCK could return NULL or (or 0 if the code is changed),
// in which case isLocked should be true until the timeout expires -- synchronizing
// these states is likely not worth trying to do; reconsider the necessity of isLocked.
m.isLocked = false
return nil
}
@@ -201,7 +316,7 @@ func (m *Mysql) Run(migration io.Reader) error {
}
query := string(migr[:])
if _, err := m.db.Exec(query); err != nil {
if _, err := m.conn.ExecContext(context.Background(), query); err != nil {
return database.Error{OrigErr: err, Err: "migration failed", Query: migr}
}
@@ -209,20 +324,28 @@ func (m *Mysql) Run(migration io.Reader) error {
}
func (m *Mysql) SetVersion(version int, dirty bool) error {
tx, err := m.db.Begin()
tx, err := m.conn.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return &database.Error{OrigErr: err, Err: "transaction start failed"}
}
query := "TRUNCATE `" + m.config.MigrationsTable + "`"
if _, err := m.db.Exec(query); err != nil {
if _, err := tx.ExecContext(context.Background(), query); err != nil {
if errRollback := tx.Rollback(); errRollback != nil {
err = multierror.Append(err, errRollback)
}
return &database.Error{OrigErr: err, Query: []byte(query)}
}
if version >= 0 {
// Also re-write the schema version for nil dirty versions to prevent
// empty schema version for failed down migration on the first migration
// See: https://github.com/golang-migrate/migrate/issues/330
if version >= 0 || (version == database.NilVersion && dirty) {
query := "INSERT INTO `" + m.config.MigrationsTable + "` (version, dirty) VALUES (?, ?)"
if _, err := m.db.Exec(query, version, dirty); err != nil {
tx.Rollback()
if _, err := tx.ExecContext(context.Background(), query, version, dirty); err != nil {
if errRollback := tx.Rollback(); errRollback != nil {
err = multierror.Append(err, errRollback)
}
return &database.Error{OrigErr: err, Query: []byte(query)}
}
}
@@ -236,7 +359,7 @@ func (m *Mysql) SetVersion(version int, dirty bool) error {
func (m *Mysql) Version() (version int, dirty bool, err error) {
query := "SELECT version, dirty FROM `" + m.config.MigrationsTable + "` LIMIT 1"
err = m.db.QueryRow(query).Scan(&version, &dirty)
err = m.conn.QueryRowContext(context.Background(), query).Scan(&version, &dirty)
switch {
case err == sql.ErrNoRows:
return database.NilVersion, false, nil
@@ -254,14 +377,18 @@ func (m *Mysql) Version() (version int, dirty bool, err error) {
}
}
func (m *Mysql) Drop() error {
func (m *Mysql) Drop() (err error) {
// select all tables
query := `SHOW TABLES LIKE '%'`
tables, err := m.db.Query(query)
tables, err := m.conn.QueryContext(context.Background(), query)
if err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
defer tables.Close()
defer func() {
if errClose := tables.Close(); errClose != nil {
err = multierror.Append(err, errClose)
}
}()
// delete one table after another
tableNames := make([]string, 0)
@@ -274,28 +401,56 @@ func (m *Mysql) Drop() error {
tableNames = append(tableNames, tableName)
}
}
if err := tables.Err(); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
if len(tableNames) > 0 {
// disable checking foreign key constraints until finished
query = `SET foreign_key_checks = 0`
if _, err := m.conn.ExecContext(context.Background(), query); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
defer func() {
// enable foreign key checks
_, _ = m.conn.ExecContext(context.Background(), `SET foreign_key_checks = 1`)
}()
// delete one by one ...
for _, t := range tableNames {
query = "DROP TABLE IF EXISTS `" + t + "` CASCADE"
if _, err := m.db.Exec(query); err != nil {
query = "DROP TABLE IF EXISTS `" + t + "`"
if _, err := m.conn.ExecContext(context.Background(), query); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
}
if err := m.ensureVersionTable(); err != nil {
return err
}
}
return nil
}
func (m *Mysql) ensureVersionTable() error {
// ensureVersionTable checks if versions table exists and, if not, creates it.
// Note that this function locks the database, which deviates from the usual
// convention of "caller locks" in the Mysql type.
func (m *Mysql) ensureVersionTable() (err error) {
if err = m.Lock(); err != nil {
return err
}
defer func() {
if e := m.Unlock(); e != nil {
if err == nil {
err = e
} else {
err = multierror.Append(err, e)
}
}
}()
// check if migration table exists
var result string
query := `SHOW TABLES LIKE "` + m.config.MigrationsTable + `"`
if err := m.db.QueryRow(query).Scan(&result); err != nil {
query := `SHOW TABLES LIKE '` + m.config.MigrationsTable + `'`
if err := m.conn.QueryRowContext(context.Background(), query).Scan(&result); err != nil {
if err != sql.ErrNoRows {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
@@ -305,7 +460,7 @@ func (m *Mysql) ensureVersionTable() error {
// if not, create the empty migration table
query = "CREATE TABLE `" + m.config.MigrationsTable + "` (version bigint not null primary key, dirty boolean not null)"
if _, err := m.db.Exec(query); err != nil {
if _, err := m.conn.ExecContext(context.Background(), query); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
return nil
+398 -32
View File
@@ -1,34 +1,86 @@
package mysql
import (
"context"
"crypto/ed25519"
"crypto/x509"
"database/sql"
sqldriver "database/sql/driver"
"encoding/pem"
"errors"
"fmt"
// "io/ioutil"
// "log"
"io/ioutil"
"log"
"math/big"
"math/rand"
"net/url"
"os"
"strconv"
"testing"
// "github.com/go-sql-driver/mysql"
dt "github.com/mattes/migrate/database/testing"
mt "github.com/mattes/migrate/testing"
)
var versions = []mt.Version{
{"mysql:8", []string{"MYSQL_ROOT_PASSWORD=root", "MYSQL_DATABASE=public"}},
{"mysql:5.7", []string{"MYSQL_ROOT_PASSWORD=root", "MYSQL_DATABASE=public"}},
{"mysql:5.6", []string{"MYSQL_ROOT_PASSWORD=root", "MYSQL_DATABASE=public"}},
{"mysql:5.5", []string{"MYSQL_ROOT_PASSWORD=root", "MYSQL_DATABASE=public"}},
}
import (
"github.com/dhui/dktest"
"github.com/go-sql-driver/mysql"
"github.com/stretchr/testify/assert"
)
func isReady(i mt.Instance) bool {
db, err := sql.Open("mysql", fmt.Sprintf("root:root@tcp(%v:%v)/public", i.Host(), i.Port()))
import (
"github.com/golang-migrate/migrate/v4"
dt "github.com/golang-migrate/migrate/v4/database/testing"
"github.com/golang-migrate/migrate/v4/dktesting"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
const defaultPort = 3306
var (
opts = dktest.Options{
Env: map[string]string{"MYSQL_ROOT_PASSWORD": "root", "MYSQL_DATABASE": "public"},
PortRequired: true, ReadyFunc: isReady,
}
optsAnsiQuotes = dktest.Options{
Env: map[string]string{"MYSQL_ROOT_PASSWORD": "root", "MYSQL_DATABASE": "public"},
PortRequired: true, ReadyFunc: isReady,
Cmd: []string{"--sql-mode=ANSI_QUOTES"},
}
// Supported versions: https://www.mysql.com/support/supportedplatforms/database.html
specs = []dktesting.ContainerSpec{
{ImageName: "mysql:5.5", Options: opts},
{ImageName: "mysql:5.6", Options: opts},
{ImageName: "mysql:5.7", Options: opts},
{ImageName: "mysql:8", Options: opts},
}
specsAnsiQuotes = []dktesting.ContainerSpec{
{ImageName: "mysql:5.5", Options: optsAnsiQuotes},
{ImageName: "mysql:5.6", Options: optsAnsiQuotes},
{ImageName: "mysql:5.7", Options: optsAnsiQuotes},
{ImageName: "mysql:8", Options: optsAnsiQuotes},
}
)
func isReady(ctx context.Context, c dktest.ContainerInfo) bool {
ip, port, err := c.Port(defaultPort)
if err != nil {
return false
}
defer db.Close()
err = db.Ping()
if err == sqldriver.ErrBadConn {
db, err := sql.Open("mysql", fmt.Sprintf("root:root@tcp(%v:%v)/public", ip, port))
if err != nil {
return false
}
defer func() {
if err := db.Close(); err != nil {
log.Println("close error:", err)
}
}()
if err = db.PingContext(ctx); err != nil {
switch err {
case sqldriver.ErrBadConn, mysql.ErrInvalidConn:
return false
default:
fmt.Println(err)
}
return false
}
@@ -38,23 +90,337 @@ func isReady(i mt.Instance) bool {
func Test(t *testing.T) {
// mysql.SetLogger(mysql.Logger(log.New(ioutil.Discard, "", log.Ltime)))
mt.ParallelTest(t, versions, isReady,
func(t *testing.T, i mt.Instance) {
p := &Mysql{}
addr := fmt.Sprintf("mysql://root:root@tcp(%v:%v)/public", i.Host(), i.Port())
d, err := p.Open(addr)
if err != nil {
t.Fatalf("%v", err)
}
dt.Test(t, d, []byte("SELECT 1"))
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.Port(defaultPort)
if err != nil {
t.Fatal(err)
}
// check ensureVersionTable
if err := d.(*Mysql).ensureVersionTable(); err != nil {
t.Fatal(err)
addr := fmt.Sprintf("mysql://root:root@tcp(%v:%v)/public", ip, port)
p := &Mysql{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
// check again
if err := d.(*Mysql).ensureVersionTable(); err != nil {
t.Fatal(err)
}()
dt.Test(t, d, []byte("SELECT 1"))
// check ensureVersionTable
if err := d.(*Mysql).ensureVersionTable(); err != nil {
t.Fatal(err)
}
// check again
if err := d.(*Mysql).ensureVersionTable(); err != nil {
t.Fatal(err)
}
})
}
func TestMigrate(t *testing.T) {
// mysql.SetLogger(mysql.Logger(log.New(ioutil.Discard, "", log.Ltime)))
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.Port(defaultPort)
if err != nil {
t.Fatal(err)
}
addr := fmt.Sprintf("mysql://root:root@tcp(%v:%v)/public", ip, port)
p := &Mysql{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
m, err := migrate.NewWithDatabaseInstance("file://./examples/migrations", "public", d)
if err != nil {
t.Fatal(err)
}
dt.TestMigrate(t, m)
// check ensureVersionTable
if err := d.(*Mysql).ensureVersionTable(); err != nil {
t.Fatal(err)
}
// check again
if err := d.(*Mysql).ensureVersionTable(); err != nil {
t.Fatal(err)
}
})
}
func TestMigrateAnsiQuotes(t *testing.T) {
// mysql.SetLogger(mysql.Logger(log.New(ioutil.Discard, "", log.Ltime)))
dktesting.ParallelTest(t, specsAnsiQuotes, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.Port(defaultPort)
if err != nil {
t.Fatal(err)
}
addr := fmt.Sprintf("mysql://root:root@tcp(%v:%v)/public", ip, port)
p := &Mysql{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
m, err := migrate.NewWithDatabaseInstance("file://./examples/migrations", "public", d)
if err != nil {
t.Fatal(err)
}
dt.TestMigrate(t, m)
// check ensureVersionTable
if err := d.(*Mysql).ensureVersionTable(); err != nil {
t.Fatal(err)
}
// check again
if err := d.(*Mysql).ensureVersionTable(); err != nil {
t.Fatal(err)
}
})
}
func TestLockWorks(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.Port(defaultPort)
if err != nil {
t.Fatal(err)
}
addr := fmt.Sprintf("mysql://root:root@tcp(%v:%v)/public", ip, port)
p := &Mysql{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
dt.Test(t, d, []byte("SELECT 1"))
ms := d.(*Mysql)
err = ms.Lock()
if err != nil {
t.Fatal(err)
}
err = ms.Unlock()
if err != nil {
t.Fatal(err)
}
// make sure the 2nd lock works (RELEASE_LOCK is very finicky)
err = ms.Lock()
if err != nil {
t.Fatal(err)
}
err = ms.Unlock()
if err != nil {
t.Fatal(err)
}
})
}
func TestNoLockParamValidation(t *testing.T) {
ip := "127.0.0.1"
port := 3306
addr := fmt.Sprintf("mysql://root:root@tcp(%v:%v)/public", ip, port)
p := &Mysql{}
_, err := p.Open(addr + "?x-no-lock=not-a-bool")
if !errors.Is(err, strconv.ErrSyntax) {
t.Fatal("Expected syntax error when passing a non-bool as x-no-lock parameter")
}
}
func TestNoLockWorks(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.Port(defaultPort)
if err != nil {
t.Fatal(err)
}
addr := fmt.Sprintf("mysql://root:root@tcp(%v:%v)/public", ip, port)
p := &Mysql{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
lock := d.(*Mysql)
p = &Mysql{}
d, err = p.Open(addr + "?x-no-lock=true")
if err != nil {
t.Fatal(err)
}
noLock := d.(*Mysql)
// Should be possible to take real lock and no-lock at the same time
if err = lock.Lock(); err != nil {
t.Fatal(err)
}
if err = noLock.Lock(); err != nil {
t.Fatal(err)
}
if err = lock.Unlock(); err != nil {
t.Fatal(err)
}
if err = noLock.Unlock(); err != nil {
t.Fatal(err)
}
})
}
func TestExtractCustomQueryParams(t *testing.T) {
testcases := []struct {
name string
config *mysql.Config
expectedParams map[string]string
expectedCustomParams map[string]string
expectedErr error
}{
{name: "nil config", expectedErr: ErrNilConfig},
{
name: "no params",
config: mysql.NewConfig(),
expectedCustomParams: map[string]string{},
},
{
name: "no custom params",
config: &mysql.Config{Params: map[string]string{"hello": "world"}},
expectedParams: map[string]string{"hello": "world"},
expectedCustomParams: map[string]string{},
},
{
name: "one param, one custom param",
config: &mysql.Config{
Params: map[string]string{"hello": "world", "x-foo": "bar"},
},
expectedParams: map[string]string{"hello": "world"},
expectedCustomParams: map[string]string{"x-foo": "bar"},
},
{
name: "multiple params, multiple custom params",
config: &mysql.Config{
Params: map[string]string{
"hello": "world",
"x-foo": "bar",
"dead": "beef",
"x-cat": "hat",
},
},
expectedParams: map[string]string{"hello": "world", "dead": "beef"},
expectedCustomParams: map[string]string{"x-foo": "bar", "x-cat": "hat"},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
customParams, err := extractCustomQueryParams(tc.config)
if tc.config != nil {
assert.Equal(t, tc.expectedParams, tc.config.Params,
"Expected config params have custom params properly removed")
}
assert.Equal(t, tc.expectedErr, err, "Expected errors to match")
assert.Equal(t, tc.expectedCustomParams, customParams,
"Expected custom params to be properly extracted")
})
}
}
func createTmpCert(t *testing.T) string {
tmpCertFile, err := ioutil.TempFile("", "migrate_test_cert")
if err != nil {
t.Fatal("Failed to create temp cert file:", err)
}
t.Cleanup(func() {
if err := os.Remove(tmpCertFile.Name()); err != nil {
t.Log("Failed to cleanup temp cert file:", err)
}
})
r := rand.New(rand.NewSource(0))
pub, priv, err := ed25519.GenerateKey(r)
if err != nil {
t.Fatal("Failed to generate ed25519 key for temp cert file:", err)
}
tmpl := x509.Certificate{
SerialNumber: big.NewInt(0),
}
derBytes, err := x509.CreateCertificate(r, &tmpl, &tmpl, pub, priv)
if err != nil {
t.Fatal("Failed to generate temp cert file:", err)
}
if err := pem.Encode(tmpCertFile, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
t.Fatal("Failed to encode ")
}
if err := tmpCertFile.Close(); err != nil {
t.Fatal("Failed to close temp cert file:", err)
}
return tmpCertFile.Name()
}
func TestURLToMySQLConfig(t *testing.T) {
tmpCertFilename := createTmpCert(t)
tmpCertFilenameEscaped := url.PathEscape(tmpCertFilename)
testcases := []struct {
name string
urlStr string
expectedDSN string // empty string signifies that an error is expected
}{
{name: "no user/password", urlStr: "mysql://tcp(127.0.0.1:3306)/myDB?multiStatements=true",
expectedDSN: "tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
{name: "only user", urlStr: "mysql://username@tcp(127.0.0.1:3306)/myDB?multiStatements=true",
expectedDSN: "username@tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
{name: "only user - with encoded :",
urlStr: "mysql://username%3A@tcp(127.0.0.1:3306)/myDB?multiStatements=true",
expectedDSN: "username:@tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
{name: "only user - with encoded @",
urlStr: "mysql://username%40@tcp(127.0.0.1:3306)/myDB?multiStatements=true",
expectedDSN: "username@@tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
{name: "user/password", urlStr: "mysql://username:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true",
expectedDSN: "username:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
// Not supported yet: https://github.com/go-sql-driver/mysql/issues/591
// {name: "user/password - user with encoded :",
// urlStr: "mysql://username%3A:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true",
// expectedDSN: "username::pasword@tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
{name: "user/password - user with encoded @",
urlStr: "mysql://username%40:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true",
expectedDSN: "username@:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
{name: "user/password - password with encoded :",
urlStr: "mysql://username:password%3A@tcp(127.0.0.1:3306)/myDB?multiStatements=true",
expectedDSN: "username:password:@tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
{name: "user/password - password with encoded @",
urlStr: "mysql://username:password%40@tcp(127.0.0.1:3306)/myDB?multiStatements=true",
expectedDSN: "username:password@@tcp(127.0.0.1:3306)/myDB?multiStatements=true"},
{name: "custom tls",
urlStr: "mysql://username:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true&tls=custom&x-tls-ca=" + tmpCertFilenameEscaped,
expectedDSN: "username:password@tcp(127.0.0.1:3306)/myDB?multiStatements=true&tls=custom&x-tls-ca=" + tmpCertFilenameEscaped},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
config, err := urlToMySQLConfig(tc.urlStr)
if err != nil {
t.Fatal("Failed to parse url string:", tc.urlStr, "error:", err)
}
dsn := config.FormatDSN()
if dsn != tc.expectedDSN {
t.Error("Got unexpected DSN:", dsn, "!=", tc.expectedDSN)
}
})
}
}
+20
View File
@@ -0,0 +1,20 @@
# neo4j
The Neo4j driver (bolt) does not natively support executing multiple statements in a single query. To allow for multiple statements in a single migration, you can use the `x-multi-statement` param.
This mode splits the migration text into separately-executed statements by a semi-colon `;`. Thus `x-multi-statement` cannot be used when a statement in the migration contains a string with a semi-colon.
The queries **should** run in a single transaction, so partial migrations should not be a concern, but this is untested.
`neo4j://user:password@host:port/`
| URL Query | WithInstance Config | Description |
|------------|---------------------|-------------|
| `x-multi-statement` | `MultiStatement` | Enable multiple statements to be ran in a single migration (See note above) |
| `user` | Contained within `AuthConfig` | The user to sign in as |
| `password` | Contained within `AuthConfig` | The user's password |
| `host` | | The host to connect to. Values that start with / are for unix domain sockets. (default is localhost) |
| `port` | | The port to bind to. (default is 7687) |
| | `MigrationsLabel` | Name of the migrations node label |
## Supported versions
Only Neo4j v3.5+ is [supported](https://github.com/neo4j/neo4j-go-driver/issues/64#issuecomment-625133600)
+97
View File
@@ -0,0 +1,97 @@
## Create migrations
Let's create nodes called `Users`:
```
migrate create -ext cypher -dir db/migrations -seq create_user_nodes
```
If there were no errors, we should have two files available under `db/migrations` folder:
- 000001_create_user_nodes.down.cypher
- 000001_create_user_nodes.up.cypher
Note the `cypher` extension that we provided.
In the `.up.cypher` file let's create the table:
```
CREATE (u1:User {name: "Peter"})
CREATE (u2:User {name: "Paul"})
CREATE (u3:User {name: "Mary"})
```
And in the `.down.sql` let's delete it:
```
MATCH (u:User) WHERE u.name IN ["Peter", "Paul", "Mary"] DELETE u
```
Ideally your migrations should be idempotent. You can read more about idempotency in [getting started](GETTING_STARTED.md#create-migrations)
## Run migrations
```
migrate -database ${NEO4J_URL} -path db/migrations up
```
Let's check if the table was created properly by running `bin/cypher-shell -u neo4j -p password`, then `neo4j> MATCH (u:User)`
The output you are supposed to see:
```
+-----------------------------------------------------------------+
| u |
+-----------------------------------------------------------------+
| (:User {name: "Peter") |
| (:User {name: "Paul") |
| (:User {name: "Mary") |
+-----------------------------------------------------------------+
```
Great! Now let's check if running reverse migration also works:
```
migrate -database ${NEO4J_URL} -path db/migrations down
```
Make sure to check if your database changed as expected in this case as well.
## Database transactions
To show database transactions usage, let's create another set of migrations by running:
```
migrate create -ext cypher -dir db/migrations -seq add_mood_to_users
```
Again, it should create for us two migrations files:
- 000002_add_mood_to_users.down.cypher
- 000002_add_mood_to_users.up.cypher
In Neo4j, when we want our queries to be done in a transaction, we need to wrap it with `:BEGIN` and `:COMMIT` commands.
Migration up:
```
:BEGIN
MATCH (u:User)
SET u.mood = "Cheery"
:COMMIT
```
Migration down:
```
:BEGIN
MATCH (u:User)
SET u.mood = null
:COMMIT
```
## Optional: Run migrations within your Go app
Here is a very simple app running migrations for the above configuration:
```
import (
"log"
"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/neo4j"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
func main() {
m, err := migrate.New(
"file://db/migrations",
"neo4j://neo4j:password@localhost:7687/")
if err != nil {
log.Fatal(err)
}
if err := m.Up(); err != nil {
log.Fatal(err)
}
}
```
@@ -0,0 +1 @@
DROP CONSTRAINT ON (m:Movie) ASSERT m.Name IS UNIQUE
@@ -0,0 +1 @@
CREATE CONSTRAINT ON (m:Movie) ASSERT m.Name IS UNIQUE
@@ -0,0 +1,2 @@
MATCH (m:Movie)
DELETE m
@@ -0,0 +1,2 @@
CREATE (:Movie {name: "Footloose"})
CREATE (:Movie {name: "Ghost"})
@@ -0,0 +1,3 @@
CREATE (:Movie {name: "Hollow Man"});
CREATE (:Movie {name: "Mystic River"});
;;;
+304
View File
@@ -0,0 +1,304 @@
package neo4j
import (
"bytes"
"fmt"
"io"
"io/ioutil"
neturl "net/url"
"strconv"
"sync/atomic"
"github.com/golang-migrate/migrate/v4/database"
"github.com/golang-migrate/migrate/v4/database/multistmt"
"github.com/hashicorp/go-multierror"
"github.com/neo4j/neo4j-go-driver/neo4j"
)
func init() {
db := Neo4j{}
database.Register("neo4j", &db)
}
const DefaultMigrationsLabel = "SchemaMigration"
var (
StatementSeparator = []byte(";")
DefaultMultiStatementMaxSize = 10 * 1 << 20 // 10 MB
)
var (
ErrNilConfig = fmt.Errorf("no config")
)
type Config struct {
MigrationsLabel string
MultiStatement bool
MultiStatementMaxSize int
}
type Neo4j struct {
driver neo4j.Driver
lock uint32
// Open and WithInstance need to guarantee that config is never nil
config *Config
}
func WithInstance(driver neo4j.Driver, config *Config) (database.Driver, error) {
if config == nil {
return nil, ErrNilConfig
}
nDriver := &Neo4j{
driver: driver,
config: config,
}
if err := nDriver.ensureVersionConstraint(); err != nil {
return nil, err
}
return nDriver, nil
}
func (n *Neo4j) Open(url string) (database.Driver, error) {
uri, err := neturl.Parse(url)
if err != nil {
return nil, err
}
password, _ := uri.User.Password()
authToken := neo4j.BasicAuth(uri.User.Username(), password, "")
uri.User = nil
uri.Scheme = "bolt"
msQuery := uri.Query().Get("x-multi-statement")
// Whether to turn on/off TLS encryption.
tlsEncrypted := uri.Query().Get("x-tls-encrypted")
multi := false
encrypted := false
if msQuery != "" {
multi, err = strconv.ParseBool(uri.Query().Get("x-multi-statement"))
if err != nil {
return nil, err
}
}
if tlsEncrypted != "" {
encrypted, err = strconv.ParseBool(tlsEncrypted)
if err != nil {
return nil, err
}
}
multiStatementMaxSize := DefaultMultiStatementMaxSize
if s := uri.Query().Get("x-multi-statement-max-size"); s != "" {
multiStatementMaxSize, err = strconv.Atoi(s)
if err != nil {
return nil, err
}
}
uri.RawQuery = ""
driver, err := neo4j.NewDriver(uri.String(), authToken, func(config *neo4j.Config) {
config.Encrypted = encrypted
})
if err != nil {
return nil, err
}
return WithInstance(driver, &Config{
MigrationsLabel: DefaultMigrationsLabel,
MultiStatement: multi,
MultiStatementMaxSize: multiStatementMaxSize,
})
}
func (n *Neo4j) Close() error {
return n.driver.Close()
}
// local locking in order to pass tests, Neo doesn't support database locking
func (n *Neo4j) Lock() error {
if !atomic.CompareAndSwapUint32(&n.lock, 0, 1) {
return database.ErrLocked
}
return nil
}
func (n *Neo4j) Unlock() error {
if !atomic.CompareAndSwapUint32(&n.lock, 1, 0) {
return database.ErrNotLocked
}
return nil
}
func (n *Neo4j) Run(migration io.Reader) (err error) {
session, err := n.driver.Session(neo4j.AccessModeWrite)
if err != nil {
return err
}
defer func() {
if cerr := session.Close(); cerr != nil {
err = multierror.Append(err, cerr)
}
}()
if n.config.MultiStatement {
_, err = session.WriteTransaction(func(transaction neo4j.Transaction) (interface{}, error) {
var stmtRunErr error
if err := multistmt.Parse(migration, StatementSeparator, n.config.MultiStatementMaxSize, func(stmt []byte) bool {
trimStmt := bytes.TrimSpace(stmt)
if len(trimStmt) == 0 {
return true
}
trimStmt = bytes.TrimSuffix(trimStmt, StatementSeparator)
if len(trimStmt) == 0 {
return true
}
result, err := transaction.Run(string(trimStmt), nil)
if _, err := neo4j.Collect(result, err); err != nil {
stmtRunErr = err
return false
}
return true
}); err != nil {
return nil, err
}
return nil, stmtRunErr
})
return err
}
body, err := ioutil.ReadAll(migration)
if err != nil {
return err
}
_, err = neo4j.Collect(session.Run(string(body[:]), nil))
return err
}
func (n *Neo4j) SetVersion(version int, dirty bool) (err error) {
session, err := n.driver.Session(neo4j.AccessModeWrite)
if err != nil {
return err
}
defer func() {
if cerr := session.Close(); cerr != nil {
err = multierror.Append(err, cerr)
}
}()
query := fmt.Sprintf("MERGE (sm:%s {version: $version}) SET sm.dirty = $dirty, sm.ts = datetime()",
n.config.MigrationsLabel)
_, err = neo4j.Collect(session.Run(query, map[string]interface{}{"version": version, "dirty": dirty}))
if err != nil {
return err
}
return nil
}
type MigrationRecord struct {
Version int
Dirty bool
}
func (n *Neo4j) Version() (version int, dirty bool, err error) {
session, err := n.driver.Session(neo4j.AccessModeRead)
if err != nil {
return database.NilVersion, false, err
}
defer func() {
if cerr := session.Close(); cerr != nil {
err = multierror.Append(err, cerr)
}
}()
query := fmt.Sprintf(`MATCH (sm:%s) RETURN sm.version AS version, sm.dirty AS dirty
ORDER BY COALESCE(sm.ts, datetime({year: 0})) DESC, sm.version DESC LIMIT 1`,
n.config.MigrationsLabel)
result, err := session.ReadTransaction(func(transaction neo4j.Transaction) (interface{}, error) {
result, err := transaction.Run(query, nil)
if err != nil {
return nil, err
}
if result.Next() {
record := result.Record()
mr := MigrationRecord{}
versionResult, ok := record.Get("version")
if !ok {
mr.Version = database.NilVersion
} else {
mr.Version = int(versionResult.(int64))
}
dirtyResult, ok := record.Get("dirty")
if ok {
mr.Dirty = dirtyResult.(bool)
}
return mr, nil
}
return nil, result.Err()
})
if err != nil {
return database.NilVersion, false, err
}
if result == nil {
return database.NilVersion, false, err
}
mr := result.(MigrationRecord)
return mr.Version, mr.Dirty, err
}
func (n *Neo4j) Drop() (err error) {
session, err := n.driver.Session(neo4j.AccessModeWrite)
if err != nil {
return err
}
defer func() {
if cerr := session.Close(); cerr != nil {
err = multierror.Append(err, cerr)
}
}()
if _, err := neo4j.Collect(session.Run("MATCH (n) DETACH DELETE n", nil)); err != nil {
return err
}
return nil
}
func (n *Neo4j) ensureVersionConstraint() (err error) {
session, err := n.driver.Session(neo4j.AccessModeWrite)
if err != nil {
return err
}
defer func() {
if cerr := session.Close(); cerr != nil {
err = multierror.Append(err, cerr)
}
}()
/**
Get constraint and check to avoid error duplicate
using db.labels() to support Neo4j 3 and 4.
Neo4J 3 doesn't support db.constraints() YIELD name
*/
res, err := neo4j.Collect(session.Run(fmt.Sprintf("CALL db.labels() YIELD label WHERE label=\"%s\" RETURN label", n.config.MigrationsLabel), nil))
if err != nil {
return err
}
if len(res) == 1 {
return nil
}
query := fmt.Sprintf("CREATE CONSTRAINT ON (a:%s) ASSERT a.version IS UNIQUE", n.config.MigrationsLabel)
if _, err := neo4j.Collect(session.Run(query, nil)); err != nil {
return err
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More