mule/engines/postgres/migration

PostgreSQL schema migrations for the Mule adapter — the source of truth for the schema. Two consumers read it:

Mirrors Oban.Migrations.Postgres: each schema version is one Step; add V2, V3… by appending to steps_for. Like Elixir’s Oban.Migration.up, the walk takes options — Version to stop at an intermediate version, Prefix to install into a Postgres schema other than public (created on demand, CreateSchema opts out), and every statement is guarded (if not exists / if exists) so re-running against a partially-applied schema converges instead of erroring — the Elixir property “migrations between versions are idempotent”.

Coexisting with Elixir Oban

Mule’s tables are deliberately named mule_* where Elixir Oban’s are oban_*, so the two never collide: a Mule install and an Elixir Oban install can share a database — even a schema — side by side, with no options required.

The ownership probe remains as a defensive check: a mule_jobs table these migrations did not create (hand-written, or another tool’s) is detected by its state column’s type — text is ours, and any other type makes every entry point refuse with ForeignSchema before touching anything, instead of corrupting a table it does not own. Drop or rename such a table, or give this instance its own schema: migrate_with(connection, options: [Prefix("mule")]) and the engine’s new_with_prefix.

Types

Everything migrate/rollback/verify_migrated can report beyond success. MigrationFailed and ForeignSchema come from any entry point; the remaining variants are verify_migrated findings. describe renders each as the message the boot guard prints.

pub type MigrationError {
  MigrationFailed(pog.TransactionError(pog.QueryError))
  ForeignSchema(state_column_type: String)
  NotMigrated
  MissingLedger
  UnreadableLedger(comment: String)
  OutdatedVersion(migrated: Int, target: Int)
}

Constructors

  • A statement failed; wraps pog’s transaction error (the whole walk runs in one transaction, so nothing was applied).

  • ForeignSchema(state_column_type: String)

    mule_jobs exists but was not created by these migrations: its state column is not text (an enum-typed column reports as USER-DEFINED). Refused rather than managed — see the module docs for the dedicated-prefix escape hatch.

  • NotMigrated

    verify: the mule_jobs table does not exist — migrations never ran.

  • MissingLedger

    verify: mule_jobs exists (state is text) but carries no version comment — a schema applied from gen_migration files, whose external runner owns sequencing. Running migrate once converges the schema and stamps the ledger.

  • UnreadableLedger(comment: String)

    verify: the table comment is neither a version number nor '∞'. The comment IS the ledger; migrate reclaims it by re-walking the guarded steps and restamping.

  • OutdatedVersion(migrated: Int, target: Int)

    verify: the ledger is behind this library’s target (Elixir’s “Oban migrations are outdated” raise).

Options for migrate_with/rollback_with/verify_migrated_with — the port of Oban.Migration.up/down’s keyword options. Later entries win.

pub type Option {
  Version(version: Int)
  Prefix(prefix: String)
  CreateSchema(create_schema: Bool)
}

Constructors

  • Version(version: Int)

    Migrate up to this schema version instead of the newest (Elixir’s version:). Ignored by rollback_with (its target is to:) and by verify_migrated_with (which always checks the library target, like Elixir verifying against current_version).

  • Prefix(prefix: String)

    The Postgres schema (“prefix” in Ecto terms) the tables live in. Defaults to "public". Pair with the engine’s new_with_prefix and, in a cluster, the notifier’s channel prefix.

  • CreateSchema(create_schema: Bool)

    Whether migrate first runs create schema if not exists. Defaults to True exactly when the prefix is not "public" (Elixir’s create_schema default); pass CreateSchema(False) when the database user may not create schemas.

One schema version: its forward and backward SQL as a list of individual statements (one per element). pgo runs the extended query protocol, which executes a single statement per query, so the direct-apply path runs each element separately; the generator joins them with ; for the file. name feeds the generated filename (e.g. cigogne’s <unix_ts>-<name>.sql).

pub type Step {
  Step(
    version: Int,
    name: String,
    up: List(String),
    down: List(String),
  )
}

Constructors

  • Step(
      version: Int,
      name: String,
      up: List(String),
      down: List(String),
    )

Values

pub fn describe(error error: MigrationError) -> String

Render an error as the human-facing message the boot guard raises with — the port of verify_migrated!’s raise texts.

pub fn migrate(
  connection connection: pog.Connection,
) -> Result(Int, MigrationError)

Apply every step newer than the recorded version, then stamp the reached version into the table comment. The whole walk runs in one transaction so a failed step rolls back cleanly. Equivalent to migrate_with(connection, options: []): public prefix, newest version.

pub fn migrate_with(
  connection connection: pog.Connection,
  options options: List(Option),
) -> Result(Int, MigrationError)

migrate with Options — Oban.Migration.up(version:, prefix:, create_schema:). Returns the ledger version after the walk: the highest step applied, or the current version untouched when nothing was pending (so a ledger ahead of this build — a newer library’s stamp, or an '∞' reported as target_version() — is never overwritten backwards).

pub fn rollback(
  connection connection: pog.Connection,
  to target: Int,
) -> Result(Int, MigrationError)

Reverse the schema down to target by running each applied step’s down, newest-first. Rolling back to 0 drops the tables (and the ledger with them); a partial rollback re-stamps the comment with the new target. Equivalent to rollback_with(connection, to:, options: []).

pub fn rollback_with(
  connection connection: pog.Connection,
  to target: Int,
  options options: List(Option),
) -> Result(Int, MigrationError)

rollback with Options (Prefix/CreateSchema; Version is ignored — the target is to:). A missing install returns 0; an unstamped or '∞'-stamped table of ours is treated as fully applied, so every guarded down between the library target and to runs (they no-op what is absent). A to at or above the current version reverts nothing and leaves the ledger untouched — rollback never moves the stamp forward.

pub fn steps() -> List(Step)

The ordered schema history against the default public prefix — what gen_migration emits without --prefix.

pub fn steps_for(
  prefix prefix: String,
  create_schema create_schema: Bool,
) -> List(Step)

The ordered schema history with every table reference qualified by prefix. With create_schema: True, V1 first creates the schema (mirroring Elixir’s V01 CREATE SCHEMA IF NOT EXISTS). Every statement is guarded so a re-run over an already-applied version converges: tables and indexes use if not exists, and each CHECK constraint is dropped (if present) and re-added — re-adding validates existing rows again, the price of converging an unstamped schema.

pub fn target_version() -> Int

Highest version known to this build — the target a fresh migrate reaches.

pub fn verify_migrated(
  connection connection: pog.Connection,
) -> Result(Nil, MigrationError)

Check that the database is migrated to this library’s target version — the port of Oban.Migration.verify_migrated!, minus the raise: each finding is a MigrationError variant and describe renders its message. mule.start runs this through the engine when a testing mode is enabled (mirroring Elixir’s boot guard); production boot sequences can call it directly.

pub fn verify_migrated_with(
  connection connection: pog.Connection,
  options options: List(Option),
) -> Result(Nil, MigrationError)

verify_migrated against a non-default Prefix. Always checks the library’s target_version (Version is ignored, like Elixir verifying against current_version).

Search Document