GithubHelp home page GithubHelp logo

tern's Introduction

Tern - The SQL Fan's Migrator

Tern is a standalone migration tool for PostgreSQL. It includes traditional migrations as well as a separate optional workflow for managing database code such as functions and views.

Features

  • Multi-platform
  • Stand-alone binary
  • SSH tunnel support built-in
  • Data variable interpolation into migrations

Installation

go install github.com/jackc/tern/v2@latest

Creating a Tern Project

To create a new tern project in the current directory run:

tern init

Or to create the project somewhere else:

tern init path/to/project

Tern projects are composed of a directory of migrations and optionally a config file. See the sample directory for an example.

Configuration

Database connection settings can be specified via the standard PostgreSQL environment variables, via program arguments, or in a config file. By default tern will look in the current directory for the config file tern.conf and the migrations.

The tern.conf file is stored in the ini format with two sections, database and data. The database section contains settings for connection to the database server.

Values in the data section will be available for interpolation into migrations. This can help in scenarios where migrations are managing permissions and the user to which permissions are granted should be configurable.

If all database settings are supplied by PG* environment variables or program arguments the config file is not required. In particular, using the PGSERVICE can reduce or eliminate the need for a configuration file.

The entire tern.conf file is processed through the Go standard text/template package. Sprig functions are available.

Example tern.conf:

[database]
# host supports TCP addresses and Unix domain sockets
# host = /private/tmp
host = 127.0.0.1
# port = 5432
database = tern_test
user = jack
password = {{env "MIGRATOR_PASSWORD"}}
# version_table = public.schema_version
#
# sslmode generally matches the behavior described in:
# http://www.postgresql.org/docs/9.4/static/libpq-ssl.html#LIBPQ-SSL-PROTECTION
#
# There are only two modes that most users should use:
# prefer - on trusted networks where security is not required
# verify-full - require SSL connection
# sslmode = prefer
#
# "conn_string" accepts two formats; URI or DSN as described in:
# https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
#
# This property is lenient i.e., it does not throw error
# if values for both "conn_string" and "host/port/.." are
# provided. In this case, the individual properties will
# override the correspoding part in the "conn_string".
#
# URI format:
# conn_string = postgresql://other@localhost/otherdb?connect_timeout=10&application_name=myapp
# DSN format:
# conn_string = host=localhost port=5432 dbname=mydb connect_timeout=10

# Proxy the above database connection via SSH
# [ssh-tunnel]
# host =
# port = 22
# user defaults to OS user
# user =
# password is not required if using SSH agent authentication
# password =

[data]
prefix = foo
app_user = joe

This flexibility configuration style allows handling multiple environments such as test, development, and production in several ways.

  • Separate config file for each environment
  • Environment variables for database settings and optionally one config file for shared settings
  • Program arguments for database settings and optionally one config file for shared settings

In addition to program arguments, TERN_CONFIG and TERN_MIGRATIONS environment variables may be used to set the config path and migrations path respectively.

Migrations

To create a new migration:

tern new name_of_migration

This will create a migration file with the given name prefixed by the next available sequence number (e.g. 001, 002, 003). The -e flag can be used to automatically open the new file in EDITOR.

The migrations themselves have an extremely simple file format. They are simply the up and down SQL statements divided by a magic comment.

---- create above / drop below ----

Example:

create table t1(
  id serial primary key
);

---- create above / drop below ----

drop table t1;

If a migration is irreversible such as a drop table, simply delete the magic comment.

drop table widgets;

To interpolate a custom data value from the config file prefix the name with a dot and surround the whole with double curly braces.

create table {{.prefix}}config(
  id serial primary key
);

Migrations are read from files in the migration directory in the order of the numerical prefix. Each migration is run in a transaction.

Any SQL files in subdirectories of the migration directory, will be available for inclusion with the template command. This can be especially useful for definitions of views and functions that may have to be dropped and recreated when the underlying table(s) change.

// Include the file shared/v1_001.sql. Note the trailing dot.
// It is necessary if the shared file needs access to custom data values.
{{ template "shared/v1_001.sql" . }}
);

Tern uses the standard Go text/template package so conditionals and other advanced templating features are available if needed. See the package docs for details. Sprig functions are also available.

Migrations are wrapped in a transaction by default. Some SQL statements such as create index concurrently cannot be performed within a transaction. To disable the transaction include the magic comment:

---- tern: disable-tx ----

Migrating

To migrate up to the last version using migrations and config file located in the same directory simply run tern:

tern migrate

To migrate up or down to a specific version:

tern migrate --destination 42

To migrate up N versions:

tern migrate --destination +3

To migrate down N versions:

tern migrate --destination -3

To migrate down and rerun the previous N versions:

tern migrate --destination -+3

To use a different config file:

tern migrate --config path/to/tern.json

To use a different migrations directory:

tern migrate --migrations path/to/migrations

