goose
Goose is a database migration tool. Both a CLI and a library.
Manage your database schema by creating incremental SQL changes or Go functions.
Features
- Works against multiple databases:
- Postgres, MySQL, SQLite, YDB, ClickHouse, MSSQL, Vertica, and
more.
- Supports Go migrations written as plain functions.
- Supports embedded migrations.
- Out-of-order migrations.
- Seeding data.
- Environment variable substitution in SQL migrations.
- ... and more.
Install
go install github.com/pressly/goose/v3/cmd/goose@latest
This will install the goose
binary to your $GOPATH/bin
directory.
Binary too big? Build a lite version by excluding the drivers you don't need:
go build -tags='no_postgres no_mysql no_sqlite3 no_ydb' -o goose ./cmd/goose
# Available build tags:
# no_clickhouse no_libsql no_mssql no_mysql
# no_postgres no_sqlite3 no_vertica no_ydb
For macOS users goose
is available as a Homebrew
Formulae:
brew install goose
See installation documentation for more details.
Usage
Click to show goose help
output
Usage: goose [OPTIONS] DRIVER DBSTRING COMMAND
or
Set environment key
GOOSE_DRIVER=DRIVER
GOOSE_DBSTRING=DBSTRING
GOOSE_MIGRATION_DIR=MIGRATION_DIR
Usage: goose [OPTIONS] COMMAND
Drivers:
postgres
mysql
sqlite3
mssql
redshift
tidb
clickhouse
vertica
ydb
Examples:
goose sqlite3 ./foo.db status
goose sqlite3 ./foo.db create init sql
goose sqlite3 ./foo.db create add_some_column sql
goose sqlite3 ./foo.db create fetch_user_data go
goose sqlite3 ./foo.db up
goose postgres "user=postgres dbname=postgres sslmode=disable" status
goose mysql "user:password@/dbname?parseTime=true" status
goose redshift "postgres://user:password@qwerty.us-east-1.redshift.amazonaws.com:5439/db" status
goose tidb "user:password@/dbname?parseTime=true" status
goose mssql "sqlserver://user:password@dbname:1433?database=master" status
goose clickhouse "tcp://127.0.0.1:9000" status
goose vertica "vertica://user:password@localhost:5433/dbname?connection_load_balance=1" status
goose ydb "grpcs://localhost:2135/local?go_query_mode=scripting&go_fake_tx=scripting&go_query_bind=declare,numeric" status
GOOSE_DRIVER=sqlite3 GOOSE_DBSTRING=./foo.db goose status
GOOSE_DRIVER=sqlite3 GOOSE_DBSTRING=./foo.db goose create init sql
GOOSE_DRIVER=postgres GOOSE_DBSTRING="user=postgres dbname=postgres sslmode=disable" goose status
GOOSE_DRIVER=mysql GOOSE_DBSTRING="user:password@/dbname" goose status
GOOSE_DRIVER=redshift GOOSE_DBSTRING="postgres://user:password@qwerty.us-east-1.redshift.amazonaws.com:5439/db" goose status
GOOSE_DRIVER=clickhouse GOOSE_DBSTRING="clickhouse://user:password@qwerty.clickhouse.cloud:9440/dbname?secure=true&skip_verify=false" goose status
Options:
-allow-missing
applies missing (out-of-order) migrations
-certfile string
file path to root CA's certificates in pem format (only support on mysql)
-dir string
directory with migration files (default ".", can be set via the GOOSE_MIGRATION_DIR env variable).
-h print help
-no-color
disable color output (NO_COLOR env variable supported)
-no-versioning
apply migration commands with no versioning, in file order, from directory pointed to
-s use sequential numbering for new migrations
-ssl-cert string
file path to SSL certificates in pem format (only support on mysql)
-ssl-key string
file path to SSL key in pem format (only support on mysql)
-table string
migrations table name (default "goose_db_version")
-timeout duration
maximum allowed duration for queries to run; e.g., 1h13m
-v enable verbose mode
-version
print version
Commands:
up Migrate the DB to the most recent version available
up-by-one Migrate the DB up by 1
up-to VERSION Migrate the DB to a specific VERSION
down Roll back the version by 1
down-to VERSION Roll back to a specific VERSION
redo Re-run the latest migration
reset Roll back all migrations
status Dump the migration status for the current DB
version Print the current version of the database
create NAME [sql|go] Creates new migration file with the current timestamp
fix Apply sequential ordering to migrations
validate Check migration files without running them
Commonly used commands:
create • up • up-to • down • down-to • status • version
create
Create a new SQL migration.
$ goose create add_some_column sql
$ Created new file: 20170506082420_add_some_column.sql
$ goose -s create add_some_column sql
$ Created new file: 00001_add_some_column.sql
Edit the newly created file to define the behavior of your migration.
You can also create a Go migration, if you then invoke it with your own goose
binary:
$ goose create fetch_user_data go
$ Created new file: 20170506082421_fetch_user_data.go
up
Apply all available migrations.
$ goose up
$ OK 001_basics.sql
$ OK 002_next.sql
$ OK 003_and_again.go
up-to
Migrate up to a specific version.
$ goose up-to 20170506082420
$ OK 20170506082420_create_table.sql
up-by-one
Migrate up a single migration from the current version
$ goose up-by-one
$ OK 20170614145246_change_type.sql
down
Roll back a single migration from the current version.
$ goose down
$ OK 003_and_again.go
down-to
Roll back migrations to a specific version.
$ goose down-to 20170506082527
$ OK 20170506082527_alter_column.sql
Or, roll back all migrations (careful!):
$ goose down-to 0
status
Print the status of all migrations:
$ goose status
$ Applied At Migration
$ =======================================
$ Sun Jan 6 11:25:03 2013 -- 001_basics.sql
$ Sun Jan 6 11:25:03 2013 -- 002_next.sql
$ Pending -- 003_and_again.go
Note: for MySQL parseTime flag must be enabled.
Note: for MySQL
multiStatements
must
be enabled. This is required when writing multiple queries separated by ';' characters in a single
sql file.
version
Print the current version of the database:
$ goose version
$ goose: version 002
Migrations
goose supports migrations written in SQL or in Go.
SQL Migrations
A sample SQL migration looks like:
CREATE TABLE post (
id int NOT NULL,
title text,
body text,
PRIMARY KEY(id)
);
DROP TABLE post;
Each migration file must have exactly one -- +goose Up
annotation. The -- +goose Down
annotation
is optional. If the file has both annotations, then the -- +goose Up
annotation must come
first.
Notice the annotations in the comments. Any statements following -- +goose Up
will be executed as
part of a forward migration, and any statements following -- +goose Down
will be executed as part
of a rollback.
By default, all migrations are run within a transaction. Some statements like CREATE DATABASE
,
however, cannot be run within a transaction. You may optionally add -- +goose NO TRANSACTION
to
the top of your migration file in order to skip transactions within that specific migration file.
Both Up and Down migrations within this file will be run without transactions.
By default, SQL statements are delimited by semicolons - in fact, query statements must end with a
semicolon to be properly recognized by goose.
More complex statements (PL/pgSQL) that have semicolons within them must be annotated with -- +goose StatementBegin
and -- +goose StatementEnd
to be properly recognized. For example:
CREATE OR REPLACE FUNCTION histories_partition_creation( DATE, DATE )
returns void AS $$
DECLARE
create_query text;
BEGIN
FOR create_query IN SELECT
'CREATE TABLE IF NOT EXISTS histories_'
|| TO_CHAR( d, 'YYYY_MM' )
|| ' ( CHECK( created_at >= timestamp '''
|| TO_CHAR( d, 'YYYY-MM-DD 00:00:00' )
|| ''' AND created_at < timestamp '''
|| TO_CHAR( d + INTERVAL '1 month', 'YYYY-MM-DD 00:00:00' )
|| ''' ) ) inherits ( histories );'
FROM generate_series( $1, $2, '1 month' ) AS d
LOOP
EXECUTE create_query;
END LOOP;
END;
$$
language plpgsql;
Goose supports environment variable substitution in SQL migrations through annotations. To enable
this feature, use the -- +goose ENVSUB ON
annotation before the queries where you want
substitution applied. It stays active until the -- +goose ENVSUB OFF
annotation is encountered.
You can use these annotations multiple times within a file.
This feature is disabled by default for backward compatibility with existing scripts.
For PL/pgSQL
functions or other statements where substitution is not desired, wrap the annotations
explicitly around the relevant parts. For example, to exclude escaping the **
characters:
CREATE OR REPLACE FUNCTION test_func()
RETURNS void AS $$
BEGIN
RAISE NOTICE '${SOME_ENV_VAR}';
END;
$$ LANGUAGE plpgsql;
Supported expansions (click here to expand):
${VAR}
or $VAR - expands to the value of the environment variable VAR
${VAR:-default}
- expands to the value of the environment variable VAR
, or default
if VAR
is unset or null${VAR-default}
- expands to the value of the environment variable VAR
, or default
if VAR
is unset${VAR?err_msg}
- expands to the value of the environment variable VAR
, or prints err_msg
and
error if VAR
unset${VAR:?err_msg}
- expands to the value of the environment variable VAR
, or prints err_msg
and error if VAR
unset or null. THIS IS NOT SUPPORTED
See
mfridman/interpolate
for more details on supported expansions.
Embedded sql migrations
Go 1.16 introduced new feature: compile-time embedding files into
binary and corresponding filesystem abstraction.
This feature can be used only for applying existing migrations. Modifying operations such as fix
and create
will continue to operate on OS filesystem even if using embedded files. This is
expected behaviour because io/fs
interfaces allows read-only access.
Make sure to configure the correct SQL dialect, see dialect.go for supported SQL
dialects.
Example usage, assuming that SQL migrations are placed in the migrations
directory:
package main
import (
"database/sql"
"embed"
"github.com/pressly/goose/v3"
)
var embedMigrations embed.FS
func main() {
var db *sql.DB
goose.SetBaseFS(embedMigrations)
if err := goose.SetDialect("postgres"); err != nil {
panic(err)
}
if err := goose.Up(db, "migrations"); err != nil {
panic(err)
}
}
Note that we pass "migrations"
as directory argument in Up
because embedding saves directory
structure.
Go Migrations
- Create your own goose binary, see example
- Import
github.com/pressly/goose
- Register your migration functions
- Run goose command, ie.
goose.Up(db *sql.DB, dir string)
A sample Go migration 00002_users_add_email.go file
looks like:
package migrations
import (
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(Up, Down)
}
func Up(tx *sql.Tx) error {
_, err := tx.Exec("UPDATE users SET username='admin' WHERE username='root';")
if err != nil {
return err
}
return nil
}
func Down(tx *sql.Tx) error {
_, err := tx.Exec("UPDATE users SET username='root' WHERE username='admin';")
if err != nil {
return err
}
return nil
}
Note that Go migration files must begin with a numeric value, followed by an underscore, and must
not end with *_test.go
.
Hybrid Versioning
Please, read the versioning
problem first.
By default, if you attempt to apply missing (out-of-order) migrations goose
will raise an error.
However, If you want to apply these missing migrations pass goose the -allow-missing
flag, or if
using as a library supply the functional option goose.WithAllowMissing()
to Up, UpTo or UpByOne.
However, we strongly recommend adopting a hybrid versioning approach, using both timestamps and
sequential numbers. Migrations created during the development process are timestamped and sequential
versions are ran on production. We believe this method will prevent the problem of conflicting
versions when writing software in a team environment.
To help you adopt this approach, create
will use the current timestamp as the migration version.
When you're ready to deploy your migrations in a production environment, we also provide a helpful
fix
command to convert your migrations into sequential order, while preserving the timestamp
ordering. We recommend running fix
in the CI pipeline, and only when the migrations are ready for
production.
Credit
The gopher mascot was designed by Renée French / CC
3.0. For more info check out the Go
Blog. Adapted by Ellen.
License
Licensed under MIT License