Repository: pressly/goose (language: go)

Issue:
Add CI tests to run migrations against a "real" postgres database
In GitHub Actions we can spin up containers, such as Postgres, MySQL, etc. and execute `goose` migrations against them.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 476cadfec..b23bb17c5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -29,5 +29,5 @@ jobs:
         run: |
           mkdir -p bin
           go vet ./...
-          go test -v ./...
           go build ./...
+          make test-packages
diff --git a/Makefile b/Makefile
index 690c6707a..347e279d6 100644
--- a/Makefile
+++ b/Makefile
@@ -16,3 +16,17 @@ vendor:
 	GO111MODULE=on go mod vendor && GO111MODULE=on go mod tidy
 	mv go.mod _go.mod
 	mv go.sum _go.sum
+
+test-packages:
+	go test -v $$(go list ./... | grep -v -e /tests -e /bin -e /cmd -e /examples)
+
+test-e2e: test-e2e-postgres test-e2e-mysql
+
+test-e2e-postgres:
+	go test -v ./tests/e2e -dialect=postgres
+
+test-e2e-mysql:
+	go test -v ./tests/e2e -dialect=mysql
+
+docker-cleanup:
+	docker stop -t=0 $$(docker ps --filter="label=goose_test" -aq)
diff --git a/go.mod b/go.mod
index b84214dd0..106b44851 100644
--- a/go.mod
+++ b/go.mod
@@ -3,11 +3,16 @@ module github.com/pressly/goose/v3
 go 1.16
 
 require (
+	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 	github.com/ClickHouse/clickhouse-go v1.4.5
+	github.com/containerd/continuity v0.2.0 // indirect
 	github.com/denisenkom/go-mssqldb v0.10.0
 	github.com/go-sql-driver/mysql v1.6.0
 	github.com/lib/pq v1.10.2
+	github.com/matryer/is v1.4.0
 	github.com/mattn/go-sqlite3 v1.14.8
+	github.com/ory/dockertest/v3 v3.8.0
 	github.com/pkg/errors v0.9.1
 	github.com/ziutek/mymysql v1.5.4
+	golang.org/x/net v0.0.0-20211008194852-3b03d305991f // indirect
 )
diff --git a/go.sum b/go.sum
index f2e89e0af..32705020f 100644
--- a/go.sum
+++ b/go.sum
@@ -1,36 +1,312 @@
+bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM=
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/ClickHouse/clickhouse-go v1.4.5 h1:FfhyEnv6/BaWldyjgT2k4gDDmeNwJ9C4NbY/MXxJlXk=
 github.com/ClickHouse/clickhouse-go v1.4.5/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
+github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU=
+github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
+github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
+github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
 github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=
 github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
+github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ=
+github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
+github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
 github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
-github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
+github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
+github.com/containerd/continuity v0.2.0 h1:j/9Wnn+hrEWjLvHuIxUU1YI5JjEjVlT2AA68cse9rwY=
+github.com/containerd/continuity v0.2.0/go.mod h1:wCYX+dRqZdImhGucXOqTQn05AhX6EUDaGEMUzTFFpLg=
+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
+github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waNNZfHBM8=
 github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/docker/cli v20.10.8+incompatible h1:/zO/6y9IOpcehE49yMRTV9ea0nBpb8OeqSskXLNfH1E=
+github.com/docker/cli v20.10.8+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
+github.com/docker/docker v20.10.7+incompatible h1:Z6O9Nhsjv+ayUEeI1IojKbYcsGdgYSNqxe1s2MYzUhQ=
+github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
+github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
+github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
+github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
+github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
+github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
+github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
 github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
 github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
+github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
+github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
+github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
+github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/opencontainers/runc v1.0.2 h1:opHZMaswlyxz1OuGpBE53Dwe4/xF7EZTY0A2L/FpCOg=
+github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0=
+github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
+github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
+github.com/ory/dockertest/v3 v3.8.0 h1:i5b0cJCd801qw0cVQUOH6dSpI9fT3j5tdWu0jKu90ks=
+github.com/ory/dockertest/v3 v3.8.0/go.mod h1:9zPATATlWQru+ynXP+DytBQrsXV7Tmlx7K86H6fQaDo=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
+github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
+github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
+github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
-golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20211008194852-3b03d305991f h1:1scJEYZBaF48BaG6tYbtxmLcXqwYGSfGcMoStTqkkIw=
+golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
+gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
+gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=