Renumbering Conflicting Migrations

When migrations are created on multiple branches the migrations need to be renumbered when the branches are merged. The tern renumber command can automatically do this. On the branch with the only migrations to keep at the lower numbers run tern renumber start. Merge the branches. Then run tern renumber finish.

$ git switch master
Switched to branch 'master'
$ ls
001_create_users.sql
002_add_last_login_to_users.sql

$ git switch feature
Switched to branch 'feature'
$ ls
001_create_users.sql
002_create_todos.sql

# Both branches have a migration number 2.

# Run tern renumber start on the branch with the migrations that should come first.

$ git switch master
Switched to branch 'master'
$ tern renumber start

# Then go to the branch with migrations that should come later and merge or rebase.

$ git switch feature
$ git rebase master
Successfully rebased and updated refs/heads/feature.
$ ls
001_create_users.sql
002_add_last_login_to_users.sql
002_create_todos.sql

# There are now two migrations with the same migration number.

$ tern renumber finish
$ ls
001_create_users.sql
002_add_last_login_to_users.sql
003_create_todos.sql

# The migrations are now renumbered in the correct order.

Code Packages

The migration paradigm works well for creating and altering tables, but it can be unwieldy when dealing with database code such as server side functions and views. For example, consider a schema where view c depends on view b which depends on view a. A change to a may require the following steps:

  1. Drop c
  2. Drop b
  3. Drop a
  4. Create a
  5. Create b
  6. Create c

In addition to the challenge of manually building such a migration it is difficult to use version control to see the changes in a particular database object over time when its definition is scattered through multiple migrations.

A solution to this is code packages. A code package is a directory with an install.sql file that contains the instructions to completely drop and recreate a set of database code. The command code install can be used to directly install a code package (especially useful during development) and the code snapshot command can be used to make a single migration that installs that code package.

For example given a directory code containing the following files:

-- install.sql
drop schema if exists code cascade;
create schema code;

{{ template "a.sql" . }}
{{ template "b.sql" . }}
{{ template "c.sql" . }}
-- a.sql
create view code.a as select ...;
-- b.sql
create view code.b as select * from code.a where ...;
-- c.sql
create view code.c as select * from code.b where ...;

Then this command would install the package into the database.

tern code install path/to/code --config path/to/tern.conf

And this command would create a migration from the current state of the code package.

tern code snapshot path/to/code --migrations path/to/migrations

Code packages have access to data variables defined in your configuration file as well as functions provided by Sprig.

It is recommended but not required for each code package to be installed into its own PostgreSQL schema. This schema could be determined by environment variable as part of a blue / green deployment process.

Template Tips

The env function can be used to read process environment variables.

drop schema if exists {{ env "CODE_SCHEMA" }} cascade;
create schema {{ env "CODE_SCHEMA" }};

The Sprig dictionary functions can be useful to call templates with extra parameters merged into the . value.

{{ template "_view_partial.sql" (merge (dict "view_name" "some_name" "where_clause" "some_extra_condition=true") . ) }}

SSH Tunnel

Tern includes SSH tunnel support. Simply supply the SSH host, and optionally port, user, and password in the config file or as program arguments and Tern will tunnel the database connection through that server. When using a SSH tunnel the database host should be from the context of the SSH server. For example, if your PostgreSQL server is pg.example.com, but you only have SSH access, then your SSH host would be pg.example.com and your database host would be localhost.

Tern will automatically use an SSH agent or ~/.ssh/id_rsa if available.

Embedding Tern

All the actual functionality of tern is in the github.com/jackc/tern/v2/migrate library. If you need to embed migrations into your own application this library can help. If you don't need the full functionality of tern, then a migration generator script as described below may be a easier way of embedding simple migrations.

Generating a Migration Generator SQL Script

Sometimes an application or plugin needs to perform migrations but it is not the owner of the database and tern is not available.

The gengen command generates a SQL script that when run against a database will generate a SQL script with the migrations necessary to bring the database to the latest schema version.

For example, a Go job queue library may need to perform database migrations, but it does not want to require its users to also use tern. In this case the plugin author would use gengen to create a SQL script that inspects that database and creates the actual SQL migration script. This script can then be integrated with whatever schema management system the host application is using.

Usage:

$ tern gengen > generate-migrations.sql
$ psql --no-psqlrc --tuples-only --quiet --no-align -f generate-migrations.sql mydb > migrations.sql

# migrations.sql now contains the commands to migrate mydb to the latest schema version. It can be run directly or
# integrated with another migrate system.

Limitations:

  • Every migration runs in a transaction. That is, if a migration has a disable-tx magic comment it will be ignored.
  • Migrations can only go forward to the latest version.

Running the Tests

