tern

repository·master·Indexed 23 days ago

https://github.com/jackc/tern

A standalone migration tool for PostgreSQL that supports traditional SQL migrations and a specialized 'code package' workflow for managing complex database objects like functions and views. It provides commands for migrating to specific versions, generating new migration files, checking status, and baselining existing databases via version overrides. Tern supports configuration through tern.conf or CLI flags, including SSH tunneling and Go text/template interpolation for dynamic SQL generation.

Tokens
4.7K
Snippets
12
Records
26
Agent score
78%

What's inside tern

  1. Manage database code with Code Packages

    master

    For complex database objects like views and functions that have inter-dependencies, use Code Packages instead of standard migrations. A code package is a directory containing an install.sql file.

    Key Commands:

    • tern code install <path>: Directly installs the code package into the database (useful for development). It executes the install.sql file which should contain logic to drop and recreate the objects.
    • tern code snapshot <path>: Creates a new migration file that, when run, installs the code package. This is the preferred way to deploy code package changes via standard migrations.

    Example Structure:

    code/
    ├── install.sql
    ├── a.sql
    ├── b.sql
    └── c.sql

    install.sql typically uses {{ template "filename.sql" . }} to include the component files.

    tern code install path/to/code --config path/to/tern.conf
    tern code snapshot path/to/code --migrations path/to/migrations
  2. Run Tern tests locally

    master

    To run the tests for Tern and its migrate library, you must provide two separate test databases and set the corresponding connection strings via environment variables.

    1. Create a database for the main program (e.g., tern_test).
    2. Create a database for the migrate library (e.g., tern_migrate_test).
    3. Configure testdata/tern.conf.example with your connection info and save it as testdata/tern.conf.
    4. Run the tests using the following command structure:
    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 ./...
  3. Initialize a new Tern project

    master

    To create a new Tern project, use the init command. This sets up the directory structure for migrations and configuration.

    To initialize in the current directory: tern init

    To initialize in a specific path: tern init path/to/project

    tern init
    # or
    tern init path/to/project
  4. Resolve migration conflicts with renumber

    master

    When multiple branches have migrations with the same sequence number, use the renumber workflow to fix them after a merge.

    Workflow:

    1. On the branch containing the migrations that should appear first (lower numbers), run: tern renumber start.
    2. Merge or rebase your feature branch into this branch.
    3. On the merged branch, run: tern renumber finish.

    This will automatically re-sequence the migrations into a correct, non-conflicting order.

    tern renumber start
    # ... merge/rebase ...
    tern renumber finish
  5. Baseline an existing database with override-version

    master

    If you are using Tern on a database that already has a schema, you must 'baseline' it so Tern doesn't try to recreate existing objects.

    Workflow:

    1. Write a migration file that represents your current schema state.
    2. Run tern override-version <N> where <N> is the sequence number of that migration. This sets the version in the internal version table without executing the SQL.
    3. Subsequent tern migrate calls will now work normally starting from version <N>.

    Warning: You cannot use tern migrate -d <lower> to roll back through migrations that were skipped via override-version, as the 'down' SQL will attempt to drop objects that were never actually created by Tern.

    tern override-version 1
  6. Create and write SQL migrations

    master

    Migrations are SQL files prefixed by a sequence number (e.g., 001_name.sql).

    Creating a migration: Run tern new <name>. Use the -e flag to open the new file in your default editor immediately.

    Migration Format: Migrations use a magic comment to separate the 'up' (create) and 'down' (drop) logic:

    -- SQL to run when migrating up
    CREATE TABLE users (id serial PRIMARY KEY);
    
    ---- create above / drop below ----
    
    -- SQL to run when rolling back
    DROP TABLE users;

    Special Rules:

    • Irreversible migrations: If a migration has no rollback (e.g., DROP TABLE), simply omit the magic comment.
    • Disabling Transactions: By default, each migration runs in a transaction. To run a migration without a transaction (required for commands like CREATE INDEX CONCURRENTLY), add the magic comment ---- tern: disable-tx ---- at the top of the file.
    • Data Interpolation: Access values from the [data] section of tern.conf by prefixing the key with a dot: {{.prefix_name}}.
  7. Configure Tern using tern.conf

    master

    Tern uses an ini format configuration file named tern.conf. It supports two main sections:

    1. [database]: Contains connection settings for the PostgreSQL server (e.g., host, port, database, user, password, sslmode, or a conn_string).
    2. [data]: Contains key-value pairs that are available for interpolation into your SQL migrations using Go text/template syntax.

    Key Features:

    • Templating: The entire file is processed via Go's text/template package. Sprig functions are available.
    • Environment Variables: You can use {{env "VAR_NAME"}} to inject environment variables into the config.
    • Connection Flexibility: You can use standard PostgreSQL environment variables (like PGSERVICE), program arguments, or the tern.conf file. If all settings are provided via PG* env vars or arguments, a config file is not required.
    • SSH Tunneling: You can proxy connections via SSH by providing [ssh-tunnel] settings in the [database] section.
    [database]
    host = 127.0.0.1
    database = tern_test
    user = jack
    password = {{env "MIGRATOR_PASSWORD"}}
    sslmode = prefer
    
    [data]
    prefix = foo
    app_user = joe
  8. Use special syntax for destination versions

    master

    When specifying a destination version in Tern commands, you can use shorthand syntax instead of absolute version numbers:

    • last: Targets the most recent migration available.
    • +N: Targets currentVersion + N (moves forward N steps).
    • -N: Targets currentVersion - N (moves backward N steps).
    • N: Targets the specific absolute version number N.
  9. Use Code Packages for dynamic migrations

    master

    Tern supports "code packages" that allow you to generate SQL dynamically using code.

    Workflow:

    1. Snapshot: Use tern code snapshot PATH to take a directory of code and save it as a snapshot in your migrations folder. This creates a migration file containing an {{ install_snapshot "ID" }} template tag.
    2. Install: Use tern code install PATH to evaluate the code package and execute the resulting SQL against the database.
    3. Compile: Use tern code compile PATH to see the SQL that a code package would generate without executing it.

    Code packages use the [data] section from your configuration for template evaluation.

  10. Configure database connection via Config

    master

    Tern uses a Config object to manage database connections and migration settings. Configuration can be loaded from files (which populate PGEnvvars and VersionTable) or overridden via CLI arguments.

    Key Configuration Fields:

    • PGEnvvars: A map of PostgreSQL environment variables (e.g., PGHOST, PGPORT, PGUSER, PGPASSWORD, PGDATABASE, PGSSLMODE, PGSSLROOTCERT).
    • VersionTable: The name of the table used to track migration versions (defaults to public.schema_version).
    • Data: A map used to pass arbitrary data to the migrator.
    • SSHConnConfig: Configuration for connecting via an SSH tunnel (Host, Port, User, Password, KeyFile, Passphrase).
    • ConnString: A direct PostgreSQL connection string that can be parsed using pgx.ParseConfig.
  11. Embed Tern migrations in a Go application

    master

    If you need to integrate migrations directly into your Go application, use the github.com/jackc/tern/v2/migrate library. You can define migrations using Go functions (UpFunc, DownFunc), pure SQL (UpSQL, DownSQL), or a combination of both.

    To use the library, create a migrate.Migrator instance with a database connection (e.g., from pgx) and a version table name. Then, populate the Migrations slice with migrate.Migration objects and call m.Migrate(ctx).

    Note: Ensure the necessary PostgreSQL environment variables (PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD) are set for the connection to work.

    	// Note: requires the right mix of environment variables to be set: PGHOST, PGPORT, PGDATABASE,
    	// PGUSER, PGPASSWORD.
    	conn, _ := pgx.Connect(ctx, "")
    	m, _ := migrate.NewMigrator(context.Background(), conn, "my_schema_version")
    
    	m.Migrations = []*migrate.Migration{
    		// Migration that uses Go functions.
    		{
    			Sequence: 1,
    			Name:     "1",
    			UpFunc: func(ctx context.Context, conn *pgx.Conn) error {
    				_, err := conn.Exec(ctx, "CREATE TABLE tmp (id INT);")
    				return err
    			},
    			DownFunc: func(ctx context.Context, conn *pgx.Conn) error {
    				_, err := conn.Exec(ctx, "DROP TABLE tmp;")
    				return err
    			},
    		},
    		// Migration that uses SQL.
    		{
    			Sequence: 2,
    			Name:     "2",
    			UpSQL:    `CREATE TABLE tmp2 (id INT);`,
    			DownSQL:  `DROP TABLE tmp2;`,
    		},
    
    		// Migration that uses both Go function and SQL.
    		{
    			Sequence: 3,
    			Name:     "3",
    			UpFunc: func(ctx context.Context, conn *pgx.Conn) error {
    				_, err := conn.Exec(ctx, "CREATE TABLE tmp3 (id INT);")
    				return err
    			},
    			DownSQL: `DROP TABLE tmp3;`,
    		},
    	}
    
    	_ = m.Migrate(ctx)