Depot.Migration
Depot.Migration: migration DDL module for generating SQL statements.
Functions generate SQL DDL strings and log operations to a Vault-backed migration log. No real DB yet — functions return the SQL they would execute.
Usage:
Depot.Migration.create_table("users", {
id = ("UUID", { primary_key: true }),
name = "String",
email = ("String", { null: false }),
role = ("String", { default: "user" })
})
Depot.Migration.create_index("users", ["email"], { unique: true })
Depot.Migration.drop_table("old_table")Functions
Generate ALTER TABLE statement(s).
changes is a record with optional fields: add — column record (same format as create_table columns) modify — column record (generates ALTER COLUMN ... TYPE ...) remove — list of column name strings
Depot.Migration.alter_table("users", {
add: { avatar: "String" },
remove: ["legacy_field"]
})Return the list of applied migration versions.
Depot.Migration.applied_migrations()Read the list of applied migration versions from schema_migrations, ordered ascending. Ensures the tracking table exists.
Generate a CREATE INDEX statement.
columns is a list of column name strings. opts is a record with optional fields: unique — Bool (default false) name — custom index name string using — index method string, e.g. "gin", "gist", "btree" (default: Postgres's own default, btree)
Depot.Migration.create_index("users", ["email"], { unique: true })
Depot.Migration.create_index("articles", ["tags"], { using: "gin" })For full-text search: this is the pairing for Depot.Query.ts_match — build a GIN index over a real tsvector-typed column with create_index(table, [field], { using: "gin" }), not create_index_expr. See Depot.Query.ts_match's doc comment for the full contract.
Generate a CREATE INDEX statement over a raw SQL expression rather than a bare column list — e.g. a GIN index over to_tsvector('english', body).
index_expr is embedded verbatim (no escaping): callers must not put user input directly in this string.
Different use case from create_index + Depot.Query.ts_match: this function indexes a COMPUTED expression over a plain column (e.g. wrapping a text column in to_tsvector(...)) for when you don't have a stored tsvector column. Querying against an expression index requires the WHERE clause to use the IDENTICAL expression — Depot.Query.ts_match/3 filters a BARE column instead, so it does NOT hit an index built by this function; that combination silently falls back to a Seq Scan. If you have a real tsvector column (maintained by a trigger or generated column), index it with plain create_index(table, [field], { using: "gin" }) and use ts_match/ ts_rank_expr against that column — see Depot.Query.ts_match's doc comment for the full pairing.
opts is a record with optional fields: unique — Bool (default false) name — custom index name string (recommended: expression indexes don't have an obvious auto-generated name, so pass one explicitly) using — index method string, e.g. "gin", "gist" (default: btree)
Depot.Migration.create_index_expr("articles", "to_tsvector('english', body)", { using: "gin", name: "articles_body_gin" })Generate a CREATE INDEX with no options.
Depot.Migration.create_index_simple("users", ["name", "email"])Generate a CREATE TABLE statement.
columns is a record where each field is either a bare type string like "String" or a tuple ("String", { null: false, default: "user" }).
Depot.Migration.create_table("users", {
id: ("UUID", { primary_key: true }),
name: "String",
email: ("String", { null: false })
})Generate a CREATE TABLE IF NOT EXISTS statement. Idempotent — safe to re-run against a database that already has the table.
Depot.Migration.create_table_if_not_exists("users", { id: ("UUID", { primary_key: true }) })Generate a DROP INDEX statement.
Depot.Migration.drop_index("users_email_index")Generate a DROP TABLE statement.
Depot.Migration.drop_table("old_table")Generate a DROP TABLE IF EXISTS statement. Idempotent — safe to re-run.
Depot.Migration.drop_table_if_exists("old_table")Log raw SQL to the migration log.
Depot.Migration.execute("INSERT INTO settings VALUES ('key', 'val')")Check if a migration version has been applied.
Depot.Migration.is_applied(20240101120000)Apply every migration in migrations whose version is not yet in the DB's schema_migrations table.
Migrations are applied in ascending version order. Each migration's up runs inside its own transaction via Depot.Migration.run, and its version is recorded in schema_migrations on success. The first failure short-circuits the run.
If the DB records a version that is not present in migrations, this is treated as schema/code divergence (older checkout) and surfaces as Err("divergent version ...") before any migration runs.
Returns Ok(n) where n is the number of migrations applied.
Depot.Migration.migrate(conn, [m1, m2, m3])Generate the SQL to create the schema_migrations tracking table.
Depot.Migration.migrations_table()Record a migration version as applied.
Depot.Migration.record_migration(20240101120000)Build a foreign key references descriptor record.
Returns a record with table, column, on_delete, on_update fields, merged with any overrides from opts.
let ref = Depot.Migration.references("posts", { column: "post_id", on_delete: "CASCADE" })Generate a RENAME COLUMN statement.
Depot.Migration.rename_column("users", "name", "full_name")Generate a RENAME TABLE statement.
Depot.Migration.rename_table("old_name", "new_name")Run down for the last n applied migrations, most-recent first.
The applied set is read from schema_migrations; each down runs inside its own transaction; the version row is deleted from schema_migrations after a successful down. Missing-in-code migrations short-circuit the rollback with Err.
Returns Ok(k) where k is the number of migrations actually reversed.
Execute migration_fn against a live PostgreSQL connection.
migration_fn is a thunk (fn () -> _) that builds the migration by calling create_table, alter_table, create_index, etc. Each call accumulates SQL in the Vault log; after the thunk returns, this function drains the log and executes every statement inside a single Depot.Transaction.run. A statement failure rolls back all siblings.
Returns Ok(()) on full success, Err(reason) on the first failure. On error, the Vault log is consumed either way — failed migrations do not leave stale pending statements behind.
Depot.Migration.run(conn, fn () ->
Depot.Migration.create_table_if_not_exists("users", {
id: ("UUID", { primary_key: true }),
email: ("String", { null: false })
})
)Execute a single SQL string against conn, wrapped in a transaction. Convenience for one-off migrations where populating the log would be overkill.
Execute a pre-built list of SQL statements inside a single transaction, recording them in the migration log for uniform telemetry. Used by migrate and rollback to run a migration's up / down list.