To run the tests tern requires two test databases to run migrations against.

  1. Create a new database for main tern program tests (e.g. tern_test).
  2. Open testdata/tern.conf.example
  3. Enter the connection information.
  4. Save as testdata/tern.conf.
  5. Run tests with the connection string for the main tern program tests in the TERN_TEST_CONN_STRING environment variable.
  6. Create another database for the migrate library tests (e.g. tern_migrate_test).
  7. Run tests with the connection string for the migrate library tests in the MIGRATE_TEST_CONN_STRING environment variable
TERN_TEST_CONN_STRING="host=/private/tmp database=tern_test" MIGRATE_TEST_CONN_STRING="host=/private/tmp database=tern_migrate_test" MIGRATE_TEST_DATABASE=tern_migrate_test go test ./...

Prior Ruby Gem Version

The projects using the prior version of tern that was distributed as a Ruby Gem are incompatible with the version 1 release. However, that version of tern is still available through RubyGems and the source code is on the ruby branch.

Version History

2.2.1 (June 13, 2024)

  • Fix: handle dollar-quoted strings when splitting SQL statements (Krzysztof Szafrański)
  • Fix: code snapshot command uses TERN_MIGRATIONS environment variable
  • Fix: code snapshot command sets file permissions properly

2.2.0 (May 17, 2024)

  • Add gengen command
  • Upgrade dependencies
  • Fixes for SSH tunnel port

2.1.1 (June 17, 2023)

  • Upgrade to pgx v5.4.0 - This should resolve ssh tunnel issues
  • CLI tool sets application_name to "tern" by default
  • Fix FindMigrations panic with missing migrations (Billy Keyes)

2.1.0 (April 13, 2023)

  • Add print-connstring command to CLI (abs3ntdev)
  • Allow multiple config files on CLI (abs3ntdev)
  • Fix port being ignored in config file if sslmode is set (abs3ntdev)

2.0.1 (March 2, 2023)

  • Fix -e flag with terminal editors (abs3nt)

2.0.0 (February 23, 2023)

  • Remove deprecated env access syntax in config file
  • Replace MigratorFS interface with fs.FS
  • Upgrade to pgx v5
  • Upgrade to sprig v3
  • Add TERN_CONFIG and TERN_MIGRATIONS environment variables
  • Add -e flag to tern new to open file in EDITOR
  • Add per migration disable-tx option
  • Add renumber commands to help when merging branches that have conflicting migration numbers

1.13.0 (April 21, 2022)

  • Add conn string connection config option (vivek-shrikhande)
  • Add Filename to MigrationPgError (davidmdm)

1.12.5 (June 12, 2021)

  • Look for SSH keys in ~/.ssh/id_rsa (Miles Delahunty)

1.12.4 (February 27, 2021)

  • Use user's known_hosts file when connecting via SSH

1.12.3 (December 24, 2020)

  • Fix reported version number

1.12.2 (December 23, 2020)

  • Fix setting port from config file
  • Fix non-schema qualified version table not in public but in search path (Tynor Fujimoto)

1.12.1 (June 27, 2020)

  • Update to latest version of pgx.

1.12.0 (June 26, 2020)

  • Command code install no longer outputs compiled SQL.
  • Add code compile command to print compiled SQL code package.
  • Better error reporting for code install.

1.11.0 (April 10, 2020)

  • Add Sprig functions to configuration file and migrations.
  • Add SQL code management distinct from migrations.

1.10.2 (March 28, 2020)

  • CLI now handles SIGINT (ctrl+c) and attempts to cancel in-progress migration before quitting

1.10.1 (March 24, 2020)

  • Fix default CLI version-table argument overriding config value

1.10.0 (March 7, 2020)

  • Better locking to protect against multiple concurrent migrators on first run
  • Update pgx version to support PostgreSQL service files

1.9.1 (February 1, 2020)

  • Look for version table in all schemas in search_path instead of just the top schema

1.9.0 (February 1, 2020)

  • Default version table is explicitly in public schema
  • Update to pgx v4 (Alex Gaynor)

1.8.2 (July 19, 2019)

  • Show PostgreSQL error details
  • Rename internal error type

1.8.1 (April 5, 2019)

  • Issue reset all after every migration
  • Use go modules instead of Godep / vendoring

1.8.0 (February 26, 2018)

  • Update to latest version of pgx (PostgreSQL driver)
  • Support PGSSLROOTCERT
  • Fix typos and internal cleanup
  • Refactor internals for easier embedding (hsyed)

1.7.1 (January 30, 2016)

  • Simplify SSH tunnel code so it does not listen on localhost

1.7.0 (January 17, 2016)

  • Add SSH tunnel support

1.6.1 (January 16, 2016)

  • Fix version output
  • Evaluate config files through text/template with ENV

1.6.0 (January 15, 2016)

  • Optionally read database connection settings from environment
  • Accept database connection settings via program arguments
  • Make config file optional

1.5.0 (October 1, 2015)

  • Add status command
  • Add relative migration destinations
  • Add redo migration destinations

1.4.0 (May 15, 2015)

  • Add TLS support

