diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 66eca2e..8419fe6 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -13,7 +13,7 @@ jobs: with: go-version: '1.17' - name: Run coverage - run: go test ./... -race -coverprofile=coverage.txt -covermode=atomic + run: make calculate_coverage - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 diff --git a/.github/workflows/mysql.yml b/.github/workflows/mysql.yml index 3b9da50..c2b5874 100644 --- a/.github/workflows/mysql.yml +++ b/.github/workflows/mysql.yml @@ -39,7 +39,7 @@ jobs: - name: Run Coverage env: LIBSCHEMA_MYSQL_TEST_DSN: "root:mysql@tcp(127.0.0.1:3306)/libschematest?tls=false" - run: go test -coverprofile=coverage.txt -covermode=atomic ./lsmysql/... + run: go test -coverprofile=coverage.txt -covermode=atomic -coverpkg=github.com/muir/libschema/... ./lsmysql/... - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 diff --git a/.github/workflows/pg.yml b/.github/workflows/pg.yml index a336b73..fa5dbd8 100644 --- a/.github/workflows/pg.yml +++ b/.github/workflows/pg.yml @@ -38,7 +38,7 @@ jobs: - name: Run Coverage env: LIBSCHEMA_POSTGRES_TEST_DSN: "postgres://postgres:postgres@localhost?sslmode=disable" - run: go test -coverprofile=coverage.txt -covermode=atomic ./lspostgres/... + run: go test -coverprofile=coverage.txt -covermode=atomic -coverpkg=github.com/muir/libschema/... ./lspostgres/... - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..10b56d3 --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ + + +all: + go install golang.org/x/tools/...@latest + go generate + go test + golangci-lint run + +coverageO: + go install github.com/eltorocorp/drygopher/drygopher@latest + -drygopher -s 80 + go tool cover -html=coverage.out + +calculate_coverage: + echo "mode: atomic" > coverage.txt + for d in $$(go list ./...); do \ + go test -race -covermode=atomic -coverprofile=profile.out -coverpkg=github.com/muir/libschema/... $$d; \ + if [ -f profile.out ]; then \ + grep -v ^mode profile.out >> coverage.txt; \ + rm profile.out; \ + fi; \ + done + +coverage: calculate_coverage + go tool cover -html=coverage.txt + +golanglint: + # binary will be $(go env GOPATH)/bin/golangci-lint + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin v1.45.2 + golangci-lint --version diff --git a/api.go b/api.go index d8c652c..f223eb7 100644 --- a/api.go +++ b/api.go @@ -177,10 +177,10 @@ func (s *Schema) NewDatabase(log MyLogger, name string, db *sql.DB, driver Drive return database, nil } -// Asyncrhronous marks a migration is okay to run asynchronously. If all of the +// Asynchronous marks a migration is okay to run asynchronously. If all of the // remaining migrations can be asynchronous, then schema.Migrate() will return // while the remaining migrations run. -func Asyncrhronous() MigrationOption { +func Asynchronous() MigrationOption { return func(m Migration) { m.Base().async = true } @@ -203,12 +203,24 @@ func After(lib, migration string) MigrationOption { } } +// SkipIf is checked before the migration is run. If the function returns true +// then this migration is skipped. For MySQL, this allows migrations +// that are not idempotent to be checked before they're run and skipped +// if they have already been applied. func SkipIf(pred func() (bool, error)) MigrationOption { return func(m Migration) { m.Base().skipIf = pred } } +// SkipRemainingIf is checked before the migration is run. If the function +// returns true then this migration and all following it are not run at this +// time. One use for this to hold back migrations that have not been released +// yet. For example, in a blue-green deploy organization, you could first +// do a migration that creates another column, then later do a migration that +// removes the old column. The migration to remove the old column can be defined +// and tested in advance but held back by SkipRemainingIf until it's time to +// deploy it. func SkipRemainingIf(pred func() (bool, error)) MigrationOption { return func(m Migration) { m.Base().skipRemainingIf = pred diff --git a/apply.go b/apply.go index aff5a44..2737cef 100644 --- a/apply.go +++ b/apply.go @@ -277,6 +277,7 @@ func (d *Database) lastUnfinishedSynchrnous() int { return -1 } +// allDone reports status func (d *Database) allDone(m Migration, err error) { if err != nil && m != nil { err = errors.Wrapf(err, "Migration %s", m.Base().Name) @@ -299,8 +300,15 @@ func (d *Database) allDone(m Migration, err error) { func (d *Database) asyncMigrate(ctx context.Context) { var err error var m Migration + d.log.Info("Starting async migrations") defer func() { + d.asyncInProgress = false + e := d.unlock() + if err == nil { + err = e + } d.allDone(m, err) + d.log.Info("Done with async migrations") }() for _, m = range d.sequence { if m.Base().Status().Done { diff --git a/dgorder/dependencies_test.go b/dgorder/dependencies_test.go index 90becde..3a874e7 100644 --- a/dgorder/dependencies_test.go +++ b/dgorder/dependencies_test.go @@ -1,7 +1,6 @@ package dgorder import ( - "fmt" "strconv" "testing" @@ -33,11 +32,34 @@ func TestDependenciesOkay(t *testing.T) { }, want: []int{1, 0, 3, 4, 2, 5}, }, + { + nodes: []Node{ + {}, + { + Blocking: []int{2}, + }, + { + Blocking: []int{1}, + }, + {}, + }, + want: nil, + err: "Circular dependency found that includes 1 depending upon 2", + }, + { + nodes: []Node{ + { + Blocking: []int{22}, + }, + }, + want: nil, + err: "Invalid dependency from 0 to 22", + }, } for _, tc := range cases { got, err := Order(tc.nodes, strconv.Itoa) if tc.err != "" { - assert.Equal(t, fmt.Errorf(tc.err), err) + assert.Equal(t, tc.err, err.Error()) } else { assert.NoError(t, err) assert.Equal(t, tc.want, got) diff --git a/driver.go b/driver.go index aa1bb75..4e7f1ea 100644 --- a/driver.go +++ b/driver.go @@ -6,14 +6,23 @@ import ( "strings" ) +// maps from DSN to driver name +var driverAliases = map[string]string{ + "postgresql": "postgres", +} + func OpenAnyDB(dsn string) (*sql.DB, error) { - for _, driver := range sql.Drivers() { - if strings.HasPrefix(dsn, driver+"://") { - return sql.Open(driver, dsn) - } - } if i := strings.Index(dsn, "://"); i != -1 { - return nil, fmt.Errorf("Could not find database driver matching %s", dsn[:i]) + wanted := dsn[0:i] + if alias, ok := driverAliases[wanted]; ok { + wanted = alias + } + for _, driver := range sql.Drivers() { + if driver == wanted { + return sql.Open(driver, dsn) + } + } + return nil, fmt.Errorf("Could not find database driver matching %s", wanted) } return nil, fmt.Errorf("Could not find appropriate database driver for DSN") } diff --git a/fake_test.go b/fake_test.go new file mode 100644 index 0000000..147231a --- /dev/null +++ b/fake_test.go @@ -0,0 +1,11 @@ +package libschema_test + +import( + "testing" +) + +// TestNothing exists so that there are at least some tests at the +// libschema level. Without any tests, test coverage reports are not +// generated. +func TestNothing(t *testing.T) { +} diff --git a/go.mod b/go.mod index 474e059..161956c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/muir/libschema go 1.16 require ( - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/eltorocorp/drygopher v1.1.2 github.com/go-sql-driver/mysql v1.6.0 github.com/lib/pq v1.10.5 github.com/muir/sqltoken v0.0.4 diff --git a/go.sum b/go.sum index 1f808aa..bc0689c 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,193 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +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/alvaroloes/enumer v1.1.2/go.mod h1:FxrjvuXoDAx9isTJrv4c+T410zFi0DtXIT0m65DJ+Wo= +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/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +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/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/eltorocorp/drygopher v1.1.2 h1:3gqKdGFg4RIDtAJjejLiroyVWV4CQyHE+dZ0p4thCzs= +github.com/eltorocorp/drygopher v1.1.2/go.mod h1:Rs3UoBz+rQdCARZJcOJHurSBG6aJa4NoL7oct/pTr/Q= +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.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/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/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/gonum/blas v0.0.0-20181208220705-f22b278b28ac h1:Q0Jsdxl5jbxouNs1TQYt0gxesYMU4VXRbsTlgDloZ50= +github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc= +github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82 h1:EvokxLQsaaQjcWVWSV38221VAK7qc2zhaO17bKys/18= +github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg= +github.com/gonum/integrate v0.0.0-20181209220457-a422b5c0fdf2 h1:GUSkTcIe1SlregbHNUKbYDhBsS8lNgYfIp4S4cToUyU= +github.com/gonum/integrate v0.0.0-20181209220457-a422b5c0fdf2/go.mod h1:pDgmNM6seYpwvPos3q+zxlXMsbve6mOIPucUnUOrI7Y= +github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 h1:8jtTdc+Nfj9AR+0soOeia9UZSvYBvETVHZrugUowJ7M= +github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks= +github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9 h1:7qnwS9+oeSiOIsiUMajT+0R7HR6hw5NegnKPmn/94oI= +github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A= +github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 h1:V2IgdyerlBa/MxaEFRbV5juy/C3MGdj4ePi+g6ePIp4= +github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw= +github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b h1:fbskpz/cPqWH8VqkQ7LJghFkl2KPAiIFUHrTJ2O3RGk= +github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b/go.mod h1:Z4GIJBJO3Wa4gD4vbwQxXXZ+WHmW6E9ixmNrwvs0iZs= +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/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/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +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/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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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 v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ= github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +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/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/muir/sqltoken v0.0.4 h1:SioNnG90ZYXmlfnPaUxUdNC1dFkhKL64pDeS+wXZ8k8= github.com/muir/sqltoken v0.0.4/go.mod h1:6hPsZxszMpYyNf12og4f4VShFo/Qipz6Of0cn5KGAAU= github.com/muir/testinglogur v0.0.1 h1:k0lztrKzttiH5Pjtzj7S4tXXXBgUaxqTtVKXK4ndiI8= github.com/muir/testinglogur v0.0.1/go.mod h1:18iL5fVrQ2hu0NeXKtEE9pS5jgdaNTgqWHNl+p33g6M= +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/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1/go.mod h1:eD5JxqMiuNYyFNmyY9rkJ/slN8y59oEu4Ei7F8OoKWQ= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/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/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/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 v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU= +github.com/spf13/cobra v0.0.7/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 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +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/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/vektra/mockery v0.0.0-20181123154057-e78b021dcbb5/go.mod h1:ppEjwdhyy7Y31EnHRDm1JkChoC7LXIJ7Ex0VYLWtZtQ= +github.com/willf/pad v0.0.0-20200313202418-172aa767f2a4 h1:Y+IMUhhlO9FLTZpNrUAMWOr7Lh0tHDKu0nrDVhp6A7o= +github.com/willf/pad v0.0.0-20200313202418-172aa767f2a4/go.mod h1:+pVHwmjc9CH7ugBFxESIwQkXkVj0gUj4cFp63TLwP1Y= +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.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +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/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/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/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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-20181112210238-4b1f3b6b1646/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-20190524210228-3d17549cdc6b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200327195553-82bb89366a1e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +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= +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= +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.2.8/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= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/lspostgres/async_test.go b/lspostgres/async_test.go new file mode 100644 index 0000000..e5c81b8 --- /dev/null +++ b/lspostgres/async_test.go @@ -0,0 +1,108 @@ +package lspostgres_test + +import ( + "context" + "database/sql" + "os" + "testing" + "time" + + "github.com/muir/libschema" + "github.com/muir/libschema/lspostgres" + "github.com/muir/libschema/lstesting" + "github.com/muir/testinglogur" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAsyncMigrations(t *testing.T) { + dsn := os.Getenv("LIBSCHEMA_POSTGRES_TEST_DSN") + if dsn == "" { + t.Skip("Set $LIBSCHEMA_POSTGRES_TEST_DSN to test libschema/lspostgres") + } + + migrationComplete := make(chan struct{}) + migrateReturned := make(chan struct{}) + m3 := make(chan struct{}) + + options, cleanup := lstesting.FakeSchema(t, "CASCADE") + options.ErrorOnUnknownMigrations = true + options.OnMigrationsComplete = func(err error) { + assert.NoError(t, err, "completion error") + time.Sleep(10 * time.Millisecond) + close(migrationComplete) + t.Log("All migrations are complete (signaled)") + } + + db, err := sql.Open("postgres", dsn) + require.NoError(t, err, "open database") + defer func() { + t.Log("Closing db...") + db.Close() + t.Log("done") + }() + defer func() { + t.Log("Doing cleanup...") + cleanup(db) + t.Log("done") + }() + + s := libschema.New(context.Background(), options) + dbase, err := lspostgres.New(testinglogur.Get(t), "test", s, db) + require.NoError(t, err, "libschema NewDatabase") + + t.Log("define three migrations, two are async") + dbase.Migrations("L2", + lspostgres.Generate("M1", func(_ context.Context, _ libschema.MyLogger, _ *sql.Tx) string { + t.Log("migration M1 running") + return `CREATE TABLE M1 (id text)` + }), + lspostgres.Generate("M2", func(_ context.Context, _ libschema.MyLogger, _ *sql.Tx) string { + t.Log("migration M2 waiting migration complete signal") + nt := time.NewTimer(time.Second) + select { + case <-migrationComplete: + assert.FailNow(t, "early complete", "early complete") + case <-migrateReturned: + case <-nt.C: + assert.FailNow(t, "timeout", "timeout") + } + nt.Stop() + t.Log("migration M2 running") + return `CREATE TABLE M2 (id text)` + }, libschema.Asynchronous()), + lspostgres.Generate("M3", func(_ context.Context, _ libschema.MyLogger, _ *sql.Tx) string { + t.Log("migration M3 running") + close(m3) + return `CREATE TABLE M3 (id text);` + }, libschema.Asynchronous()), + ) + + t.Log("Starting migrations") + err = s.Migrate(context.Background()) + assert.NoError(t, err) + t.Log("Synchronous migrations complete") + time.Sleep(10 * time.Millisecond) + t.Log("Signalling that the migrate command returned") + close(migrateReturned) + + t.Log("Waiting for migration M3 to be done") + nt := time.NewTimer(time.Second) + select { + case <-m3: + case <-migrationComplete: + assert.FailNow(t, "early complete 2", "early complete 2") + case <-nt.C: + assert.FailNow(t, "timeout2", "timeout2") + } + nt.Reset(time.Second) + + t.Log("Waiting for the migration complete signal") + select { + case <-migrationComplete: + case <-nt.C: + assert.FailNow(t, "timeout2", "timeout2") + } + nt.Stop() + t.Log("All done!") +} diff --git a/lspostgres/bad_test.go b/lspostgres/bad_test.go new file mode 100644 index 0000000..51c7cb6 --- /dev/null +++ b/lspostgres/bad_test.go @@ -0,0 +1,76 @@ +package lspostgres_test + +import ( + "context" + "os" + "testing" + + "github.com/muir/libschema" + "github.com/muir/libschema/lsmysql" + "github.com/muir/libschema/lspostgres" + "github.com/muir/libschema/lstesting" + "github.com/muir/testinglogur" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBadMigrations(t *testing.T) { + cases := []struct { + name string + error string + define func(*libschema.Database) + }{ + { + name: "table missing", + error: `relation "t1" does not exist`, + define: func(dbase *libschema.Database) { + dbase.Migrations("L2", + lspostgres.Script("T4", `INSERT INTO T1 (id) VALUES ('T4')`), + ) + }, + }, + { + name: "table missing", + error: `Non-postgres`, + define: func(dbase *libschema.Database) { + dbase.Migrations("L2", + lsmysql.Script("T4", `INSERT INTO T1 (id) VALUES ('T4')`), + ) + }, + }, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + testBadMigration(t, tc.error, tc.define) + }) + } +} + +func testBadMigration(t *testing.T, expected string, define func(*libschema.Database)) { + dsn := os.Getenv("LIBSCHEMA_POSTGRES_TEST_DSN") + if dsn == "" { + t.Skip("Set $LIBSCHEMA_POSTGRES_TEST_DSN to test libschema/lspostgres") + } + + options, cleanup := lstesting.FakeSchema(t, "CASCADE") + options.DebugLogging = true + + db, err := libschema.OpenAnyDB(dsn) + require.NoError(t, err, "open database") + defer db.Close() + defer cleanup(db) + + s := libschema.New(context.Background(), options) + dbase, err := lspostgres.New(testinglogur.Get(t), "test", s, db) + require.NoError(t, err, "libschema NewDatabase") + + t.Log("now we define the migrations") + define(dbase) + + err = s.Migrate(context.Background()) + assert.Error(t, err, "should error") + + assert.Contains(t, err.Error(), expected) +} diff --git a/lspostgres/postgres_test.go b/lspostgres/postgres_test.go index 2eee03c..87762b8 100644 --- a/lspostgres/postgres_test.go +++ b/lspostgres/postgres_test.go @@ -91,6 +91,7 @@ func TestPostgresMigrations(t *testing.T) { ` }), } + if extra { l1migrations = append(l1migrations, lspostgres.Generate("G1", func(_ context.Context, _ libschema.MyLogger, _ *sql.Tx) string { @@ -117,6 +118,16 @@ func TestPostgresMigrations(t *testing.T) { INSERT INTO T3 (id) VALUES ('T4'); CREATE TABLE T4 (id text)` }), + lspostgres.Generate("G2", func(_ context.Context, _ libschema.MyLogger, _ *sql.Tx) string { + actions = append(actions, "MIGRATE: G2") + return `CREATE TABLE G2 (id text);` + }, libschema.SkipRemainingIf(func() (bool, error) { + return !extra, nil + })), + lspostgres.Generate("G3", func(_ context.Context, _ libschema.MyLogger, _ *sql.Tx) string { + actions = append(actions, "MIGRATE: G3") + return `CREATE TABLE G3 (id text);` + }), ) } @@ -167,6 +178,8 @@ func TestPostgresMigrations(t *testing.T) { assert.Equal(t, []string{ "START", "MIGRATE: G1", + "MIGRATE: G2", + "MIGRATE: G3", "COMPLETE", }, actions) }