diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 665c917..a3cd7d5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,7 +13,7 @@ jobs: strategy: matrix: - go-version: ["1.19", "1.20", "1.21"] + go-version: ["1.20", "1.21"] os: [ubuntu-latest] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index e76edf1..8d0c08e 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -14,7 +14,7 @@ jobs: strategy: max-parallel: 2 matrix: - dialect: ["postgres", "mysql", "vertica", "clickhouse", "ydb"] + dialect: ["postgres", "mysql", "vertica", "clickhouse", "ydb", "turso"] steps: - name: Checkout code diff --git a/Makefile b/Makefile index 08e6187..497ba4b 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ DB_POSTGRES_PORT ?= 5433 DB_MYSQL_PORT ?= 3307 DB_CLICKHOUSE_PORT ?= 9001 DB_YDB_PORT ?= 2136 +DB_TURSO_PORT ?= 8080 .PHONY: dist dist: @@ -37,7 +38,7 @@ test-packages: test-packages-short: go test -test.short $(GO_TEST_FLAGS) $$(go list ./... | grep -v -e /tests -e /bin -e /cmd -e /examples) -test-e2e: test-e2e-postgres test-e2e-mysql test-e2e-clickhouse test-e2e-vertica test-e2e-ydb +test-e2e: test-e2e-postgres test-e2e-mysql test-e2e-clickhouse test-e2e-vertica test-e2e-ydb test-e2e-turso test-e2e-postgres: go test $(GO_TEST_FLAGS) ./tests/e2e -dialect=postgres @@ -54,6 +55,9 @@ test-e2e-vertica: test-e2e-ydb: go test $(GO_TEST_FLAGS) -parallel=1 ./tests/e2e -dialect=ydb +test-e2e-turso: + go test $(GO_TEST_FLAGS) -parallel=1 ./tests/e2e -dialect=turso + docker-cleanup: docker stop -t=0 $$(docker ps --filter="label=goose_test" -aq) @@ -85,3 +89,9 @@ docker-clickhouse: -p $(DB_CLICKHOUSE_PORT):9000/tcp \ -l goose_test \ clickhouse/clickhouse-server:23-alpine + +docker-turso: + docker run --rm -d \ + -p $(DB_TURSO_PORT):8080 \ + -l goose_test \ + ghcr.io/tursodatabase/libsql-server:v0.22.10 \ No newline at end of file diff --git a/cmd/goose/driver_turso.go b/cmd/goose/driver_turso.go new file mode 100644 index 0000000..8093009 --- /dev/null +++ b/cmd/goose/driver_turso.go @@ -0,0 +1,5 @@ +package main + +import ( + _ "github.com/libsql/libsql-client-go/libsql" +) diff --git a/cmd/goose/main.go b/cmd/goose/main.go index c16ef3c..35e4c76 100644 --- a/cmd/goose/main.go +++ b/cmd/goose/main.go @@ -214,6 +214,7 @@ Drivers: clickhouse vertica ydb + turso Examples: goose sqlite3 ./foo.db status @@ -230,12 +231,14 @@ Examples: goose clickhouse "tcp://127.0.0.1:9000" status goose vertica "vertica://user:password@localhost:5433/dbname?connection_load_balance=1" status goose ydb "grpcs://localhost:2135/local?go_query_mode=scripting&go_fake_tx=scripting&go_query_bind=declare,numeric" status + goose turso "libsql://dbname.turso.io?authToken=token" status GOOSE_DRIVER=sqlite3 GOOSE_DBSTRING=./foo.db goose status GOOSE_DRIVER=sqlite3 GOOSE_DBSTRING=./foo.db goose create init sql GOOSE_DRIVER=postgres GOOSE_DBSTRING="user=postgres dbname=postgres sslmode=disable" goose status GOOSE_DRIVER=mysql GOOSE_DBSTRING="user:password@/dbname" goose status GOOSE_DRIVER=redshift GOOSE_DBSTRING="postgres://user:password@qwerty.us-east-1.redshift.amazonaws.com:5439/db" goose status + GOOSE_DRIVER=turso GOOSE_DBSTRING="libsql://dbname.turso.io?authToken=token" goose status Options: ` diff --git a/database/dialect.go b/database/dialect.go index 2e502f5..c86b8d1 100644 --- a/database/dialect.go +++ b/database/dialect.go @@ -22,6 +22,7 @@ const ( DialectTiDB Dialect = "tidb" DialectVertica Dialect = "vertica" DialectYdB Dialect = "ydb" + DialectTurso Dialect = "turso" ) // NewStore returns a new [Store] implementation for the given dialect. @@ -42,6 +43,7 @@ func NewStore(dialect Dialect, tablename string) (Store, error) { DialectTiDB: &dialectquery.Tidb{}, DialectVertica: &dialectquery.Vertica{}, DialectYdB: &dialectquery.Ydb{}, + DialectTurso: &dialectquery.Turso{}, } querier, ok := lookup[dialect] if !ok { diff --git a/db.go b/db.go index 8f43f59..95e73dd 100644 --- a/db.go +++ b/db.go @@ -17,10 +17,12 @@ func OpenDBWithDriver(driver string, dbstring string) (*sql.DB, error) { driver = "sqlserver" case "tidb": driver = "mysql" + case "turso": + driver = "libsql" } switch driver { - case "postgres", "pgx", "sqlite3", "sqlite", "mysql", "sqlserver", "clickhouse", "vertica", "azuresql", "ydb": + case "postgres", "pgx", "sqlite3", "sqlite", "mysql", "sqlserver", "clickhouse", "vertica", "azuresql", "ydb", "libsql": return sql.Open(driver, dbstring) default: return nil, fmt.Errorf("unsupported driver %s", driver) diff --git a/dialect.go b/dialect.go index 7f4b669..3454c5f 100644 --- a/dialect.go +++ b/dialect.go @@ -50,6 +50,8 @@ func SetDialect(s string) error { d = dialect.Vertica case "ydb": d = dialect.Ydb + case "turso": + d = dialect.Turso default: return fmt.Errorf("%q: unknown dialect", s) } diff --git a/go.mod b/go.mod index 702633c..1544632 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,12 @@ module github.com/pressly/goose/v3 -go 1.19 +go 1.20 require ( github.com/ClickHouse/clickhouse-go/v2 v2.16.0 github.com/go-sql-driver/mysql v1.7.1 github.com/jackc/pgx/v5 v5.5.1 + github.com/libsql/libsql-client-go v0.0.0-20231215151033-bc1f2dc4de81 github.com/microsoft/go-mssqldb v1.6.0 github.com/ory/dockertest/v3 v3.10.0 github.com/sethvargo/go-retry v0.2.4 @@ -23,6 +24,7 @@ require ( github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/andybalholm/brotli v1.0.6 // indirect + github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/containerd/continuity v0.4.3 // indirect github.com/docker/cli v24.0.7+incompatible // indirect @@ -49,6 +51,7 @@ require ( github.com/jonboulle/clockwork v0.4.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/compress v1.17.2 // indirect + github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/term v0.5.0 // indirect @@ -70,6 +73,7 @@ require ( go.opentelemetry.io/otel v1.20.0 // indirect go.opentelemetry.io/otel/trace v1.20.0 // indirect golang.org/x/crypto v0.16.0 // indirect + golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/sys v0.15.0 // indirect @@ -90,6 +94,7 @@ require ( modernc.org/opt v0.1.3 // indirect modernc.org/strutil v1.2.0 // indirect modernc.org/token v1.1.0 // indirect + nhooyr.io/websocket v1.8.7 // indirect ) retract ( diff --git a/go.sum b/go.sum index 891c534..74acd80 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8 github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 h1:goHVqTbFX3AIo0tzGr14pgfAW2ZfPChKO21Z9MGf/gk= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -61,12 +63,29 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI= github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= @@ -82,6 +101,7 @@ github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +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= @@ -104,12 +124,15 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= 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/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= @@ -126,10 +149,13 @@ github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8 github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 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/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= @@ -139,7 +165,14 @@ 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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2 h1:hRGSmZu7j271trc9sneMrpOW7GN5ngLm8YUZIPzf394= +github.com/libsql/libsql-client-go v0.0.0-20231215151033-bc1f2dc4de81 h1:TIug2pPf5i9P79B+gTgzy5CirMBbm8wv6JNPhvRpLmQ= +github.com/libsql/libsql-client-go v0.0.0-20231215151033-bc1f2dc4de81/go.mod h1:DwtnnuteYyHTmkYnWDXZ+FSijmHzrEO/Um07WK0gT5M= +github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475 h1:6PfEMwfInASh9hkN83aR0j4W/eKaAZt/AURtXAXlas0= +github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475/go.mod h1:20nXSmcf0nAscrzqsXeC2/tA3KkV2eCiJqYuyAgl+ss= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= @@ -149,6 +182,10 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -187,11 +224,16 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw= github.com/vertica/vertica-sql-go v1.3.3/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= @@ -228,6 +270,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= +golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -263,6 +307,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h 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-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/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= @@ -277,11 +322,13 @@ golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -335,6 +382,7 @@ gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa 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.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -370,3 +418,5 @@ modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= +nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= +nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= diff --git a/internal/dialect/dialectquery/turso.go b/internal/dialect/dialectquery/turso.go new file mode 100644 index 0000000..ced0f5d --- /dev/null +++ b/internal/dialect/dialectquery/turso.go @@ -0,0 +1,7 @@ +package dialectquery + +type Turso struct { + Sqlite3 +} + +var _ Querier = (*Turso)(nil) diff --git a/internal/dialect/dialects.go b/internal/dialect/dialects.go index 1024f41..6ab06e5 100644 --- a/internal/dialect/dialects.go +++ b/internal/dialect/dialects.go @@ -13,4 +13,5 @@ const ( Clickhouse Dialect = "clickhouse" Vertica Dialect = "vertica" Ydb Dialect = "ydb" + Turso Dialect = "turso" ) diff --git a/internal/dialect/store.go b/internal/dialect/store.go index 4125ef4..f698fd3 100644 --- a/internal/dialect/store.go +++ b/internal/dialect/store.go @@ -67,6 +67,8 @@ func NewStore(d Dialect) (Store, error) { querier = &dialectquery.Vertica{} case Ydb: querier = &dialectquery.Ydb{} + case Turso: + querier = &dialectquery.Turso{} default: return nil, fmt.Errorf("unknown querier dialect: %v", d) } diff --git a/internal/testdb/turso.go b/internal/testdb/turso.go new file mode 100644 index 0000000..ebff0a3 --- /dev/null +++ b/internal/testdb/turso.go @@ -0,0 +1,99 @@ +package testdb + +import ( + "database/sql" + "fmt" + "log" + "strconv" + + _ "github.com/libsql/libsql-client-go/libsql" + "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" +) + +const ( + // ghcr.io/tursodatabase/libsql-server:v0.22.10 + TURSO_IMAGE = "ghcr.io/tursodatabase/libsql-server" + TURSO_VERSION = "v0.22.10" + TURSO_PORT = "8080" +) + +// NewTurso starts a Turso docker container. Returns db connection and a docker cleanup function. +func NewTurso(options ...OptionsFunc) (db *sql.DB, cleanup func(), err error) { + return newTurso(options...) +} + +func newTurso(opts ...OptionsFunc) (*sql.DB, func(), error) { + option := &options{} + for _, f := range opts { + f(option) + } + // Uses a sensible default on windows (tcp/http) and linux/osx (socket). + pool, err := dockertest.NewPool("") + if err != nil { + return nil, nil, err + } + runOptions := &dockertest.RunOptions{ + Repository: TURSO_IMAGE, + Tag: TURSO_VERSION, + Labels: map[string]string{"goose_test": "1"}, + PortBindings: make(map[docker.Port][]docker.PortBinding), + } + if option.debug { + runOptions.Env = append(runOptions.Env, "RUST=trace") + } else { + runOptions.Env = append(runOptions.Env, "RUST=error") + } + if option.bindPort > 0 { + runOptions.PortBindings[TURSO_PORT+"/tcp"] = []docker.PortBinding{ + {HostPort: strconv.Itoa(option.bindPort)}, + } + } + container, err := pool.RunWithOptions( + runOptions, + 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, nil, err + } + cleanup := func() { + if option.debug { + // User must manually delete the Docker container. + return + } + if err := pool.Purge(container); err != nil { + log.Printf("failed to purge resource: %v", err) + } + } + // Fetch port assigned to container + + 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 { + db, err = tursoOpenDB(container) + return err + }); err != nil { + return nil, cleanup, fmt.Errorf("could not connect to docker database: %w", err) + } + return db, cleanup, nil +} + +func tursoOpenDB(container *dockertest.Resource) (*sql.DB, error) { + address := fmt.Sprintf("http://127.0.0.1:%s", container.GetPort(TURSO_PORT+"/tcp")) + db, err := sql.Open("libsql", address) + if err != nil { + return db, err + } + // let's do a ping to be sure we are connected + var result int + err = db.QueryRow("SELECT 1").Scan(&result) + if err != nil { + return nil, err + } + return db, nil +} diff --git a/tests/e2e/main_test.go b/tests/e2e/main_test.go index 646fa42..22c1a97 100644 --- a/tests/e2e/main_test.go +++ b/tests/e2e/main_test.go @@ -20,6 +20,7 @@ const ( dialectPostgres = "postgres" dialectMySQL = "mysql" dialectYdb = "ydb" + dialectTurso = "turso" ) // Flags. @@ -67,7 +68,7 @@ func TestMain(m *testing.M) { flag.Parse() switch *dialect { - case dialectPostgres, dialectMySQL, dialectYdb: + case dialectPostgres, dialectMySQL, dialectYdb, dialectTurso: default: log.Printf("dialect not supported: %q", *dialect) os.Exit(1) @@ -114,6 +115,8 @@ func newDockerDB(t *testing.T) (*sql.DB, error) { db, cleanup, err = testdb.NewMariaDB(options...) case dialectYdb: db, cleanup, err = testdb.NewYdb(options...) + case dialectTurso: + db, cleanup, err = testdb.NewTurso(options...) default: return nil, fmt.Errorf("unsupported dialect: %q", *dialect) } diff --git a/tests/e2e/migrations_test.go b/tests/e2e/migrations_test.go index 882d2e3..f013cbf 100644 --- a/tests/e2e/migrations_test.go +++ b/tests/e2e/migrations_test.go @@ -365,6 +365,8 @@ func getTableNames(db *sql.DB) (tableNames []string, _ error) { return nil, err } return tableNames, nil + case dialectTurso: + return getTableNamesThroughQuery(db, `SELECT NAME FROM sqlite_master where type='table' and name!='sqlite_sequence' ORDER BY NAME;`) default: return nil, fmt.Errorf("getTableNames not supported with dialect %q", *dialect) } diff --git a/tests/e2e/testdata/turso/migrations/00001_a.sql b/tests/e2e/testdata/turso/migrations/00001_a.sql new file mode 100644 index 0000000..537a18a --- /dev/null +++ b/tests/e2e/testdata/turso/migrations/00001_a.sql @@ -0,0 +1,21 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE owners ( + owner_id integer, + owner_name integer, + owner_type integer, + PRIMARY KEY (owner_id) +); +CREATE TABLE repos ( + repo_id integer, + repo_owner_id integer, + repo_full_name integer, + PRIMARY KEY (repo_id) +); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE repos; +DROP TABLE owners; +-- +goose StatementEnd diff --git a/tests/e2e/testdata/turso/migrations/00002_b.sql b/tests/e2e/testdata/turso/migrations/00002_b.sql new file mode 100644 index 0000000..598c33b --- /dev/null +++ b/tests/e2e/testdata/turso/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, 'space', 'organization'); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DELETE FROM owners; +-- +goose StatementEnd diff --git a/tests/e2e/testdata/turso/migrations/00003_c.sql b/tests/e2e/testdata/turso/migrations/00003_c.sql new file mode 100644 index 0000000..ae622a2 --- /dev/null +++ b/tests/e2e/testdata/turso/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_id = 3 OR owner_id = 4); +DELETE FROM repos WHERE (repo_id = 1 OR repo_id = 2); +-- +goose StatementEnd diff --git a/tests/e2e/testdata/turso/migrations/00004_d.sql b/tests/e2e/testdata/turso/migrations/00004_d.sql new file mode 100644 index 0000000..42478eb --- /dev/null +++ b/tests/e2e/testdata/turso/migrations/00004_d.sql @@ -0,0 +1,15 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE repos + ADD COLUMN homepage_url text; +ALTER TABLE repos + ADD COLUMN is_private integer; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE repos + DROP COLUMN homepage_url; +ALTER TABLE repos + DROP COLUMN is_private; +-- +goose StatementEnd diff --git a/tests/e2e/testdata/turso/migrations/00005_e.sql b/tests/e2e/testdata/turso/migrations/00005_e.sql new file mode 100644 index 0000000..96c7669 --- /dev/null +++ b/tests/e2e/testdata/turso/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/turso/migrations/00006_f.sql b/tests/e2e/testdata/turso/migrations/00006_f.sql new file mode 100644 index 0000000..ed6a1a8 --- /dev/null +++ b/tests/e2e/testdata/turso/migrations/00006_f.sql @@ -0,0 +1,15 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE stargazers ( + stargazer_repo_id integer, + stargazer_owner_id integer, + stargazer_starred_at integer, + stargazer_location text, + 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/turso/migrations/00007_g.sql b/tests/e2e/testdata/turso/migrations/00007_g.sql new file mode 100644 index 0000000..aff8d39 --- /dev/null +++ b/tests/e2e/testdata/turso/migrations/00007_g.sql @@ -0,0 +1,16 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE issues ( + issue_id integer, + issue_created_by integer, + issue_repo_id integer, + issue_created_at integer, + issue_description text, + PRIMARY KEY (issue_id) +); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE issues; +-- +goose StatementEnd diff --git a/tests/e2e/testdata/turso/migrations/00008_h.sql b/tests/e2e/testdata/turso/migrations/00008_h.sql new file mode 100644 index 0000000..754cb1d --- /dev/null +++ b/tests/e2e/testdata/turso/migrations/00008_h.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE stargazers DROP COLUMN stargazer_location; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE stargazers ADD COLUMN stargazer_location text; +-- +goose StatementEnd