1.3.3 (May 1, 2015)

  • Fix version output

1.3.2 (May 1, 2015)

  • Better error messages

1.3.1 (December 24, 2014)

  • Fix custom version table name

1.3.0 (December 23, 2014)

  • Prefer host config whether connecting with unix domain socket or TCP

1.2.2 (May 30, 2014)

  • Fix new migration short name

1.2.1 (May 18, 2014)

  • Support socket directory as well as socket file

1.2.0 (May 6, 2014)

  • Move to subcommand interface
  • Require migrations to begin with ascending, gapless numbers
  • Fix: migrations directory can contain other files
  • Fix: gracefully handle invalid current version
  • Fix: gracefully handle migrations with duplicate sequence number

1.1.1 (April 22, 2014)

  • Do not require user -- default to OS user

1.1.0 (April 22, 2014)

  • Add sub-template support
  • Switch to ini for config files
  • Add custom data merging

1.0.0 (April 19, 2014)

  • Total rewrite in Go

0.7.1

  • Print friendly error message when database error occurs instead of stack trace.

0.7.0

  • Added ERB processing to SQL files

License

Copyright (c) 2011-2014 Jack Christensen, released under the MIT license

tern's People

Contributors

abs3ntdev avatar alex avatar arthurdotwork avatar bluekeyes avatar davidmdm avatar hsyed avatar jackc avatar josefwn avatar kamikazechaser avatar kszafran avatar lzap avatar magnushoerberg avatar mdelah avatar michaelknowles avatar muhlemmer avatar odyfey avatar perrito666 avatar rafabulsing avatar tynor avatar vivek-shrikhande avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tern's Issues

Always use a schema for expressing the schema version table

With the currently unprefixed schema_version table the following sequence of events is possible.

  • First tern run creates schema_version in public
  • You run a migration that creates a schema with the same name as the user
  • Next run of tern now creates schema_version in $user
  • tern now attempts to re-apply migrations from 0, potentially causing havoc

Simply specifying versionTable: "public.schema_version" solves this. Using a pgx.Identifier for versionTable in type Migrator will encourage people to do this.

Merging a branch with duplicate migration numbers

It is inconvenient to merge a branch when there are migrations with duplicate numbers. Rails resolved this by moving to timestamp migrations. I dislike this approach because it makes the order migrations are applied non-deterministic. However, it would be nice to have a simpler way to resolve this than manually renaming the offending files.

Broadly speaking there are two directions to go with this.

  1. Integrate with version control to determine which files come from which branch. On the plus side it could probably resolve a whole set of migrations with minimal human involvement. On the down side this seems complicated, may add dependencies, and needs to be built for each VCS. It also wouldn't help if duplicate numbers were introduced somewhere other than in a merge.
  2. An interactive renumber mode. Something like git add --patch where it shows each conflict and asks which one comes first.

Allow override of config file

It would be great if it was possible to override values in the config file via environment variables or flags passed to tern during invocation.

For example, given a standard tern config, override host via:

PGHOST=foo tern migrate

or

tern --pg-host=foo migrate

This is very useful for certain environments such as CI where the database could be run in a linux container that is linked into the container currently running the test suite. For example, in Docker when you link two containers it exports certain environment variables, such as $PG_PORT_5432_TCP_ADDR. It would be great to invoke tern like so:

PGHOST=$PG_PORT_5432_TCP_ADDR tern migrate or via the flag approach above.

Can we specify migration metadata table name?

Hi,
we have 3 microservices and I would like to use tern with them. They share same database but different schemas and tables.

Can we specify the tablename used by tern to record migrations?

Ignored `port` from config in Windows

Windows 11 Pro N, 21H2
Tern: 1.12.5
PostgreSQL 13.4 in Docker for Windows.
Docker uses WSL2 as a backend.
WSL OS: Fedora Server 34.
Port mapping in docker enabled: 5432 container -> 5431 host.

I built two different binaries: for unix:amd/64, windows:amd/64.
If I run an unix's binary it's OK. It can connect, do necessary work, etc.
If I run window's binary I getting an error "Failed to connect" and I see a default pg port: 5432, not those which is specified in config.


Output:

iyuryevich@ElevenStation ~/Development/___/migrations/ru (master+*?) $ tern.exe status
Unable to connect to PostgreSQL:
  failed to connect to `host=127.0.0.1 user=test1 database=test_dev_01`: dial error (dial tcp 127.0.0.1:5432: connectex: No connection could be made because the target machine actively refused it.)
iyuryevich@ElevenStation ~/Development/___/migrations/ru (master+*?) $ tern status
status:   up to date
version:  4 of 4
host:     127.0.0.1
database: test_dev_01
iyuryevich@ElevenStation ~/Development/___/migrations/ru (master+*?) $ tern.exe version
tern v1.12.5
iyuryevich@ElevenStation ~/Development/___/migrations/ru (master+*?) $ tern version
tern v1.12.5
iyuryevich@ElevenStation ~/Development/___/migrations/ru (master+*?) $

