diff --git a/.github/workflows/golang.yml b/.github/workflows/golang.yml new file mode 100644 index 00000000..e3462414 --- /dev/null +++ b/.github/workflows/golang.yml @@ -0,0 +1,111 @@ +name: golang +on: + push: + branches: + - master + pull_request: +jobs: + test: + strategy: + fail-fast: true + matrix: + go-version: [ '1.13', '1.14', '1.15', '1.16', '1.17', '1.18', '1.19', '1.20'] + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v3 + - name: setup-go + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + cache: true + cache-dependency-path: go.sum + - name: go build + run: go build -o ./bin/sql-migrate ./sql-migrate && ./bin/sql-migrate --help + - name: go test + run: go test ./... + lint: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v3 + - name: setup-go + uses: actions/setup-go@v4 + with: + go-version: '1.20' + cache: true + cache-dependency-path: go.sum + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.52.2 + - name: go mod tidy + run: go mod tidy + - name: check for any changes + run: | + [[ $(git status --porcelain) == "" ]] || (echo "changes detected" && exit 1) + integration: + needs: + - test + - lint + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + go-version: [ '1.13', '1.14', '1.15', '1.16', '1.17', '1.18', '1.19', '1.20'] + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: '1' + MYSQL_ROOT_PASSWORD: '' + MYSQL_DATABASE: 'test' + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + postgres: + image: postgres:15 + env: + POSTGRES_PASSWORD: 'password' + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: + MYSQL_HOST: '127.0.0.1' + PGHOST: '127.0.0.1' + PGUSER: 'postgres' + PGPASSWORD: 'password' + steps: + - name: checkout + uses: actions/checkout@v3 + - name: setup-go + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + cache: true + cache-dependency-path: go.sum + - name: setup databases + run: | + mysql --user=root -e 'CREATE DATABASE IF NOT EXISTS test;' + mysql --user=root -e 'CREATE DATABASE IF NOT EXISTS test_env;' + psql -U postgres -c 'CREATE DATABASE test;' + - name: install sql-migrate + run: go install ./... + - name: postgres + run: bash ./test-integration/postgres.sh + - name: mysql + run: bash ./test-integration/mysql.sh + - name: mysql-flag + run: bash ./test-integration/mysql-flag.sh + - name: mysql-env + run: bash ./test-integration/mysql-env.sh + - name: sqlite + run: bash ./test-integration/sqlite.sh \ No newline at end of file diff --git a/.gitignore b/.gitignore index b083cca4..9720db0c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ /sql-migrate/test.db /test.db +.vscode/ +bin/ diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 00000000..40d1720e --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,98 @@ +linters-settings: + gocritic: + disabled-checks: + - ifElseChain + goimports: + local-prefixes: github.com/rubenv/sql-migrate + govet: + enable-all: true + disable: + - fieldalignment + depguard: + list-type: blacklist + include-go-root: true + include-go-std-lib: true + exhaustive: + default-signifies-exhaustive: true + nolintlint: + allow-unused: false + allow-leading-space: false + allow-no-explanation: + - depguard + require-explanation: true + require-specific: true + revive: + enable-all-rules: false + rules: + - name: atomic + - name: blank-imports + - name: bool-literal-in-expr + - name: call-to-gc + - name: constant-logical-expr + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: duplicated-imports + - name: empty-block + - name: empty-lines + - name: error-naming + - name: error-return + - name: error-strings + - name: errorf + - name: exported + - name: identical-branches + - name: imports-blacklist + - name: increment-decrement + - name: indent-error-flow + - name: modifies-parameter + - name: modifies-value-receiver + - name: package-comments + - name: range + - name: range-val-address + - name: range-val-in-closure + - name: receiver-naming + - name: string-format + - name: string-of-int + - name: struct-tag + - name: time-naming + - name: unconditional-recursion + - name: unexported-naming + - name: unexported-return + - name: superfluous-else + - name: unreachable-code + - name: var-declaration + - name: waitgroup-by-value + - name: unused-receiver + - name: unnecessary-stmt + - name: unused-parameter +run: + tests: true + timeout: 1m +linters: + disable-all: true + enable: + - asciicheck + - depguard + - errcheck + - exhaustive + - gocritic + - gofmt + - gofumpt + - goimports + - govet + - ineffassign + - nolintlint + - revive + - staticcheck + - typecheck + - unused + - whitespace + - errorlint + - gosimple + - unparam +issues: + exclude: + - 'declaration of "err" shadows declaration at' # Allow shadowing of `err` because it's so common + - 'error-strings: error strings should not be capitalized or end with punctuation or a newline' + max-same-issues: 10000 + max-issues-per-linter: 10000 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 64f8c15b..00000000 --- a/.travis.yml +++ /dev/null @@ -1,33 +0,0 @@ -language: go - -sudo: false - -go: - - "1.13" - - "1.14" - - "1.15" - - "1.16" - -services: - - mysql - - postgresql - -before_install: - - mysql -e "CREATE DATABASE IF NOT EXISTS test;" -uroot - - mysql -e "CREATE DATABASE IF NOT EXISTS test_env;" -uroot - - psql -c "CREATE DATABASE test;" -U postgres - -install: - - go get -t ./... - - go install ./... - - go get -u github.com/kisielk/errcheck - -script: - - CGO_ENABLED=0 go build -v . - - go test -v ./... - - bash test-integration/postgres.sh - - bash test-integration/mysql.sh - - bash test-integration/mysql-flag.sh - - bash test-integration/mysql-env.sh - - bash test-integration/sqlite.sh - - errcheck ./... diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..e17ae0ad --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +.PHONY: test lint build + +test: + go test ./... + +lint: + golangci-lint run --fix --config .golangci.yaml + +build: + mkdir -p bin + go build -o ./bin/sql-migrate ./sql-migrate diff --git a/README.md b/README.md index 1f206960..a42adb62 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ * Up/down migrations to allow rollback * Supports multiple database types in one project * Works great with other libraries such as [sqlx](https://jmoiron.github.io/sqlx/) +* Supported on go1.13+ ## Installation diff --git a/bindata_test.go b/bindata_test.go index e8afed6f..655cebf8 100644 --- a/bindata_test.go +++ b/bindata_test.go @@ -11,7 +11,7 @@ import ( func bindata_read(data []byte, name string) ([]byte, error) { gz, err := gzip.NewReader(bytes.NewBuffer(data)) if err != nil { - return nil, fmt.Errorf("Read %q: %v", name, err) + return nil, fmt.Errorf("Read %q: %w", name, err) } var buf bytes.Buffer @@ -21,7 +21,7 @@ func bindata_read(data []byte, name string) ([]byte, error) { } if err != nil { - return nil, fmt.Errorf("Read %q: %v", name, err) + return nil, fmt.Errorf("Read %q: %w", name, err) } return buf.Bytes(), nil @@ -68,7 +68,7 @@ func test_migrations_2_record_sql() ([]byte, error) { // It returns an error if the asset could not be found or // could not be loaded. func Asset(name string) ([]byte, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) + canonicalName := strings.ReplaceAll(name, "\\", "/") if f, ok := _bindata[canonicalName]; ok { return f() } @@ -94,11 +94,13 @@ var _bindata = map[string]func() ([]byte, error){ // directory embedded in the file by go-bindata. // For example if you run go-bindata on data/... and data contains the // following hierarchy: -// data/ -// foo.txt -// img/ -// a.png -// b.png +// +// data/ +// foo.txt +// img/ +// a.png +// b.png +// // then AssetDir("data") would return []string{"foo.txt", "img"} // AssetDir("data/img") would return []string{"a.png", "b.png"} // AssetDir("foo.txt") and AssetDir("notexist") would return an error @@ -106,7 +108,7 @@ var _bindata = map[string]func() ([]byte, error){ func AssetDir(name string) ([]string, error) { node := _bintree if len(name) != 0 { - canonicalName := strings.Replace(name, "\\", "/", -1) + canonicalName := strings.ReplaceAll(name, "\\", "/") pathList := strings.Split(canonicalName, "/") for _, p := range pathList { node = node.Children[p] @@ -131,8 +133,8 @@ type _bintree_t struct { } var _bintree = &_bintree_t{nil, map[string]*_bintree_t{ - "test-migrations": &_bintree_t{nil, map[string]*_bintree_t{ - "1_initial.sql": &_bintree_t{test_migrations_1_initial_sql, map[string]*_bintree_t{}}, - "2_record.sql": &_bintree_t{test_migrations_2_record_sql, map[string]*_bintree_t{}}, + "test-migrations": {nil, map[string]*_bintree_t{ + "1_initial.sql": {test_migrations_1_initial_sql, map[string]*_bintree_t{}}, + "2_record.sql": {test_migrations_2_record_sql, map[string]*_bintree_t{}}, }}, }} diff --git a/doc.go b/doc.go index eb4ed857..8ff186d0 100644 --- a/doc.go +++ b/doc.go @@ -1,24 +1,23 @@ /* - SQL Schema migration tool for Go. Key features: - * Usable as a CLI tool or as a library - * Supports SQLite, PostgreSQL, MySQL, MSSQL and Oracle databases (through gorp) - * Can embed migrations into your application - * Migrations are defined with SQL for full flexibility - * Atomic migrations - * Up/down migrations to allow rollback - * Supports multiple database types in one project + - Usable as a CLI tool or as a library + - Supports SQLite, PostgreSQL, MySQL, MSSQL and Oracle databases (through gorp) + - Can embed migrations into your application + - Migrations are defined with SQL for full flexibility + - Atomic migrations + - Up/down migrations to allow rollback + - Supports multiple database types in one project -Installation +# Installation To install the library and command line program, use the following: go get -v github.com/rubenv/sql-migrate/... -Command-line tool +# Command-line tool The main command is called sql-migrate. @@ -77,7 +76,7 @@ Use the status command to see the state of the applied migrations: | 2_record.sql | no | +---------------+-----------------------------------------+ -MySQL Caveat +# MySQL Caveat If you are using MySQL, you must append ?parseTime=true to the datasource configuration. For example: @@ -89,7 +88,7 @@ If you are using MySQL, you must append ?parseTime=true to the datasource config See https://github.com/go-sql-driver/mysql#parsetime for more information. -Library +# Library Import sql-migrate into your application: @@ -137,7 +136,7 @@ Note that n can be greater than 0 even if there is an error: any migration that The full set of capabilities can be found in the API docs below. -Writing migrations +# Writing migrations Migrations are defined in SQL files, which contain a set of SQL statements. Special comments are used to distinguish up and down migrations. @@ -183,7 +182,7 @@ Normally each migration is run within a transaction in order to guarantee that i -- +migrate Down DROP INDEX people_unique_id_idx; -Embedding migrations with packr +# Embedding migrations with packr If you like your Go applications self-contained (that is: a single binary): use packr (https://github.com/gobuffalo/packr) to embed the migration files. @@ -202,7 +201,7 @@ If you already have a box and would like to use a subdirectory: Dir: "./migrations", } -Embedding migrations with bindata +# Embedding migrations with bindata As an alternative, but slightly less maintained, you can use bindata (https://github.com/shuLhan/go-bindata) to embed the migration files. @@ -226,7 +225,7 @@ Both Asset and AssetDir are functions provided by bindata. Then proceed as usual. -Extending +# Extending Adding a new migration source means implementing MigrationSource. diff --git a/go.mod b/go.mod index bf3d733b..efe6c9b6 100644 --- a/go.mod +++ b/go.mod @@ -29,3 +29,6 @@ require ( gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/yaml.v2 v2.4.0 ) + +// Later releases use unsafe.Slice, which is unsupported in go1.16 +replace golang.org/x/sys => golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc diff --git a/go.sum b/go.sum index b3680632..d9d18207 100644 --- a/go.sum +++ b/go.sum @@ -540,64 +540,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc h1:MeuS1UDyZyFH++6vVy44PuufTeFF0d0nfI6XB87YGSk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -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-20181026203630-95b1ffbd15a5/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= diff --git a/migrate.go b/migrate.go index 34757d33..0974eb61 100644 --- a/migrate.go +++ b/migrate.go @@ -2,6 +2,7 @@ package migrate import ( "bytes" + "context" "database/sql" "errors" "fmt" @@ -183,15 +184,15 @@ type OracleDialect struct { gorp.OracleDialect } -func (d OracleDialect) IfTableNotExists(command, schema, table string) string { +func (OracleDialect) IfTableNotExists(command, _, _ string) string { return command } -func (d OracleDialect) IfSchemaNotExists(command, schema string) string { +func (OracleDialect) IfSchemaNotExists(command, _ string) string { return command } -func (d OracleDialect) IfTableExists(command, schema, table string) string { +func (OracleDialect) IfTableExists(command, _, _ string) string { return command } @@ -287,13 +288,13 @@ func migrationFromFile(dir http.FileSystem, root string, info os.FileInfo) (*Mig path := path.Join(root, info.Name()) file, err := dir.Open(path) if err != nil { - return nil, fmt.Errorf("Error while opening %s: %s", info.Name(), err) + return nil, fmt.Errorf("Error while opening %s: %w", info.Name(), err) } defer func() { _ = file.Close() }() migration, err := ParseMigration(info.Name(), file) if err != nil { - return nil, fmt.Errorf("Error while parsing %s: %s", info.Name(), err) + return nil, fmt.Errorf("Error while parsing %s: %w", info.Name(), err) } return migration, nil } @@ -407,7 +408,7 @@ func ParseMigration(id string, r io.ReadSeeker) (*Migration, error) { parsed, err := sqlparse.ParseMigration(r) if err != nil { - return nil, fmt.Errorf("Error parsing migration (%s): %s", id, err) + return nil, fmt.Errorf("Error parsing migration (%s): %w", id, err) } m.Up = parsed.UpStatements @@ -429,12 +430,24 @@ type SqlExecutor interface { // // Returns the number of applied migrations. func Exec(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection) (int, error) { - return ExecMax(db, dialect, m, dir, 0) + return ExecMaxContext(context.Background(), db, dialect, m, dir, 0) } // Returns the number of applied migrations. func (ms MigrationSet) Exec(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection) (int, error) { - return ms.ExecMax(db, dialect, m, dir, 0) + return ms.ExecMaxContext(context.Background(), db, dialect, m, dir, 0) +} + +// Execute a set of migrations with an input context. +// +// Returns the number of applied migrations. +func ExecContext(ctx context.Context, db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection) (int, error) { + return ExecMaxContext(ctx, db, dialect, m, dir, 0) +} + +// Returns the number of applied migrations. +func (ms MigrationSet) ExecContext(ctx context.Context, db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection) (int, error) { + return ms.ExecMaxContext(ctx, db, dialect, m, dir, 0) } // Execute a set of migrations @@ -446,50 +459,78 @@ func ExecMax(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirecti return migSet.ExecMax(db, dialect, m, dir, max) } +// Execute a set of migrations with an input context. +// +// Will apply at most `max` migrations. Pass 0 for no limit (or use Exec). +// +// Returns the number of applied migrations. +func ExecMaxContext(ctx context.Context, db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, max int) (int, error) { + return migSet.ExecMaxContext(ctx, db, dialect, m, dir, max) +} + // Execute a set of migrations // // Will apply at the target `version` of migration. Cannot be a negative value. // // Returns the number of applied migrations. func ExecVersion(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, version int64) (int, error) { + return ExecVersionContext(context.Background(), db, dialect, m, dir, version) +} + +// Execute a set of migrations with an input context. +// +// Will apply at the target `version` of migration. Cannot be a negative value. +// +// Returns the number of applied migrations. +func ExecVersionContext(ctx context.Context, db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, version int64) (int, error) { if version < 0 { return 0, fmt.Errorf("target version %d should not be negative", version) } - return migSet.ExecVersion(db, dialect, m, dir, version) + return migSet.ExecVersionContext(ctx, db, dialect, m, dir, version) } // Returns the number of applied migrations. func (ms MigrationSet) ExecMax(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, max int) (int, error) { + return ms.ExecMaxContext(context.Background(), db, dialect, m, dir, max) +} + +// Returns the number of applied migrations, but applies with an input context. +func (ms MigrationSet) ExecMaxContext(ctx context.Context, db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, max int) (int, error) { migrations, dbMap, err := ms.PlanMigration(db, dialect, m, dir, max) if err != nil { return 0, err } - return ms.applyMigrations(dir, migrations, dbMap) + return ms.applyMigrations(ctx, dir, migrations, dbMap) } // Returns the number of applied migrations. func (ms MigrationSet) ExecVersion(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, version int64) (int, error) { + return ms.ExecVersionContext(context.Background(), db, dialect, m, dir, version) +} + +func (ms MigrationSet) ExecVersionContext(ctx context.Context, db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, version int64) (int, error) { migrations, dbMap, err := ms.PlanMigrationToVersion(db, dialect, m, dir, version) if err != nil { return 0, err } - return ms.applyMigrations(dir, migrations, dbMap) + return ms.applyMigrations(ctx, dir, migrations, dbMap) } // Applies the planned migrations and returns the number of applied migrations. -func (ms MigrationSet) applyMigrations(dir MigrationDirection, migrations []*PlannedMigration, dbMap *gorp.DbMap) (int, error) { +func (MigrationSet) applyMigrations(ctx context.Context, dir MigrationDirection, migrations []*PlannedMigration, dbMap *gorp.DbMap) (int, error) { applied := 0 for _, migration := range migrations { var executor SqlExecutor var err error if migration.DisableTransaction { - executor = dbMap + executor = dbMap.WithContext(ctx) } else { - executor, err = dbMap.Begin() + e, err := dbMap.Begin() if err != nil { return applied, newTxError(migration, err) } + executor = e.WithContext(ctx) } for _, stmt := range migration.Queries { @@ -645,7 +686,6 @@ func (ms MigrationSet) planMigrationCommon(db *sql.DB, dialect string, m Migrati toApplyCount = max } for _, v := range toApply[0:toApplyCount] { - if dir == Up { result = append(result, &PlannedMigration{ Migration: v, @@ -715,7 +755,7 @@ func SkipMax(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirecti // Filter a slice of migrations into ones that should be applied. func ToApply(migrations []*Migration, current string, direction MigrationDirection) []*Migration { - var index = -1 + index := -1 if current != "" { for index < len(migrations)-1 { index++ @@ -804,16 +844,14 @@ func (ms MigrationSet) getMigrationDbMap(db *sql.DB, dialect string) (*gorp.DbMa Make sure that the parseTime option is supplied to your database connection. Check https://github.com/go-sql-driver/mysql#parsetime for more info.`) - } else { - return nil, err } + return nil, err } } // Create migration database map dbMap := &gorp.DbMap{Db: db, Dialect: d} table := dbMap.AddTableWithNameAndSchema(MigrationRecord{}, ms.SchemaName, ms.getTableName()).SetKeys(false, "Id") - //dbMap.TraceOn("", log.New(os.Stdout, "migrate: ", log.Lmicroseconds)) if dialect == "oci8" || dialect == "godror" { table.ColMap("Id").SetMaxSize(4000) diff --git a/migrate_test.go b/migrate_test.go index fcecc45f..adf0714d 100644 --- a/migrate_test.go +++ b/migrate_test.go @@ -1,8 +1,10 @@ package migrate import ( + "context" "database/sql" "net/http" + "time" "github.com/go-gorp/gorp/v3" "github.com/gobuffalo/packr/v2" @@ -502,7 +504,7 @@ func (s *SqliteMigrateSuite) TestPlanMigrationWithHoles(c *C) { c.Assert(plannedMigrations[2].Queries[0], Equals, down) } -func (s *SqliteMigrateSuite) TestLess(c *C) { +func (*SqliteMigrateSuite) TestLess(c *C) { c.Assert((Migration{Id: "1"}).Less(&Migration{Id: "2"}), Equals, true) // 1 less than 2 c.Assert((Migration{Id: "2"}).Less(&Migration{Id: "1"}), Equals, false) // 2 not less than 1 c.Assert((Migration{Id: "1"}).Less(&Migration{Id: "a"}), Equals, true) // 1 less than a @@ -520,7 +522,6 @@ func (s *SqliteMigrateSuite) TestLess(c *C) { // 20160126_1200 not less than 20160126_1100 c.Assert((Migration{Id: "20160126_1200"}). Less(&Migration{Id: "20160126_1100"}), Equals, false) - } func (s *SqliteMigrateSuite) TestPlanMigrationWithUnknownDatabaseMigrationApplied(c *C) { @@ -663,7 +664,7 @@ func (s *SqliteMigrateSuite) TestExecWithUnknownMigrationInDatabase(c *C) { c.Assert(n, Equals, 2) // Then create a new migration source with one of the migrations missing - var newSqliteMigrations = []*Migration{ + newSqliteMigrations := []*Migration{ { Id: "124_other", Up: []string{"ALTER TABLE people ADD COLUMN middle_name text"}, @@ -742,7 +743,7 @@ func (s *SqliteMigrateSuite) TestRunMigrationObjOtherTable(c *C) { c.Assert(n, Equals, 0) } -func (s *SqliteMigrateSuite) TestSetDisableCreateTable(c *C) { +func (*SqliteMigrateSuite) TestSetDisableCreateTable(c *C) { c.Assert(migSet.DisableCreateTable, Equals, false) SetDisableCreateTable(true) @@ -806,3 +807,39 @@ func (s *SqliteMigrateSuite) TestGetMigrationObjDbMapWithDisableCreateTableFalse c.Assert(tableNameIfExists.Valid, Equals, true) c.Assert(tableNameIfExists.String, Equals, ms.TableName) } + +func (s *SqliteMigrateSuite) TestContextTimeout(c *C) { + // This statement will run for a long time: 1,000,000 iterations of the fibonacci sequence + fibonacciLoopStmt := `WITH RECURSIVE + fibo (curr, next) + AS + ( SELECT 1,1 + UNION ALL + SELECT next, curr+next FROM fibo + LIMIT 1000000 ) + SELECT group_concat(curr) FROM fibo; + ` + migrations := &MemoryMigrationSource{ + Migrations: []*Migration{ + sqliteMigrations[0], + sqliteMigrations[1], + { + Id: "125", + Up: []string{fibonacciLoopStmt}, + Down: []string{}, // Not important here + }, + { + Id: "125", + Up: []string{"INSERT INTO people (id, first_name) VALUES (1, 'Test')", "SELECT fail"}, + Down: []string{}, // Not important here + }, + }, + } + + // Should never run the insert + ctx, cancelFunc := context.WithTimeout(context.Background(), 10*time.Millisecond) + defer cancelFunc() + n, err := ExecContext(ctx, s.Db, "sqlite3", migrations, Up) + c.Assert(err, Not(IsNil)) + c.Assert(n, Equals, 2) +} diff --git a/sort_test.go b/sort_test.go index f72adf5f..93743489 100644 --- a/sort_test.go +++ b/sort_test.go @@ -10,16 +10,16 @@ type SortSuite struct{} var _ = Suite(&SortSuite{}) -func (s *SortSuite) TestSortMigrations(c *C) { - var migrations = byId([]*Migration{ - &Migration{Id: "10_abc", Up: nil, Down: nil}, - &Migration{Id: "120_cde", Up: nil, Down: nil}, - &Migration{Id: "1_abc", Up: nil, Down: nil}, - &Migration{Id: "efg", Up: nil, Down: nil}, - &Migration{Id: "2_cde", Up: nil, Down: nil}, - &Migration{Id: "35_cde", Up: nil, Down: nil}, - &Migration{Id: "3_efg", Up: nil, Down: nil}, - &Migration{Id: "4_abc", Up: nil, Down: nil}, +func (*SortSuite) TestSortMigrations(c *C) { + migrations := byId([]*Migration{ + {Id: "10_abc", Up: nil, Down: nil}, + {Id: "120_cde", Up: nil, Down: nil}, + {Id: "1_abc", Up: nil, Down: nil}, + {Id: "efg", Up: nil, Down: nil}, + {Id: "2_cde", Up: nil, Down: nil}, + {Id: "35_cde", Up: nil, Down: nil}, + {Id: "3_efg", Up: nil, Down: nil}, + {Id: "4_abc", Up: nil, Down: nil}, }) sort.Sort(migrations) diff --git a/sql-migrate/command_common.go b/sql-migrate/command_common.go index e7d79dec..30a976a5 100644 --- a/sql-migrate/command_common.go +++ b/sql-migrate/command_common.go @@ -9,7 +9,7 @@ import ( func ApplyMigrations(dir migrate.MigrationDirection, dryrun bool, limit int, version int64) error { env, err := GetEnvironment() if err != nil { - return fmt.Errorf("Could not parse config: %s", err) + return fmt.Errorf("Could not parse config: %w", err) } db, dialect, err := GetConnection(env) @@ -32,7 +32,7 @@ func ApplyMigrations(dir migrate.MigrationDirection, dryrun bool, limit int, ver } if err != nil { - return fmt.Errorf("Cannot plan migration: %s", err) + return fmt.Errorf("Cannot plan migration: %w", err) } for _, m := range migrations { @@ -48,7 +48,7 @@ func ApplyMigrations(dir migrate.MigrationDirection, dryrun bool, limit int, ver } if err != nil { - return fmt.Errorf("Migration failed: %s", err) + return fmt.Errorf("Migration failed: %w", err) } if n == 1 { diff --git a/sql-migrate/command_down.go b/sql-migrate/command_down.go index bca858a0..7e727553 100644 --- a/sql-migrate/command_down.go +++ b/sql-migrate/command_down.go @@ -7,10 +7,9 @@ import ( migrate "github.com/rubenv/sql-migrate" ) -type DownCommand struct { -} +type DownCommand struct{} -func (c *DownCommand) Help() string { +func (*DownCommand) Help() string { helpText := ` Usage: sql-migrate down [options] ... @@ -28,7 +27,7 @@ Options: return strings.TrimSpace(helpText) } -func (c *DownCommand) Synopsis() string { +func (*DownCommand) Synopsis() string { return "Undo a database migration" } diff --git a/sql-migrate/command_new.go b/sql-migrate/command_new.go index 8e5b6841..4ae7ee55 100644 --- a/sql-migrate/command_new.go +++ b/sql-migrate/command_new.go @@ -16,16 +16,11 @@ var templateContent = ` -- +migrate Down ` -var tpl *template.Template +var tpl = template.Must(template.New("new_migration").Parse(templateContent)) -func init() { - tpl = template.Must(template.New("new_migration").Parse(templateContent)) -} - -type NewCommand struct { -} +type NewCommand struct{} -func (c *NewCommand) Help() string { +func (*NewCommand) Help() string { helpText := ` Usage: sql-migrate new [options] name @@ -40,7 +35,7 @@ Options: return strings.TrimSpace(helpText) } -func (c *NewCommand) Synopsis() string { +func (*NewCommand) Synopsis() string { return "Create a new migration" } @@ -79,7 +74,6 @@ func CreateMigration(name string) error { fileName := fmt.Sprintf("%s-%s.sql", time.Now().Format("20060102150405"), strings.TrimSpace(name)) pathName := path.Join(env.Dir, fileName) f, err := os.Create(pathName) - if err != nil { return err } diff --git a/sql-migrate/command_redo.go b/sql-migrate/command_redo.go index 1aae639d..4df12cc6 100644 --- a/sql-migrate/command_redo.go +++ b/sql-migrate/command_redo.go @@ -8,10 +8,9 @@ import ( migrate "github.com/rubenv/sql-migrate" ) -type RedoCommand struct { -} +type RedoCommand struct{} -func (c *RedoCommand) Help() string { +func (*RedoCommand) Help() string { helpText := ` Usage: sql-migrate redo [options] ... @@ -27,7 +26,7 @@ Options: return strings.TrimSpace(helpText) } -func (c *RedoCommand) Synopsis() string { +func (*RedoCommand) Synopsis() string { return "Reapply the last migration" } diff --git a/sql-migrate/command_skip.go b/sql-migrate/command_skip.go index 2bf54878..3dc79f99 100644 --- a/sql-migrate/command_skip.go +++ b/sql-migrate/command_skip.go @@ -8,10 +8,9 @@ import ( migrate "github.com/rubenv/sql-migrate" ) -type SkipCommand struct { -} +type SkipCommand struct{} -func (c *SkipCommand) Help() string { +func (*SkipCommand) Help() string { helpText := ` Usage: sql-migrate skip [options] ... @@ -27,13 +26,12 @@ Options: return strings.TrimSpace(helpText) } -func (c *SkipCommand) Synopsis() string { +func (*SkipCommand) Synopsis() string { return "Sets the database level to the most recent version available, without running the migrations" } func (c *SkipCommand) Run(args []string) int { var limit int - var dryrun bool cmdFlags := flag.NewFlagSet("up", flag.ContinueOnError) cmdFlags.Usage = func() { ui.Output(c.Help()) } @@ -44,7 +42,7 @@ func (c *SkipCommand) Run(args []string) int { return 1 } - err := SkipMigrations(migrate.Up, dryrun, limit) + err := SkipMigrations(migrate.Up, limit) if err != nil { ui.Error(err.Error()) return 1 @@ -53,10 +51,10 @@ func (c *SkipCommand) Run(args []string) int { return 0 } -func SkipMigrations(dir migrate.MigrationDirection, dryrun bool, limit int) error { +func SkipMigrations(dir migrate.MigrationDirection, limit int) error { env, err := GetEnvironment() if err != nil { - return fmt.Errorf("Could not parse config: %s", err) + return fmt.Errorf("Could not parse config: %w", err) } db, dialect, err := GetConnection(env) @@ -71,7 +69,7 @@ func SkipMigrations(dir migrate.MigrationDirection, dryrun bool, limit int) erro n, err := migrate.SkipMax(db, dialect, source, dir, limit) if err != nil { - return fmt.Errorf("Migration failed: %s", err) + return fmt.Errorf("Migration failed: %w", err) } switch n { diff --git a/sql-migrate/command_status.go b/sql-migrate/command_status.go index 00f6dd49..9ac2f278 100644 --- a/sql-migrate/command_status.go +++ b/sql-migrate/command_status.go @@ -8,13 +8,13 @@ import ( "time" "github.com/olekukonko/tablewriter" + migrate "github.com/rubenv/sql-migrate" ) -type StatusCommand struct { -} +type StatusCommand struct{} -func (c *StatusCommand) Help() string { +func (*StatusCommand) Help() string { helpText := ` Usage: sql-migrate status [options] ... @@ -29,7 +29,7 @@ Options: return strings.TrimSpace(helpText) } -func (c *StatusCommand) Synopsis() string { +func (*StatusCommand) Synopsis() string { return "Show migration status" } diff --git a/sql-migrate/command_up.go b/sql-migrate/command_up.go index cbc275e8..301a8b3a 100644 --- a/sql-migrate/command_up.go +++ b/sql-migrate/command_up.go @@ -7,10 +7,9 @@ import ( migrate "github.com/rubenv/sql-migrate" ) -type UpCommand struct { -} +type UpCommand struct{} -func (c *UpCommand) Help() string { +func (*UpCommand) Help() string { helpText := ` Usage: sql-migrate up [options] ... @@ -28,7 +27,7 @@ Options: return strings.TrimSpace(helpText) } -func (c *UpCommand) Synopsis() string { +func (*UpCommand) Synopsis() string { return "Migrates the database to the most recent version available" } diff --git a/sql-migrate/config.go b/sql-migrate/config.go index 7eb2e2f5..c8564ab4 100644 --- a/sql-migrate/config.go +++ b/sql-migrate/config.go @@ -10,9 +10,10 @@ import ( "runtime/debug" "github.com/go-gorp/gorp/v3" - migrate "github.com/rubenv/sql-migrate" "gopkg.in/yaml.v2" + migrate "github.com/rubenv/sql-migrate" + _ "github.com/go-sql-driver/mysql" _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" @@ -24,8 +25,10 @@ var dialects = map[string]gorp.Dialect{ "mysql": gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8"}, } -var ConfigFile string -var ConfigEnvironment string +var ( + ConfigFile string + ConfigEnvironment string +) func ConfigFlags(f *flag.FlagSet) { f.StringVar(&ConfigFile, "config", "dbconfig.yml", "Configuration file to use.") @@ -96,7 +99,7 @@ func GetEnvironment() (*Environment, error) { func GetConnection(env *Environment) (*sql.DB, string, error) { db, err := sql.Open(env.Dialect, env.DataSource) if err != nil { - return nil, "", fmt.Errorf("Cannot connect to database: %s", err) + return nil, "", fmt.Errorf("Cannot connect to database: %w", err) } // Make sure we only accept dialects that were compiled in. diff --git a/sqlparse/sqlparse.go b/sqlparse/sqlparse.go index d336e772..f04460e6 100644 --- a/sqlparse/sqlparse.go +++ b/sqlparse/sqlparse.go @@ -3,10 +3,8 @@ package sqlparse import ( "bufio" "bytes" - "errors" "fmt" "io" - "strings" ) @@ -23,29 +21,26 @@ type ParsedMigration struct { DisableTransactionDown bool } -var ( - // LineSeparator can be used to split migrations by an exact line match. This line - // will be removed from the output. If left blank, it is not considered. It is defaulted - // to blank so you will have to set it manually. - // Use case: in MSSQL, it is convenient to separate commands by GO statements like in - // SQL Query Analyzer. - LineSeparator = "" -) +// LineSeparator can be used to split migrations by an exact line match. This line +// will be removed from the output. If left blank, it is not considered. It is defaulted +// to blank so you will have to set it manually. +// Use case: in MSSQL, it is convenient to separate commands by GO statements like in +// SQL Query Analyzer. +var LineSeparator = "" func errNoTerminator() error { if len(LineSeparator) == 0 { - return errors.New(`ERROR: The last statement must be ended by a semicolon or '-- +migrate StatementEnd' marker. + return fmt.Errorf(`ERROR: The last statement must be ended by a semicolon or '-- +migrate StatementEnd' marker. See https://github.com/rubenv/sql-migrate for details.`) } - return errors.New(fmt.Sprintf(`ERROR: The last statement must be ended by a semicolon, a line whose contents are %q, or '-- +migrate StatementEnd' marker. - See https://github.com/rubenv/sql-migrate for details.`, LineSeparator)) + return fmt.Errorf(`ERROR: The last statement must be ended by a semicolon, a line whose contents are %q, or '-- +migrate StatementEnd' marker. + See https://github.com/rubenv/sql-migrate for details.`, LineSeparator) } // Checks the line to see if the line has a statement-ending semicolon // or if the line contains a double-dash comment. func endsWithSemicolon(line string) bool { - prev := "" scanner := bufio.NewScanner(strings.NewReader(line)) scanner.Split(bufio.ScanWords) @@ -88,12 +83,12 @@ func parseCommand(line string) (*migrateCommand, error) { cmd := &migrateCommand{} if !strings.HasPrefix(line, sqlCmdPrefix) { - return nil, errors.New("ERROR: not a sql-migrate command") + return nil, fmt.Errorf("ERROR: not a sql-migrate command") } fields := strings.Fields(line[len(sqlCmdPrefix):]) if len(fields) == 0 { - return nil, errors.New(`ERROR: incomplete migration command`) + return nil, fmt.Errorf(`ERROR: incomplete migration command`) } cmd.Command = fields[0] @@ -151,7 +146,6 @@ func ParseMigration(r io.ReadSeeker) (*ParsedMigration, error) { if cmd.HasOption(optionNoTransaction) { p.DisableTransactionUp = true } - break case "Down": if len(strings.TrimSpace(buf.String())) > 0 { @@ -161,20 +155,17 @@ func ParseMigration(r io.ReadSeeker) (*ParsedMigration, error) { if cmd.HasOption(optionNoTransaction) { p.DisableTransactionDown = true } - break case "StatementBegin": if currentDirection != directionNone { ignoreSemicolons = true } - break case "StatementEnd": if currentDirection != directionNone { - statementEnded = (ignoreSemicolons == true) + statementEnded = ignoreSemicolons ignoreSemicolons = false } - break } } @@ -216,11 +207,11 @@ func ParseMigration(r io.ReadSeeker) (*ParsedMigration, error) { // diagnose likely migration script errors if ignoreSemicolons { - return nil, errors.New("ERROR: saw '-- +migrate StatementBegin' with no matching '-- +migrate StatementEnd'") + return nil, fmt.Errorf("ERROR: saw '-- +migrate StatementBegin' with no matching '-- +migrate StatementEnd'") } if currentDirection == directionNone { - return nil, errors.New(`ERROR: no Up/Down annotations found, so no statements were executed. + return nil, fmt.Errorf(`ERROR: no Up/Down annotations found, so no statements were executed. See https://github.com/rubenv/sql-migrate for details.`) } diff --git a/sqlparse/sqlparse_test.go b/sqlparse/sqlparse_test.go index 0f3f8a7c..801f2d7c 100644 --- a/sqlparse/sqlparse_test.go +++ b/sqlparse/sqlparse_test.go @@ -9,12 +9,11 @@ import ( func Test(t *testing.T) { TestingT(t) } -type SqlParseSuite struct { -} +type SqlParseSuite struct{} var _ = Suite(&SqlParseSuite{}) -func (s *SqlParseSuite) TestSemicolons(c *C) { +func (*SqlParseSuite) TestSemicolons(c *C) { type testData struct { line string result bool @@ -53,7 +52,7 @@ func (s *SqlParseSuite) TestSemicolons(c *C) { } } -func (s *SqlParseSuite) TestSplitStatements(c *C) { +func (*SqlParseSuite) TestSplitStatements(c *C) { type testData struct { sql string upCount int @@ -81,21 +80,21 @@ func (s *SqlParseSuite) TestSplitStatements(c *C) { } } -func (s *SqlParseSuite) TestIntentionallyBadStatements(c *C) { +func (*SqlParseSuite) TestIntentionallyBadStatements(c *C) { for _, test := range intentionallyBad { _, err := ParseMigration(strings.NewReader(test)) c.Assert(err, NotNil) } } -func (s *SqlParseSuite) TestJustComment(c *C) { +func (*SqlParseSuite) TestJustComment(c *C) { for _, test := range justAComment { _, err := ParseMigration(strings.NewReader(test)) c.Assert(err, NotNil) } } -func (s *SqlParseSuite) TestCustomTerminator(c *C) { +func (*SqlParseSuite) TestCustomTerminator(c *C) { LineSeparator = "GO" defer func() { LineSeparator = "" }() @@ -376,4 +375,5 @@ CREATE TABLE post ( -- +migrate Down -- no migration here -`} +`, +} diff --git a/toapply_test.go b/toapply_test.go index 2aac9deb..8ddd259b 100644 --- a/toapply_test.go +++ b/toapply_test.go @@ -7,17 +7,16 @@ import ( ) var toapplyMigrations = []*Migration{ - &Migration{Id: "abc", Up: nil, Down: nil}, - &Migration{Id: "cde", Up: nil, Down: nil}, - &Migration{Id: "efg", Up: nil, Down: nil}, + {Id: "abc", Up: nil, Down: nil}, + {Id: "cde", Up: nil, Down: nil}, + {Id: "efg", Up: nil, Down: nil}, } -type ToApplyMigrateSuite struct { -} +type ToApplyMigrateSuite struct{} var _ = Suite(&ToApplyMigrateSuite{}) -func (s *ToApplyMigrateSuite) TestGetAll(c *C) { +func (*ToApplyMigrateSuite) TestGetAll(c *C) { toApply := ToApply(toapplyMigrations, "", Up) c.Assert(toApply, HasLen, 3) c.Assert(toApply[0], Equals, toapplyMigrations[0]) @@ -25,20 +24,20 @@ func (s *ToApplyMigrateSuite) TestGetAll(c *C) { c.Assert(toApply[2], Equals, toapplyMigrations[2]) } -func (s *ToApplyMigrateSuite) TestGetAbc(c *C) { +func (*ToApplyMigrateSuite) TestGetAbc(c *C) { toApply := ToApply(toapplyMigrations, "abc", Up) c.Assert(toApply, HasLen, 2) c.Assert(toApply[0], Equals, toapplyMigrations[1]) c.Assert(toApply[1], Equals, toapplyMigrations[2]) } -func (s *ToApplyMigrateSuite) TestGetCde(c *C) { +func (*ToApplyMigrateSuite) TestGetCde(c *C) { toApply := ToApply(toapplyMigrations, "cde", Up) c.Assert(toApply, HasLen, 1) c.Assert(toApply[0], Equals, toapplyMigrations[2]) } -func (s *ToApplyMigrateSuite) TestGetDone(c *C) { +func (*ToApplyMigrateSuite) TestGetDone(c *C) { toApply := ToApply(toapplyMigrations, "efg", Up) c.Assert(toApply, HasLen, 0) @@ -46,25 +45,25 @@ func (s *ToApplyMigrateSuite) TestGetDone(c *C) { c.Assert(toApply, HasLen, 0) } -func (s *ToApplyMigrateSuite) TestDownDone(c *C) { +func (*ToApplyMigrateSuite) TestDownDone(c *C) { toApply := ToApply(toapplyMigrations, "", Down) c.Assert(toApply, HasLen, 0) } -func (s *ToApplyMigrateSuite) TestDownCde(c *C) { +func (*ToApplyMigrateSuite) TestDownCde(c *C) { toApply := ToApply(toapplyMigrations, "cde", Down) c.Assert(toApply, HasLen, 2) c.Assert(toApply[0], Equals, toapplyMigrations[1]) c.Assert(toApply[1], Equals, toapplyMigrations[0]) } -func (s *ToApplyMigrateSuite) TestDownAbc(c *C) { +func (*ToApplyMigrateSuite) TestDownAbc(c *C) { toApply := ToApply(toapplyMigrations, "abc", Down) c.Assert(toApply, HasLen, 1) c.Assert(toApply[0], Equals, toapplyMigrations[0]) } -func (s *ToApplyMigrateSuite) TestDownAll(c *C) { +func (*ToApplyMigrateSuite) TestDownAll(c *C) { toApply := ToApply(toapplyMigrations, "efg", Down) c.Assert(toApply, HasLen, 3) c.Assert(toApply[0], Equals, toapplyMigrations[2]) @@ -78,13 +77,13 @@ func (s *ToApplyMigrateSuite) TestDownAll(c *C) { c.Assert(toApply[2], Equals, toapplyMigrations[0]) } -func (s *ToApplyMigrateSuite) TestAlphaNumericMigrations(c *C) { - var migrations = byId([]*Migration{ - &Migration{Id: "10_abc", Up: nil, Down: nil}, - &Migration{Id: "1_abc", Up: nil, Down: nil}, - &Migration{Id: "efg", Up: nil, Down: nil}, - &Migration{Id: "2_cde", Up: nil, Down: nil}, - &Migration{Id: "35_cde", Up: nil, Down: nil}, +func (*ToApplyMigrateSuite) TestAlphaNumericMigrations(c *C) { + migrations := byId([]*Migration{ + {Id: "10_abc", Up: nil, Down: nil}, + {Id: "1_abc", Up: nil, Down: nil}, + {Id: "efg", Up: nil, Down: nil}, + {Id: "2_cde", Up: nil, Down: nil}, + {Id: "35_cde", Up: nil, Down: nil}, }) sort.Sort(migrations)