📚 sql-migrate - Awesome Go Library for Database
Database migration tool. Allows embedding migrations into the application using go-bindata.
Detailed Description of sql-migrate
sql-migrate
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
- Works great with other libraries such as sqlx
- Supported on go1.13+
Installation
To install the library and command line program, use the following:
go get -v github.com/rubenv/sql-migrate/...
For Go version from 1.18, use:
go install github.com/rubenv/sql-migrate/...@latest
Usage
As a standalone tool
$ sql-migrate --help
usage: sql-migrate [--version] [--help] <command> [<args>]
Available commands are:
down Undo a database migration
new Create a new migration
redo Reapply the last migration
status Show migration status
up Migrates the database to the most recent version available
Each command requires a configuration file (which defaults to dbconfig.yml
, but can be specified with the -config
flag). This config file should specify one or more environments:
development:
dialect: sqlite3
datasource: test.db
dir: migrations/sqlite3
production:
dialect: postgres
datasource: dbname=myapp sslmode=disable
dir: migrations/postgres
table: migrations
(See more examples for different set ups here)
Also one can obtain env variables in datasource field via os.ExpandEnv
embedded call for the field.
This may be useful if one doesn't want to store credentials in file:
production:
dialect: postgres
datasource: host=prodhost dbname=proddb user=${DB_USER} password=${DB_PASSWORD} sslmode=require
dir: migrations
table: migrations
The table
setting is optional and will default to gorp_migrations
.
The environment that will be used can be specified with the -env
flag (defaults to development
).
Use the --help
flag in combination with any of the commands to get an overview of its usage:
$ sql-migrate up --help
Usage: sql-migrate up [options] ...
Migrates the database to the most recent version available.
Options:
-config=dbconfig.yml Configuration file to use.
-env="development" Environment.
-limit=0 Limit the number of migrations (0 = unlimited).
-version Run migrate up to a specific version, eg: the version number of migration 1_initial.sql is 1.
-dryrun Don't apply migrations, just print them.
The new
command creates a new empty migration template using the following pattern <current time>-<name>.sql
.
The up
command applies all available migrations. By contrast, down
will only apply one migration by default. This behavior can be changed for both by using the -limit
parameter, and the -version
parameter. Note -version
has higher priority than -limit
if you try to use them both.
The redo
command will unapply the last migration and reapply it. This is useful during development, when you're writing migrations.
Use the status
command to see the state of the applied migrations:
$ sql-migrate status
+---------------+-----------------------------------------+
| MIGRATION | APPLIED |
+---------------+-----------------------------------------+
| 1_initial.sql | 2014-09-13 08:19:06.788354925 +0000 UTC |
| 2_record.sql | no |
+---------------+-----------------------------------------+
Running Test Integrations
You can see how to run setups for different setups by executing the .sh
files in test-integration
# Run mysql-env.sh example (you need to be in the project root directory)
./test-integration/mysql-env.sh
MySQL Caveat
If you are using MySQL, you must append ?parseTime=true
to the datasource
configuration. For example:
production:
dialect: mysql
datasource: root@/dbname?parseTime=true
dir: migrations/mysql
table: migrations
See here for more information.
Oracle (oci8)
Oracle Driver is oci8, it is not pure Go code and relies on Oracle Office Client (Instant Client), more detailed information is in the oci8 repo.
Install with Oracle support
To install the library and command line program, use the following:
go get -tags oracle -v github.com/rubenv/sql-migrate/...
development:
dialect: oci8
datasource: user/password@localhost:1521/sid
dir: migrations/oracle
table: migrations
Oracle (godror)
Oracle Driver is godror, it is not pure Go code and relies on Oracle Office Client (Instant Client), more detailed information is in the godror repository.
Install with Oracle support
To install the library and command line program, use the following:
- Install sql-migrate
go get -tags godror -v github.com/rubenv/sql-migrate/...
- Download Oracle Office Client(e.g. macos, click Instant Client if you are other system)
wget https://download.oracle.com/otn_software/mac/instantclient/193000/instantclient-basic-macos.x64-19.3.0.0.0dbru.zip
- Configure environment variables
LD_LIBRARY_PATH
export LD_LIBRARY_PATH=your_oracle_office_path/instantclient_19_3
development:
dialect: godror
datasource: user/password@localhost:1521/sid
dir: migrations/oracle
table: migrations
As a library
Import sql-migrate into your application:
import "github.com/rubenv/sql-migrate"
Set up a source of migrations, this can be from memory, from a set of files, from bindata (more on that later), or from any library that implements http.FileSystem
:
// Hardcoded strings in memory:
migrations := &migrate.MemoryMigrationSource{
Migrations: []*migrate.Migration{
&migrate.Migration{
Id: "123",
Up: []string{"CREATE TABLE people (id int)"},
Down: []string{"DROP TABLE people"},
},
},
}
// OR: Read migrations from a folder:
migrations := &migrate.FileMigrationSource{
Dir: "db/migrations",
}
// OR: Use migrations from a packr box
// Note: Packr is no longer supported, your best option these days is [embed](https://pkg.go.dev/embed)
migrations := &migrate.PackrMigrationSource{
Box: packr.New("migrations", "./migrations"),
}
// OR: Use pkger which implements `http.FileSystem`
migrationSource := &migrate.HttpFileSystemMigrationSource{
FileSystem: pkger.Dir("/db/migrations"),
}
// OR: Use migrations from bindata:
migrations := &migrate.AssetMigrationSource{
Asset: Asset,
AssetDir: AssetDir,
Dir: "migrations",
}
// OR: Read migrations from a `http.FileSystem`
migrationSource := &migrate.HttpFileSystemMigrationSource{
FileSystem: httpFS,
}
Then use the Exec
function to upgrade your database:
db, err := sql.Open("sqlite3", filename)
if err != nil {
// Handle errors!
}
n, err := migrate.Exec(db, "sqlite3", migrations, migrate.Up)
if err != nil {
// Handle errors!
}
fmt.Printf("Applied %d migrations!\n", n)
Note that n
can be greater than 0
even if there is an error: any migration that succeeded will remain applied even if a later one fails.
Check the GoDoc reference for the full documentation.
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.
-- +migrate Up
-- SQL in section 'Up' is executed when this migration is applied
CREATE TABLE people (id int);
-- +migrate Down
-- SQL section 'Down' is executed when this migration is rolled back
DROP TABLE people;
You can put multiple statements in each block, as long as you end them with a semicolon (;
).
You can alternatively set up a separator string that matches an entire line by setting sqlparse.LineSeparator
. This
can be used to imitate, for example, MS SQL Query Analyzer functionality where commands can be separated by a line with
contents of GO
. If sqlparse.LineSeparator
is matched, it will not be included in the resulting migration scripts.
If you have complex statements which contain semicolons, use StatementBegin
and StatementEnd
to indicate boundaries:
-- +migrate Up
CREATE TABLE people (id int);
-- +migrate StatementBegin
CREATE OR REPLACE FUNCTION do_something()
returns void AS $$
DECLARE
create_query text;
BEGIN
-- Do something here
END;
$$
language plpgsql;
-- +migrate StatementEnd
-- +migrate Down
DROP FUNCTION do_something();
DROP TABLE people;
The order in which migrations are applied is defined through the filename: sql-migrate will sort migrations based on their name. It's recommended to use an increasing version number or a timestamp as the first part of the filename.
Normally each migration is run within a transaction in order to guarantee that it is fully atomic. However some SQL commands (for example creating an index concurrently in PostgreSQL) cannot be executed inside a transaction. In order to execute such a command in a migration, the migration can be run using the notransaction
option:
-- +migrate Up notransaction
CREATE UNIQUE INDEX CONCURRENTLY people_unique_id_idx ON people (id);
-- +migrate Down
DROP INDEX people_unique_id_idx;
Embedding migrations with embed
If you like your Go applications self-contained (that is: a single binary): use embed to embed the migration files.
Just write your migration files as usual, as a set of SQL files in a folder.
Import the embed package into your application and point it to your migrations:
import "embed"
//go:embed migrations/*
var dbMigrations embed.FS
Use the EmbedFileSystemMigrationSource
in your application to find the migrations:
migrations := migrate.EmbedFileSystemMigrationSource{
FileSystem: dbMigrations,
Root: "migrations",
}
Other options such as packr or go-bindata are no longer recommended.
Embedding migrations with libraries that implement http.FileSystem
You can also embed migrations with any library that implements http.FileSystem
, like vfsgen
, parcello
, or go-resources
.
migrationSource := &migrate.HttpFileSystemMigrationSource{
FileSystem: httpFS,
}
Extending
Adding a new migration source means implementing MigrationSource
.
type MigrationSource interface {
FindMigrations() ([]*Migration, error)
}
The resulting slice of migrations will be executed in the given order, so it should usually be sorted by the Id
field.
Usage with sqlx
This library is compatible with sqlx. When calling migrate just dereference the DB from your *sqlx.DB
:
n, err := migrate.Exec(db.DB, "sqlite3", migrations, migrate.Up)
// ^^^ <-- Here db is a *sqlx.DB, the db.DB field is the plain sql.DB
if err != nil {
// Handle errors!
}
Questions or Feedback?
You can use Github Issues for feedback or questions.
License
This library is distributed under the MIT license.