tern.conf

[database]
host = 127.0.0.1
port = 5431
database = test_dev_01
user = test1
password = root
version_table = schema_version

orthogonal migration support (multiple migrations).

This one is connected to #6. We are building a JS framework ontop of plv8, one of the things we need to do is manage migrations for parts of the JS separately from the schema elements. The JS would be upserted into the tables when a version bump occurs.

For this I need to manage versions -- tern's version table does not contain a migration name.

If we can add a name field to the table it would enable the following method:

func (m*Migrator) UserMigration(name string, currentVersion uint8, um function(*pgx.Conn) error) (previous uint8, err error) {
   // The user function is reached if currentVersion > version in db table.
   // if this returns no error than the db version table row with name is updated to currentVersion
}

The version table could be migrated to contain a name field and the legacy nameless migrations could be given the name "default" ?

I know this is probably crossing the use-case boundaries. But, since pgx does so much more than an average pg driver -- perhaps it makes sense.

Tern installation doesn't seem to work as described

Not sure why, but I tried: go get -u github.com/jackc/tern in my project root, which has go.mod, etc.. and in my go.mod I see tern is added. However, running tern tells me its not installed. Does this not work when running it in a go mod based project? I also tried that in an empty directory, still no go. Is the above supposed to install it somehow so that it runs anywhere I am at?

It would be fantastic to see more details around how it may be used in a CI/CD setup, how would a developer typically use it in say a microservice or what not.. and how it would be incorporated in a new empty env that has a fresh install of the DB but nothing yet created.

[Question] pg_advisory_lock not supported by CockroachDB

Greetings,

This is my favorite migration tool. However, I just started a new project with CockroachDB, and on migration I get a warning

Error initializing migrator:
  ERROR: unknown function: pg_advisory_lock() (SQLSTATE 42883)

It looks like there are a couple places where the function is used in migrate.go. Is it your opinion that this lock function is critical to the library? It seems like cockroach always uses SERIALIZABLE isolation.

Expose CLI as an importable package

I want to embed migration logic into my application so I don't need to install tern into the container separately.

I can do that by importing github.com/jackc/tern/migrate and creating a new migrator like is done in the tern CLI:

tern/main.go

Line 411 in 7208d82

migrator, err := migrate.NewMigrator(ctx, conn, config.VersionTable)

However, I don't really need to customize that process in any way. For my purposes I'd be just copying most of func Migrate from that file.

It would be a nicer alternative if I could do this:

args := [0]string{}
tern.Migrate(cobra.Command{}, args)

In fact, my use case would be to just pass the args from my own cobra command which is powering my CLI:

func RunMigrate(cmd *cobra.Command, args []string) {
	tern.Migrate(cmd, args)
}

So I could run my-command migrate ... which would just run tern.

Would you consider supporting this and/or approving a PR?

Confirmation step for production

It might be nice to optionally have some sort of guard rail or required confirmation step for production migrations. I typically use ctrl+r to get my migrate command from my shell history. It is too easy to accidentally get and run the production migrate command when wanting to migrate development.

It would be nice to have a confirmation step that required typing the name of the environment.

This could be toggled in the config files locally or with a special table remotely. Local config might be simpler, but it could be more difficult when deploy target is determined by environment variable or program argument. Also, if it was a remote table it could not be bypassed as easily.

Another approach to conflicting migrations

Would be awesome if tern has the option to choose how prefix number is generated when create a migration file.
To avoid migration conflicts, "rails like" approach is more simple and effective.
For example, instead generate a sequential number, tern would generate a timestamp numbered prefix, e.g. 20220512190701_create_foo

Windows support

How can I use tern command in the windows?
Maybe, how can I create alias in windows?

Remove explicit transactions from executing a migration

tern runs each migration within a transaction to ensure that the version table is also updated with the migration.

