Goose для миграций

5 minute read

Программист, приученный к миграциям, это как кот, приученный к лотку - где попало гадить не будет. В проекте, который разрабатывается на больше чем на одной машине, миграции нужны как воздух.

Есть много разных инструментов для миграций, написанные на других языках. На Go тоже есть. Правда поменьше и попроще. Например goose.

Устанавливается утилита через go get:

$ go get bitbucket.org/liamstask/goose/cmd/goose

Почитать о том, как использовать goose можно на страничке проекта на битбакете.

Тулза почти идеальная. Но мне нужно было использовать ее совместно с SQLite, а доступные диалекты только mysql или potsgre, что меня никак не устраивало.

Поддержка SQLite

Полезем внутря. Первым делом в файле goose/lib/goose/dialect.go добавляем новый диалект:

func dialectByName(d string) SqlDialect {
    switch d {
    case "postgres":
        return &PostgresDialect{}
    case "mysql":
        return &MySqlDialect{}
    case "sqlite3":
        return &SqliteDialect{}
    }

    return nil
}

И добавляем новый тип SqliteDialect, который соответствует интерфейсу SqlDialect

////////////////////////////
// sqlite
////////////////////////////

type SqliteDialect struct{}

func (m *SqliteDialect) createVersionTableSql() string {
    return `create table goose_db_version (
                id integer not null primary key autoincrement,
                version_id integer not null,
                is_applied integer not null,
                tstamp text null
            );`
}

func (m *SqliteDialect) insertVersionSql() string {
    return "INSERT INTO goose_db_version (version_id, is_applied) VALUES (?, ?);"
}

func (m *SqliteDialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
    rows, err := db.Query("SELECT version_id, is_applied from goose_db_version ORDER BY id DESC")

    // XXX: check for mysql specific error indicating the table doesn't exist.
    // for now, assume any error is because the table doesn't exist,
    // in which case we'll try to create it.
    if err != nil {
        return nil, ErrTableDoesNotExist
    }

    return rows, err
}

Теперь, в файле goose/lib/goose/dbconf.go указываем настройки для sqlite: какой драйвер и какой тип диалекта юзать:

// Create a new DBDriver and populate driver specific
// fields for drivers that we know about.
// Further customization may be done in NewDBConf
func newDBDriver(name, open string) DBDriver {

    d := DBDriver{
        Name:    name,
        OpenStr: open,
    }

    switch name {
    case "postgres":
        d.Import = "github.com/lib/pq"
        d.Dialect = &PostgresDialect{}

    case "mymysql":
        d.Import = "github.com/ziutek/mymysql/godrv"
        d.Dialect = &MySqlDialect{}

    case "sqlite3":
        d.Import = "github.com/mattn/go-sqlite3"
        d.Dialect = &SqliteDialect{}
    }

    return d
}

В файле goose/lib/goose/migrate.go нужно предварительно указать в импорте дравер для sqlite.

import (
    //...
    _ "github.com/mattn/go-sqlite3"
    //...
)

И на этом все. Теперь можно использовать goose для рботы с sqlite.

Только одно маленькое замечание. В отличие от драйверов postgres и mysql, драйвер sqlite не будет выполнять запросы при использовании txn.Query(). Нужно использовать txn.Exec()