mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
* migratex api uses tx now instead of db we want to be able to do external queries outside of the migration itself inside of the same transaction for version checking. if we don't do this, we risk the case where we set the version to the latest but we don't run the table creates at all, so we have a db that thinks it's up to date but doesn't even have any tables, and on subsequent boots if a migration slides in then the migrations will run when there are no tables. it was unlikely, but now it's dead. * tx friendly table exists check the previous existence checker for dbs was relying on getting back errors about the db not existing. if we use this in a tx, it makes the whole tx invalid for postgres. so, now we have count the table queries which return a 1 or a 0 instead of a 1 or an error so that we can check existence inside of a transaction. voila.
101 lines
2.0 KiB
Go
101 lines
2.0 KiB
Go
package migratex
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
_ "github.com/mattn/go-sqlite3"
|
|
)
|
|
|
|
const testsqlite3 = "file::memory:?mode=memory&cache=shared"
|
|
|
|
type tm struct{}
|
|
|
|
func (t *tm) Up(ctx context.Context, tx *sqlx.Tx) error {
|
|
_, err := tx.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS foo (
|
|
bar bigint NOT NULL PRIMARY KEY
|
|
)`)
|
|
return err
|
|
}
|
|
|
|
func (t *tm) Down(ctx context.Context, tx *sqlx.Tx) error {
|
|
_, err := tx.ExecContext(ctx, "DROP TABLE foo")
|
|
return err
|
|
}
|
|
|
|
func (t *tm) Version() int64 { return 1 }
|
|
|
|
func TestMigrateUp(t *testing.T) {
|
|
x := new(tm)
|
|
|
|
db, err := sqlx.Open("sqlite3", testsqlite3)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
do := func() error {
|
|
tx, err := db.Beginx()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer tx.Commit()
|
|
|
|
version, dirty, err := Version(ctx, tx)
|
|
if version != NilVersion || err != nil || dirty {
|
|
return fmt.Errorf("version err: %v %v", err, dirty)
|
|
}
|
|
|
|
if version != NilVersion {
|
|
return errors.New("found existing version in db, nuke it")
|
|
}
|
|
|
|
err = Up(ctx, tx, []Migration{x})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
version, dirty, err = Version(ctx, tx)
|
|
if err != nil || dirty {
|
|
return fmt.Errorf("version err: %v %v", err, dirty)
|
|
}
|
|
|
|
if version != x.Version() {
|
|
return errors.New("version did not update, migration should have ran.")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
err = do()
|
|
if err != nil {
|
|
t.Fatalf("couldn't run migrations: %v", err)
|
|
}
|
|
|
|
do = func() error {
|
|
// make sure the table is there.
|
|
// TODO find a db agnostic way of doing this.
|
|
// query := db.Rebind(`SELECT foo FROM sqlite_master WHERE type = 'table'`)
|
|
query := db.Rebind(`SELECT name FROM sqlite_master where type='table' AND name='foo'`)
|
|
var result string
|
|
err = db.QueryRowContext(ctx, query).Scan(&result)
|
|
if err != nil {
|
|
return fmt.Errorf("foo check: %v", err)
|
|
}
|
|
|
|
if result != "foo" {
|
|
return fmt.Errorf("migration version worked but migration didn't work: %v", result)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
err = do()
|
|
if err != nil {
|
|
t.Fatalf("migration check failed: %v", err)
|
|
}
|
|
}
|