This can be inconvenient when running a migrations in an existing Tx context (#4) or when a migration needs to manage its own transactions (such as large data updates that are executed in multiple steps to avoid a long lock). #5 added the ability to disable transactions from code but it was never exposed to the CLI (#36).

I think I may have come up with an improvement that removes transactions and thereby any ability or interface to disable them.

The entire text of a migration is sent at once via the simple query protocol. The simple query protocol implicitly transactional unless the SQL contains explicit transaction control statements.

The only reason tern used transactions to begin with is to ensure that the migration and the update of the version table is atomic. But what if tern appended the update version table statement to the SQL of the migration before sending it all via the simple query protocol. The whole thing would be implicitly transactional, but migrations could still use begin and commit.

The only concern I have is if there are any SQL migration that somehow could be broken by appending this to it:

; -- in case migration SQL did not terminate last statement (legal in simple query protocol)
reset all;
update schema_version set version=42; -- this line would be generated to put in the table and version

I can't think of any way it could break, but I'd like to get some feedback on the idea.

Pre-built binaries

Right now tern is installable via the go toolkit (go install). It would be good to have trusted pre-built binaries for a few platforms e.g. amd64, arm64 hosted on this repo via Github. This will allow people to fetch the binaries into their workflows or deployment/release cycle e.g. Docker images or Kubernetes init container.

I can send in a PR for this. Starting with amd64 with the following build options:

  • flags: -s -w
  • env: CGO_ENABLED=0

Let me know if this is within the scope of this repo.

Related: #56

Another proposal for selectively disabling transactional migrations

See #36 and #17 (comment) for more of the origin of this request.

Some commands cannot be run in a migration. e.g. create index concurrently. Even if DisableTx is used only one statement can be given per migration because the entire migration is sent as a single simple protocol query which is implicitly transactional. See #43 for a failed idea of removing explicit transactions.

There are two major problems.

  1. DisableTx is awkward to use and is a a blunt instrument. i.e. it is not controllable per migration. I propose replacing that with a magic comment per migration. Something like ---- disable-tx ---- at the top.
  2. The only way I can see to have multiple statements in a single migration that is not implicitly transactional is to parse the migration and send each SQL statement as its own simple protocol query message. I'm not super eager to require a SQL parser, but in this case all it needs to do is split each statement. It doesn't need to actually understand the statement. I've already written a parser for pgx to sanitize SQL parameters which detects comments and quoted strings. It might be possible to adapt that to split on any ; that is not in a comment or quote without too much effort.

Shell script migrations

It might be interesting for tern to have migrations that are simply shell scripts that can do arbitrary tasks. tern could act as a proxy to the PostgreSQL server and expose a pseudo connection via the PG* or DATABASE_URL environment variables. Then a migration could have any logic written in any language (even just using psql could be really valuable).

Tern uses out of date version of pgx

Tern is currently using v3 of pgx. This is mostly a problem because it makes it impossible to use the migrate library in conjunction with an up to date version of pgx. Is there any reason not to quickly update this to v4?

Race condition with initial migration

err = m.ensureSchemaVersionTableExists(ctx)
is run without locking, which means that

tern/migrate/migrate.go

Lines 372 to 377 in 1ae9188

_, err = m.conn.Exec(ctx, fmt.Sprintf(`
create table if not exists %s(version int4 not null);
insert into %s(version)
select 0
where 0=(select count(*) from %s);
can fail if multiple migrations are running at the same time.

Here's an example log where three Go services are all running migration for an empty database:

2020-03-03 12:33:43.974 UTC [72] ERROR:  relation "version" does not exist at character 21
2020-03-03 12:33:43.974 UTC [72] STATEMENT:  select version from version
2020-03-03 12:33:43.980 UTC [74] ERROR:  relation "version" does not exist at character 21
2020-03-03 12:33:43.980 UTC [74] STATEMENT:  select version from version
2020-03-03 12:33:43.984 UTC [74] ERROR:  duplicate key value violates unique constraint "pg_type_typname_nsp_index"
2020-03-03 12:33:43.984 UTC [74] DETAIL:  Key (typname, typnamespace)=(version, 2200) already exists.
2020-03-03 12:33:43.984 UTC [74] STATEMENT:
create table if not exists version(version int4 not null);
|  
| insert into version(version)
| select 0
| where 0=(select count(*) from version);

for the caller it seems like the migration is failing, even though one of the migrations is running and can succeed.

I would expect that this part is also under the migration advisory lock. Or that the error can be ignored by the caller, e.g. using a specific error value.

Archiving old migrations

After hundreds of migrations the migrations directory can get a little awkward to scroll through. It would be nice to have some way to move old migrations to a subdirectory to get them out of the way.

Configurable amount of leading 0s for migration files

I've noticed that the amount of leading zeros is hard-coded to 3.

While 999 is a lot, for a long-lived project, it's quite possible that it won't be enough. I did a test, and after going over that limit, tern new a creates 1000_a.sql, but then running tern new a again fails with the following message:

Error loading migrations:
  Missing migration 100

Configuration could be done via cli flag or .conf file. It could also be inferred from the other files in the migration folder (would require manual editing of the first migration to set the wanted number of leading zeros, but would work automatically from then on).

If nothing else, at least a better error message for the 1001st migration would be nice, as it now is it's a bit misleading.

I could try my hand at implementing this feature, if need be!

Feature: Docker image

For my projects I'm usually building a Docker image with the migration scripts included. Now I'm building images from scratch in various projects, but in order to safe some CI minutes I'm looking to build a single "tern" base image for all of my projects.

If you are interested in a Docker image for this repository, I can commit a Dockerfile here and (help) setup a CI environment. This can be either Github actions, or directly building it on Docker hub.

Feature Request: Parse entire DSN from environment variable

A useful feature might be to allow parsing the DSN fully from the environment, as a decent number of services expose something like DATABASE_URL which is often in a url format. In those cases, it's more difficult to manually parse out the pieces into a config file.

session-level only advisory lock

Hi

tern uses session level advisory lock that may be a problem for systems with pgbouncer, cause pgbouncer reuses session.
It would be great to have ability to use transaction level advisory locks.

I've forked the project and tried to change advisory lock type, but it can't be done without serious changes of the project structure.
It may be worth to do locking at higher level (not inside MigrateTo & ensureSchemaVersionTableExists), to allow user to select correct locking strategy for their infrastructure.

Thanks.

What is best strategy to create trigger?

Hello,
What is best strategy to create trigger?
I'm trying to create trigger in same migration file where create table is:

-- This is a sample migration.
create table people(
  id serial primary key,
  first_name varchar not null,
  last_name varchar not null,
  created_at timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at timestamp without time zone,
);

CREATE TRIGGER set_update_timestamp
    BEFORE UPDATE 
    ON people
    FOR EACH ROW
    EXECUTE PROCEDURE trigger_set_timestamp();

---- create above / drop below ----

drop table people;

I got error:
ERROR: syntax error at or near ")" (SQLSTATE 42601)
LINE 9: )
^

pre-/post-migration hooks from Go package

We'd like the ability to run arbitrary Go code as part of (before or after) a migration. One key point is that we want to be able to use the same Tx as the rest of the sql, so that the entire change is atomic.

I'd be happy to speculate about API and potentially contribute code, but I wanted to check in about the idea first.

Note that this could be a building block for #47, particularly if combined with https://github.com/mvdan/sh (which has the downside of being quite a large dependency).

Thanks!

Running migrations in a schema

I'd like to run all migrations in a specific schema; the "obvious" way to do this is something like:

db, err := pgx.Connect(ctx, "dbname=test")
_, err := db.Exec(ctx, "set search_path to myschema")

m, err := migrate.NewMigratorEx(...)

The problem with this is that MigrateTo() calls reset all over here: https://github.com/jackc/tern/blob/master/migrate/migrate.go#L372-L373

This resets the search_path and only the first migration will work (quite confusing!) The reason that reset all is in there is probably because people set the search_path in their migrations, in which case you don't want it to carry over to the next migration.

As a workaround, doing it in OnStart works:

m.OnStart = func(int32, string, string, string) {
    _, err = db.Exec(context.Background(), "set search_path to myschema")
    if err != nil {
        panic(fmt.Errorf("Migrate: %w", err))
    }
}

The downside of this is that there's no good way to signal errors from there, except a panic.


Not sure what the best way to fix this would be; adding an error return to OnStart isn't compatible, but could maybe add a SearchPath option? Or get the current search_path before running migrations and resetting it to that after every migration?

go-bindata support

I am embedding migrations into the product I am working on. I could do with a go-bindata support in the Migrator. go-bindata generated files have no dependencies -- which fits with pgx / tern.

Small bug with file suffixes (duplicate sql suffix)

This probably is not considered a bug but it is a small inconvinience nevertheless and it is at the end the user's fault anyway.

when creating a new migration with the tern new command if you accidentally write the .sql suffix yourself then the file name will have the .sql suffix twice

Example

when running the following command:

tern new create_user.sql

the file it would create would be named: 001_create_user.sql.sql

Add support for fs.FS

Since go1.16 is released now I just added this to a project to integrate with embed.FS.

It would probably be handy to have built in and io.fs probably won't be going anywhere soon.

Something like this maybe with an exported constructor function. Maybe with build in support to specify a subdirectory NewIOFSMigratorFS(fs.FS) would make it clear which migratorfs it is but that name is kind of a mouthful.

type iofsMigratorFS struct{ fsys fs.FS }

func (m iofsMigratorFS) ReadDir(dirname string) ([]fs.FileInfo, error) {
	de, err := fs.ReadDir(m.fsys, dirname)
	if err != nil {
		return nil, err
	}
	var res []os.FileInfo
	for _, v := range de {
		fi, err := v.Info()
		if err != nil {
			return nil, err
		}
		res = append(res, fi)
	}
	return res, nil
}

func (m iofsMigratorFS) ReadFile(filename string) ([]byte, error) {
	return fs.ReadFile(m.fsys, filename)
}

func (m iofsMigratorFS) Glob(pattern string) (matches []string, err error) {
	return fs.Glob(m.fsys, pattern)
}

SQL error should include detail

ERROR:  new row for relation "sometable" violates check constraint "check_something"
DETAIL:  Failing row contains (1151, 0, 2019-07-18 11:36:26.853391-05, 2019-07-18 11:36:26.853391-05).

instead of

ERROR:  new row for relation "sometable" violates check constraint "check_something"

port ignored in tern.conf

System: macOS Monterey version 12.4
tern version: 1.13.0

I'm currently facing a weird issue where port is being ignored in tern.conf. I specified it to be 9200 and tern still uses 5432. I have a docker-compose file that looks like this:

version: "3"
services:
  db:
    container_name: postgres
    restart: always
    image: postgres
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: tern_test
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    ports:
      - "9200:5432"
volumes:
  postgres_data:

I started this up in a separate terminal with docker-compose up, I can confirm the pg server is up and running and listening for connections. I then have the following tern.conf:

[database]
host = 127.0.0.1
port = 9200
database = tern_test
user = postgres
password = {{env "MIGRATOR_PASSWORD"}}
sslmode = prefer

When I run tern with MIGRATOR_PASSWORD=postgres tern migrate, I get the following error:

Unable to connect to PostgreSQL:
  failed to connect to `host=127.0.0.1 user=postgres database=tern_test`: dial error (dial tcp 127.0.0.1:5432: connect: connection refused)

If I change my docker-compose.yml to expose it on local port 5432, it works of course. I've also tried other ports and it only uses 5432. I'm using macOS Monterey version 12.4 (I saw another issue here specific to Windows, not sure if the OS is even relevant here).

SSH tunnel fails: "ssh: must specify HostKeyCallback"

This is what I did:

  1. Installed go and tern
  2. Configured a tern.conf file with [database] and [ssh-tunnel] entries
  3. Added SSH private key to ssh-agent
  4. Created a dummy migration file
  5. Tried to execute tern migrate but got the following error:
Unable to connect to PostgreSQL:
  ssh: must specify HostKeyCallback

Seems that from version 1.8.3, Go made some braking changes on SSL connections: golang/go#19767

Wrapping migration files in transactions

Hello,

since tern use transactions for each migration, is it safe to wrap each migration file between BEGIN/COMMIT statements? We are migrating from golang-migrate to tern and we have these, so I wonder if I need to remove them. It appears to work just fine, but would love to hear an official word :)

Thanks!

Add support for letting client manage the transaction for a migration.

Thanks for a great job on the driver. I am currently using the migration package directly. I need to manage the transaction a migration runs in as I am creating tenant schemas and stuff has to occur before and after the transactions. I am wondering if there is some way to detect if the connection already has a transaction associated with it -- does the TxStatus byte provide any clues ?.

If not could we get a new constructor NewMigratorNoTX that makes MigrateTo not use a Tx ? Happy to provide a PR.

Purpose of MigratorOptions.FileSystem (v2-dev)

Hey,

I am looking into the v2-dev branch a bit and I have some concerns about the FileSystem value from the MigratorOptions type. What is it used for? It looks like both LoadMigrations and FindMigrations function do take fs.FS argument already. From the history it looks like it was part of the type, so it is perhaps a leftover? Is the intention to keep using the config type, or to pass the filesystem as arguments?

Thanks!

Add migrations path to config

My use case: I always run tern <command> --migrations ./migrations ... so the config is in the root of the project and migrations are in ./migrations.
So I would like to omit --migrations ./migrations when calling commands. What if we can have it in the config?

Broken tests

Since the dep bump to pgx/v4 most of the tests are broken.

Functions like NewMigrator have a dependency on V3 of pgx, where in other parts of the tests we seem to have a rogue context injected into function calls m.MigrateTo(context.Background(), 3)

Run Migration not in a transaction

I can see in package migrate that there is an option to DisableTx,
However I can't figure out how to disable them when simply calling the tern command line.
If I have 5 migration files, and 1 of them should not be run inside of a transaction, how do I do this?

Roll up migrations

Sometimes it is convenient to roll up or flatten a range of migrations to a single migration. See #50 (comment) for initial request. I have also personally flattened migrations by hand multiple times.

There are two advantages to this feature.

  1. Initial database setup may be quicker.
  2. If the original migration files are removed it would declutter the migration directory. However, #50 could also solve this.

There are also a few downsides or tricky issues.

There is no way to perfectly roll up a set of migrations. When I have done this by hand I have used pg_dump to generate the flattened migration. However, migrations can depend on the database settings (default collations, permission grants, etc.), tern config variables, and environment variables. pg_dump freezes the results. This means the roll up migration could do something different than the original migrations.

The other is what to do with down migrations. If migrations 1 - 50 are rolled up into one migration what happens when we migrate down to 49. Or for that matter try to migrate up from 0 to a version less than 50. That would require keeping the old migrations around and deciding whether it is possible to use a roll up migration or fall back to the original migrations. Possible, but messy. Hmm... maybe down migrations always use the original migrations and up migrations only use a roll up if starting from 0 and going at least up to the end of the roll up.

Definitely a nice to have, but I'm not sure how much complexity it would introduce and if that complexity would be worth it.

port in config file not respected

I have a non-standard port for a postgres server and specifying it in the tern.conf file did not pass it through to the connection properly (it still tried to use 5432). Specifying the custom port with --port on the command line did work.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.