March Docs

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

fnalter_add_entryalter_add_entry(prefix, entry)#

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"]
  })
fnalter_modify_entryalter_modify_entry(prefix, entry)#
fnalter_tablealter_table(name, changes)#
fnapplied_migrationsapplied_migrations()#

Return the list of applied migration versions.

  Depot.Migration.applied_migrations()
fnapplied_versionsapplied_versions(conn)#

Read the list of applied migration versions from schema_migrations, ordered ascending. Ensures the tracking table exists.

fncol_entry_sqlcol_entry_sql(entry)#
fncolumn_sqlcolumn_sql(name, spec)#
fncreate_indexcreate_index(table, columns, opts)#

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.

fncreate_index_exprcreate_index_expr(table, index_expr, opts)#

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" })
fncreate_index_simplecreate_index_simple(table, columns)#

Generate a CREATE INDEX with no options.

  Depot.Migration.create_index_simple("users", ["name", "email"])
fncreate_tablecreate_table(name, columns)#

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 })
  })
fncreate_table_if_not_existscreate_table_if_not_exists(name, columns)#

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 }) })
fndrop_indexdrop_index(name)#

Generate a DROP INDEX statement.

  Depot.Migration.drop_index("users_email_index")
fndrop_tabledrop_table(name)#

Generate a DROP TABLE statement.

  Depot.Migration.drop_table("old_table")
fndrop_table_if_existsdrop_table_if_exists(name)#

Generate a DROP TABLE IF EXISTS statement. Idempotent — safe to re-run.

  Depot.Migration.drop_table_if_exists("old_table")
fnexecuteexecute(sql)#

Log raw SQL to the migration log.

  Depot.Migration.execute("INSERT INTO settings VALUES ('key', 'val')")
fnis_appliedis_applied(version)#

Check if a migration version has been applied.

  Depot.Migration.is_applied(20240101120000)
fnmigratemigrate(conn, migrations)#

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])
fnmigrations_tablemigrations_table()#

Generate the SQL to create the schema_migrations tracking table.

  Depot.Migration.migrations_table()
fnparse_col_specparse_col_spec(spec)#
fnrecord_migrationrecord_migration(version)#

Record a migration version as applied.

  Depot.Migration.record_migration(20240101120000)
fnreferencesreferences(table, opts)#

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" })
fnrename_columnrename_column(table, from_col, to_col)#

Generate a RENAME COLUMN statement.

  Depot.Migration.rename_column("users", "name", "full_name")
fnrename_tablerename_table(from_name, to_name)#

Generate a RENAME TABLE statement.

  Depot.Migration.rename_table("old_name", "new_name")
fnrollbackrollback(conn, migrations, n)#

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.

fnrunrun(conn, migration_fn)#

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 })
    })
  )
fnrun_sqlrun_sql(conn, sql)#

Execute a single SQL string against conn, wrapped in a transaction. Convenience for one-off migrations where populating the log would be overkill.

fnrun_statementsrun_statements(conn, stmts)#

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.

fnsql_typesql_type(march_type)#