Test patch:
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
new file mode 100644
index 000000000..e2313da6f
--- /dev/null
+++ b/.github/workflows/e2e.yml
@@ -0,0 +1,27 @@
+name: Goose end-end tests
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+
+jobs:
+  test:
+    name: Run e2e tests
+    timeout-minutes: 10
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        dialect: ["postgres", "mysql"]
+
+    steps:
+      - name: Install Go
+        uses: actions/setup-go@v2
+        with:
+          go-version: "1.17"
+      - name: Checkout code
+        uses: actions/checkout@v2
+      - name: Run e2e ${{ matrix.dialect }} tests
+        run: |
+          make test-e2e-${{ matrix.dialect }}
diff --git a/tests/e2e/main_test.go b/tests/e2e/main_test.go
new file mode 100644
index 000000000..b94b0b8cf
--- /dev/null
+++ b/tests/e2e/main_test.go
@@ -0,0 +1,262 @@
+package e2e
+
+import (
+	"database/sql"
+	"flag"
+	"fmt"
+	"log"
+	"os"
+	"os/signal"
+	"path/filepath"
+	"strconv"
+	"syscall"
+	"testing"
+	"time"
+
+	_ "github.com/go-sql-driver/mysql"
+	_ "github.com/lib/pq"
+
+	"github.com/ory/dockertest/v3"
+	"github.com/ory/dockertest/v3/docker"
+)
+
+const (
+	dialectPostgres = "postgres"
+	dialectMySQL    = "mysql"
+)
+
+// Flags.
+var (
+	debug = flag.Bool(
+		"debug",
+		false,
+		"Debug traps the test suite: useful for debugging running containers",
+	)
+	dialect = flag.String(
+		"dialect",
+		dialectPostgres,
+		"Dialect defines which docker container to run tests against (default: postgres)",
+	)
+	// bindPort is useful if you want to pin a database port instead of relying
+	// on the randomly assigned one from Docker. It is mainly used for debugging
+	// locally and will normally be set to 0.
+	bindPort = flag.Int(
+		"port",
+		0,
+		"Port is an optional bind port. Left empty will let Docker assign a random port (recommended)",
+	)
+)
+
+var (
+	// migrationsDir is a global that points to a ./testdata/{dialect}/migrations folder.
+	// It is set in TestMain based on the current dialect.
+	migrationsDir = ""
+
+	// known tables are the tables (including goose table) created by
+	// running all migration files. If you add a table, make sure to
+	// add to this list and keep it in order.
+	knownTables = []string{
+		"goose_db_version",
+		"issues",
+		"owners",
+		"repos",
+		"stargazers",
+	}
+)
+
+func TestMain(m *testing.M) {
+	flag.Parse()
+
+	switch *dialect {
+	case dialectPostgres, dialectMySQL:
+	default:
+		log.Printf("dialect not supported: %q", *dialect)
+		os.Exit(1)
+	}
+	migrationsDir = filepath.Join("testdata", *dialect, "migrations")
+
+	exitCode := m.Run()
+	// Useful for debugging test services.
+	if *debug {
+		sigs := make(chan os.Signal, 1)
+		done := make(chan bool, 1)
+		signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
+		go func() {
+			<-sigs
+			done <- true
+		}()
+		log.Printf("entering debug mode: must exit (CTRL+C) and cleanup containers manually. Exit code: %d)", exitCode)
+		<-done
+	}
+	os.Exit(exitCode)
+}
+
+// newDockerDB starts a database container and returns a usable SQL connection.
+func newDockerDB(t *testing.T) (*sql.DB, error) {
+	switch *dialect {
+	case dialectPostgres:
+		return newDockerPostgresDB(t, *bindPort)
+	case dialectMySQL:
+		return newDockerMariaDB(t, *bindPort)
+	}
+	return nil, fmt.Errorf("unsupported dialect: %q", *dialect)
+}
+
+func newDockerPostgresDB(t *testing.T, bindPort int) (*sql.DB, error) {
+	const (
+		dbUsername = "postgres"
+		dbPassword = "password1"
+		dbHost     = "localhost"
+		dbName     = "testdb"
+	)
+	// Uses a sensible default on windows (tcp/http) and linux/osx (socket).
+	pool, err := dockertest.NewPool("")
+	if err != nil {
+		return nil, fmt.Errorf("failed to connect to docker: %v", err)
+	}
+	options := &dockertest.RunOptions{
+		Repository: "postgres",
+		Tag:        "14-alpine",
+		Env: []string{
+			"POSTGRES_USER=" + dbUsername,
+			"POSTGRES_PASSWORD=" + dbPassword,
+			"POSTGRES_DB=" + dbName,
+			"listen_addresses = '*'",
+		},
+		Labels:       map[string]string{"goose_test": "1"},
+		PortBindings: make(map[docker.Port][]docker.PortBinding),
+	}
+	if bindPort > 0 {
+		options.PortBindings[docker.Port("5432/tcp")] = []docker.PortBinding{
+			{HostPort: strconv.Itoa(bindPort)},
+		}
+	}
+
+	container, err := pool.RunWithOptions(
+		options,
+		func(config *docker.HostConfig) {
+			// Set AutoRemove to true so that stopped container goes away by itself.
+			config.AutoRemove = true
+			config.RestartPolicy = docker.RestartPolicy{Name: "no"}
+		},
+	)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create docker container: %v", err)
+	}
+	t.Cleanup(func() {
+		if *debug {
+			// User must manually delete the Docker container.
+			return
+		}
+		if err := pool.Purge(container); err != nil {
+			log.Printf("failed to purge resource: %v", err)
+		}
+	})
+	if err != nil {
+		return nil, fmt.Errorf("failed to start resource: %v", err)
+	}
+	psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
+		dbHost,
+		container.GetPort("5432/tcp"), // Fetch port dynamically assigned to container
+		dbUsername,
+		dbPassword,
+		dbName,
+	)
+	var db *sql.DB
+	// Exponential backoff-retry, because the application in the container
+	// might not be ready to accept connections yet.
+	if err := pool.Retry(
+		func() error {
+			var err error
+			db, err = sql.Open(dialectPostgres, psqlInfo)
+			if err != nil {
+				return err
+			}
+			return db.Ping()
+		},
+	); err != nil {
+		return nil, fmt.Errorf("could not connect to docker database: %v", err)
+	}
+	return db, nil
+}
+
+func newDockerMariaDB(t *testing.T, bindPort int) (*sql.DB, error) {
+	const (
+		dbUsername = "tester"
+		dbPassword = "password1"
+		dbHost     = "localhost"
+		dbName     = "testdb"
+	)
+	// Uses a sensible default on windows (tcp/http) and linux/osx (socket).
+	pool, err := dockertest.NewPool("")
+	if err != nil {
+		return nil, fmt.Errorf("failed to connect to docker: %v", err)
+	}
+	options := &dockertest.RunOptions{
+		Repository: "mariadb",
+		Tag:        "10",
+		Env: []string{
+			"MARIADB_USER=" + dbUsername,
+			"MARIADB_PASSWORD=" + dbPassword,
+			"MARIADB_ROOT_PASSWORD=" + dbPassword,
+			"MARIADB_DATABASE=" + dbName,
+		},
+		Labels: map[string]string{"goose_test": "1"},
+		// PortBindings: make(map[docker.Port][]docker.PortBinding),
+	}
+	if bindPort > 0 {
+		options.PortBindings[docker.Port("3306/tcp")] = []docker.PortBinding{
+			{HostPort: strconv.Itoa(bindPort)},
+		}
+	}
+
+	container, err := pool.RunWithOptions(
+		options,
+		func(config *docker.HostConfig) {
+			// Set AutoRemove to true so that stopped container goes away by itself.
+			config.AutoRemove = true
+			// config.PortBindings = options.PortBindings
+			config.RestartPolicy = docker.RestartPolicy{Name: "no"}
+		},
+	)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create docker container: %v", err)
+	}
+	t.Cleanup(func() {
+		if *debug {
+			// User must manually delete the Docker container.
+			return
+		}
+		if err := pool.Purge(container); err != nil {
+			log.Printf("failed to purge resource: %v", err)
+		}
+	})
+	if err != nil {
+		return nil, fmt.Errorf("failed to start resource: %v", err)
+	}
+	// MySQL DSN: username:password@protocol(address)/dbname?param=value
+	dsn := fmt.Sprintf("%s:%s@(%s:%s)/%s?parseTime=true&multiStatements=true",
+		dbUsername,
+		dbPassword,
+		dbHost,
+		container.GetPort("3306/tcp"), // Fetch port dynamically assigned to container
+		dbName,
+	)
+	var db *sql.DB
+	// Exponential backoff-retry, because the application in the container
+	// might not be ready to accept connections yet. Add an extra sleep
+	// because mariadb containers take much longer to startup.
+	time.Sleep(5 * time.Second)
+	if err := pool.Retry(func() error {
+		var err error
+		db, err = sql.Open(dialectMySQL, dsn)
+		if err != nil {
+			return err
+		}
+		return db.Ping()
+	},
+	); err != nil {
+		return nil, fmt.Errorf("could not connect to docker database: %v", err)
+	}
+	return db, nil
+}
diff --git a/tests/e2e/migrations_test.go b/tests/e2e/migrations_test.go
new file mode 100644
index 000000000..9c2b22148
--- /dev/null
+++ b/tests/e2e/migrations_test.go
@@ -0,0 +1,186 @@
+package e2e
+
+import (
+	"database/sql"
+	"fmt"
+	"path/filepath"
+	"testing"
+
+	"github.com/matryer/is"
+	"github.com/pressly/goose/v3"
+)
+
+func TestMigrateUp(t *testing.T) {
+	t.Parallel()
+	is := is.New(t)
+
+	db, err := newDockerDB(t)
+	is.NoErr(err)
+	goose.SetDialect(*dialect)
+	migrations, err := goose.CollectMigrations(migrationsDir, 0, goose.MaxVersion)
+	is.NoErr(err)
+	is.True(len(migrations) != 0)
+	// Migrate all
+	err = goose.Up(db, migrationsDir)
+	is.NoErr(err)
+	currentVersion, err := goose.GetDBVersion(db)
+	is.NoErr(err)
+	is.Equal(currentVersion, migrations[len(migrations)-1].Version)
+	// Validate the db migration version actually matches what goose claims it is
+	gotVersion, err := getCurrentGooseVersion(db, goose.TableName())
+	is.NoErr(err)
+	is.Equal(gotVersion, currentVersion) // incorrect database version
+}
+
+func TestMigrateUpTo(t *testing.T) {
+	t.Parallel()
+	is := is.New(t)
+
+	const (
+		upToVersion int64 = 2
+	)
+	db, err := newDockerDB(t)
+	is.NoErr(err)
+	goose.SetDialect(*dialect)
+	migrations, err := goose.CollectMigrations(migrationsDir, 0, goose.MaxVersion)
+	is.NoErr(err)
+	is.True(len(migrations) != 0)
+	// Migrate up to the second migration
+	err = goose.UpTo(db, migrationsDir, upToVersion)
+	is.NoErr(err)
+	// Fetch the goose version from DB
+	currentVersion, err := goose.GetDBVersion(db)
+	is.NoErr(err)
+	is.Equal(currentVersion, upToVersion)
+	// Validate the version actually matches what goose claims it is
+	gotVersion, err := getCurrentGooseVersion(db, goose.TableName())
+	is.NoErr(err)
+	is.Equal(gotVersion, upToVersion) // incorrect database version
+}
+
+func TestMigrateUpByOne(t *testing.T) {
+	t.Parallel()
+	is := is.New(t)
+
+	db, err := newDockerDB(t)
+	is.NoErr(err)
+	goose.SetDialect(*dialect)
+	migrations, err := goose.CollectMigrations(migrationsDir, 0, goose.MaxVersion)
+	is.NoErr(err)
+	is.True(len(migrations) != 0)
+	// Migrate up to the second migration
+	var counter int
+	for {
+		err := goose.UpByOne(db, migrationsDir)
+		counter++
+		if counter > len(migrations) {
+			is.Equal(err, goose.ErrNoNextVersion)
+			break
+		}
+		is.NoErr(err)
+	}
+	currentVersion, err := goose.GetDBVersion(db)
+	is.NoErr(err)
+	is.Equal(currentVersion, migrations[len(migrations)-1].Version)
+	// Validate the db migration version actually matches what goose claims it is
+	gotVersion, err := getCurrentGooseVersion(db, goose.TableName())
+	is.NoErr(err)
+	is.Equal(gotVersion, currentVersion) // incorrect database version
+}
+
+func TestMigrateFull(t *testing.T) {
+	t.Parallel()
+	is := is.New(t)
+
+	db, err := newDockerDB(t)
+	is.NoErr(err)
+	goose.SetDialect(*dialect)
+	migrations, err := goose.CollectMigrations(migrationsDir, 0, goose.MaxVersion)
+	is.NoErr(err)
+	is.True(len(migrations) != 0)
+	// test retrieving invalid current goose migrations. goose cannot return a migration
+	// if the supplied "current" version is non-existent or 0.
+	_, err = migrations.Current(20160813)
+	is.Equal(err, goose.ErrNoCurrentVersion)
+	_, err = migrations.Current(0)
+	is.Equal(err, goose.ErrNoCurrentVersion)
+	// verify the first migration1. This should not change if more migrations are added
+	// in the future.
+	migration1, err := migrations.Current(1)
+	is.NoErr(err)
+	is.Equal(migration1.Version, int64(1))
+	is.Equal(migration1.Source, filepath.Join(migrationsDir, "00001_a.sql"))
+	is.Equal(migration1.Registered, false) // expecting false for .sql migrations
+	is.Equal(migration1.Previous, int64(-1))
+	is.Equal(migration1.Next, int64(2))
+
+	{
+		// Apply all up migrations
+		err = goose.Up(db, migrationsDir)
+		is.NoErr(err)
+		currentVersion, err := goose.GetDBVersion(db)
+		is.NoErr(err)
+		is.Equal(currentVersion, migrations[len(migrations)-1].Version)
+		// Validate the db migration version actually matches what goose claims it is
+		gotVersion, err := getCurrentGooseVersion(db, goose.TableName())
+		is.NoErr(err)
+		is.Equal(gotVersion, currentVersion) // incorrect database version
+		tables, err := getTableNames(db)
+		is.NoErr(err)
+		is.Equal(tables, knownTables)
+	}
+	{
+		// Apply 1 down migration
+		err := goose.Down(db, migrationsDir)
+		is.NoErr(err)
+		gotVersion, err := getCurrentGooseVersion(db, goose.TableName())
+		is.NoErr(err)
+		is.Equal(gotVersion, migrations[len(migrations)-1].Version-1) // incorrect database version
+	}
+	{
+		// Migrate all remaining migrations down. Should only be left with a single table:
+		// the default goose table
+		err := goose.DownTo(db, migrationsDir, 0)
+		is.NoErr(err)
+		gotVersion, err := getCurrentGooseVersion(db, goose.TableName())
+		is.NoErr(err)
+		is.Equal(gotVersion, int64(0))
+		tables, err := getTableNames(db)
+		is.NoErr(err)
+		is.Equal(tables, []string{goose.TableName()})
+	}
+}
+
+func getCurrentGooseVersion(db *sql.DB, gooseTable string) (int64, error) {
+	var gotVersion int64
+	if err := db.QueryRow(
+		fmt.Sprintf("select max(version_id) from %s", gooseTable),
+	).Scan(&gotVersion); err != nil {
+		return 0, err
+	}
+	return gotVersion, nil
+}
+
+func getTableNames(db *sql.DB) ([]string, error) {
+	var query string
+	switch *dialect {
+	case dialectPostgres:
+		query = `SELECT table_name FROM information_schema.tables WHERE table_schema='public' ORDER BY table_name`
+	case dialectMySQL:
+		query = `SELECT table_name FROM INFORMATION_SCHEMA.tables WHERE TABLE_TYPE='BASE TABLE' ORDER BY table_name`
+	}
+	rows, err := db.Query(query)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+	var tableNames []string
+	for rows.Next() {
+		var name string
+		if err := rows.Scan(&name); err != nil {
+			return nil, err
+		}
+		tableNames = append(tableNames, name)
+	}
+	return tableNames, nil
+}
diff --git a/tests/e2e/testdata/mysql/migrations/00001_a.sql b/tests/e2e/testdata/mysql/migrations/00001_a.sql
new file mode 100644
index 000000000..007b73f75
--- /dev/null
+++ b/tests/e2e/testdata/mysql/migrations/00001_a.sql
@@ -0,0 +1,20 @@
+-- +goose Up
+-- +goose StatementBegin
+CREATE TABLE owners (
+    owner_id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    owner_name varchar(255) NOT NULL,
+    owner_type ENUM('user', 'organization') NOT NULL
+);
+CREATE TABLE repos (
+    repo_id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    repo_owner_id BIGINT NOT NULL,
+    repo_full_name VARCHAR(255) NOT NULL,
+    FOREIGN KEY (repo_owner_id) REFERENCES owners (owner_id) ON DELETE CASCADE
+);
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+DROP TABLE IF EXISTS repos;
+DROP TABLE IF EXISTS owners;
+-- +goose StatementEnd
\ No newline at end of file
diff --git a/tests/e2e/testdata/mysql/migrations/00002_b.sql b/tests/e2e/testdata/mysql/migrations/00002_b.sql
new file mode 100644
index 000000000..3e893a5de
--- /dev/null
+++ b/tests/e2e/testdata/mysql/migrations/00002_b.sql
@@ -0,0 +1,10 @@
+-- +goose Up
+-- +goose StatementBegin
+INSERT INTO owners(owner_id, owner_name, owner_type) 
+    VALUES (1, 'lucas', 'user'), (2, 'spacey', 'organization');
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+DELETE FROM owners WHERE owner_id IN (1, 2);
+-- +goose StatementEnd
diff --git a/tests/e2e/testdata/mysql/migrations/00003_c.sql b/tests/e2e/testdata/mysql/migrations/00003_c.sql
new file mode 100644
index 000000000..66fb6d02a
--- /dev/null
+++ b/tests/e2e/testdata/mysql/migrations/00003_c.sql
@@ -0,0 +1,13 @@
+-- +goose Up
+-- +goose StatementBegin
+INSERT INTO owners(owner_id, owner_name, owner_type) 
+    VALUES (3, 'james', 'user'), (4, 'pressly', 'organization');
+
+INSERT INTO repos(repo_id, repo_full_name, repo_owner_id) 
+    VALUES (1, 'james/rover', 3), (2, 'pressly/goose', 4);
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+DELETE FROM owners WHERE owner_name IN ('james', 'pressly');
+-- +goose StatementEnd
diff --git a/tests/e2e/testdata/mysql/migrations/00004_d.sql b/tests/e2e/testdata/mysql/migrations/00004_d.sql
new file mode 100644
index 000000000..f47d151a6
--- /dev/null
+++ b/tests/e2e/testdata/mysql/migrations/00004_d.sql
@@ -0,0 +1,13 @@
+-- +goose Up
+-- +goose StatementBegin
+ALTER TABLE repos 
+    ADD COLUMN IF NOT EXISTS homepage_url text NOT NULL DEFAULT '',
+    ADD COLUMN is_private boolean NOT NULL DEFAULT false;
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+ALTER TABLE repos
+    DROP COLUMN IF EXISTS homepage_url,
+    DROP COLUMN is_private;
+-- +goose StatementEnd
diff --git a/tests/e2e/testdata/mysql/migrations/00005_e.sql b/tests/e2e/testdata/mysql/migrations/00005_e.sql
new file mode 100644
index 000000000..96c766984
--- /dev/null
+++ b/tests/e2e/testdata/mysql/migrations/00005_e.sql
@@ -0,0 +1,11 @@
+-- +goose Up
+-- +goose StatementBegin
+-- NOTE: intentionally left blank to verify migration logic.
+SELECT 'up SQL query';
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+-- NOTE: intentionally left blank to verify migration logic.
+SELECT 'down SQL query';
+-- +goose StatementEnd
diff --git a/tests/e2e/testdata/mysql/migrations/00006_f.sql b/tests/e2e/testdata/mysql/migrations/00006_f.sql
new file mode 100644
index 000000000..634673651
--- /dev/null
+++ b/tests/e2e/testdata/mysql/migrations/00006_f.sql
@@ -0,0 +1,17 @@
+-- +goose Up
+-- +goose StatementBegin
+CREATE TABLE IF NOT EXISTS stargazers (
+    stargazer_repo_id bigint NOT NULL REFERENCES repos(repo_id) ON DELETE CASCADE,
+    stargazer_owner_id bigint NOT NULL REFERENCES owners(owner_id) ON DELETE CASCADE,
+    stargazer_starred_at timestamp NOT NULL,
+    stargazer_location text NOT NULL
+);
+
+ALTER TABLE IF EXISTS stargazers
+   ADD CONSTRAINT stargazers_repo_id_owner_id_key PRIMARY KEY (stargazer_repo_id, stargazer_owner_id);
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+DROP TABLE stargazers;
+-- +goose StatementEnd
diff --git a/tests/e2e/testdata/mysql/migrations/00007_g.sql b/tests/e2e/testdata/mysql/migrations/00007_g.sql
new file mode 100644
index 000000000..106ca876a
--- /dev/null
+++ b/tests/e2e/testdata/mysql/migrations/00007_g.sql
@@ -0,0 +1,15 @@
+-- +goose Up
+-- +goose StatementBegin
+CREATE TABLE issues (
+    issue_id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    issue_created_by bigint NOT NULL REFERENCES owners(owner_id) ON DELETE CASCADE,
+    issue_repo_id bigint NOT NULL REFERENCES repos(repo_id) ON DELETE CASCADE,
+    issue_created_at timestamp NOT NULL,
+    issue_description text NOT NULL
+);
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+DROP TABLE issues;
+-- +goose StatementEnd
diff --git a/tests/e2e/testdata/postgres/migrations/00001_a.sql b/tests/e2e/testdata/postgres/migrations/00001_a.sql
new file mode 100644
index 000000000..67637f7bb
--- /dev/null
+++ b/tests/e2e/testdata/postgres/migrations/00001_a.sql
@@ -0,0 +1,25 @@
+-- +goose Up
+-- +goose StatementBegin
+CREATE TYPE owner_type as ENUM('user', 'organization');
+
+CREATE TABLE owners (
+    owner_id BIGSERIAL PRIMARY KEY,
+    owner_name text NOT NULL,
+    owner_type owner_type NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS repos (
+    repo_id bigint UNIQUE NOT NULL,
+    repo_full_name text NOT NULL,
+    repo_owner_id bigint NOT NULL REFERENCES owners(owner_id) ON DELETE CASCADE,
+
+    PRIMARY KEY (repo_id)
+);
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+DROP TABLE IF EXISTS repos;
+DROP TABLE IF EXISTS owners;
+DROP TYPE owner_type;
+-- +goose StatementEnd
\ No newline at end of file
diff --git a/tests/e2e/testdata/postgres/migrations/00002_b.sql b/tests/e2e/testdata/postgres/migrations/00002_b.sql
new file mode 100644
index 000000000..3e893a5de
--- /dev/null
+++ b/tests/e2e/testdata/postgres/migrations/00002_b.sql
@@ -0,0 +1,10 @@
+-- +goose Up
+-- +goose StatementBegin
+INSERT INTO owners(owner_id, owner_name, owner_type) 
+    VALUES (1, 'lucas', 'user'), (2, 'spacey', 'organization');
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+DELETE FROM owners WHERE owner_id IN (1, 2);
+-- +goose StatementEnd
diff --git a/tests/e2e/testdata/postgres/migrations/00003_c.sql b/tests/e2e/testdata/postgres/migrations/00003_c.sql
new file mode 100644
index 000000000..66fb6d02a
--- /dev/null
+++ b/tests/e2e/testdata/postgres/migrations/00003_c.sql
@@ -0,0 +1,13 @@
+-- +goose Up
+-- +goose StatementBegin
+INSERT INTO owners(owner_id, owner_name, owner_type) 
+    VALUES (3, 'james', 'user'), (4, 'pressly', 'organization');
+
+INSERT INTO repos(repo_id, repo_full_name, repo_owner_id) 
+    VALUES (1, 'james/rover', 3), (2, 'pressly/goose', 4);
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+DELETE FROM owners WHERE owner_name IN ('james', 'pressly');
+-- +goose StatementEnd
diff --git a/tests/e2e/testdata/postgres/migrations/00004_d.sql b/tests/e2e/testdata/postgres/migrations/00004_d.sql
new file mode 100644
index 000000000..7d6bc8c52
--- /dev/null
+++ b/tests/e2e/testdata/postgres/migrations/00004_d.sql
@@ -0,0 +1,13 @@
+-- +goose Up
+-- +goose StatementBegin
+ALTER TABLE repos 
+    ADD COLUMN IF NOT EXISTS homepage_url text,
+    ADD COLUMN is_private boolean NOT NULL DEFAULT false;
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+ALTER TABLE repos
+    DROP COLUMN IF EXISTS homepage_url,
+    DROP COLUMN is_private;
+-- +goose StatementEnd
diff --git a/tests/e2e/testdata/postgres/migrations/00005_e.sql b/tests/e2e/testdata/postgres/migrations/00005_e.sql
new file mode 100644
index 000000000..96c766984
--- /dev/null
+++ b/tests/e2e/testdata/postgres/migrations/00005_e.sql
@@ -0,0 +1,11 @@
+-- +goose Up
+-- +goose StatementBegin
+-- NOTE: intentionally left blank to verify migration logic.
+SELECT 'up SQL query';
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+-- NOTE: intentionally left blank to verify migration logic.
+SELECT 'down SQL query';
+-- +goose StatementEnd
diff --git a/tests/e2e/testdata/postgres/migrations/00006_f.sql b/tests/e2e/testdata/postgres/migrations/00006_f.sql
new file mode 100644
index 000000000..91f15366c
--- /dev/null
+++ b/tests/e2e/testdata/postgres/migrations/00006_f.sql
@@ -0,0 +1,17 @@
+-- +goose Up
+-- +goose StatementBegin
+CREATE TABLE IF NOT EXISTS stargazers (
+    stargazer_repo_id bigint NOT NULL REFERENCES repos(repo_id) ON DELETE CASCADE DEFERRABLE,
+    stargazer_owner_id bigint NOT NULL REFERENCES owners(owner_id) ON DELETE CASCADE DEFERRABLE,
+    stargazer_starred_at timestamp NOT NULL,
+    stargazer_location text NOT NULL
+);
+
+ALTER TABLE IF EXISTS stargazers
+   ADD CONSTRAINT stargazers_repo_id_owner_id_key PRIMARY KEY (stargazer_repo_id, stargazer_owner_id);
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+DROP TABLE stargazers;
+-- +goose StatementEnd
diff --git a/tests/e2e/testdata/postgres/migrations/00007_g.sql b/tests/e2e/testdata/postgres/migrations/00007_g.sql
new file mode 100644
index 000000000..5daab67ed
--- /dev/null
+++ b/tests/e2e/testdata/postgres/migrations/00007_g.sql
@@ -0,0 +1,15 @@
+-- +goose Up
+-- +goose StatementBegin
+CREATE TABLE issues (
+    issue_id BIGSERIAL PRIMARY KEY,
+    issue_created_by bigint NOT NULL REFERENCES owners(owner_id) ON DELETE CASCADE DEFERRABLE,
+    issue_repo_id bigint NOT NULL REFERENCES repos(repo_id) ON DELETE CASCADE DEFERRABLE,
+    issue_created_at timestamp NOT NULL,
+    issue_description text NOT NULL
+);
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+DROP TABLE issues;
+-- +goose StatementEnd