mule/engines/postgres/migration
PostgreSQL schema migrations for the Mule adapter — the source of truth for the schema. Two consumers read it:
migrate/rollbackapply directly against a live connection (used by tests andexamples/engines/postgres), tracking the applied version in an Oban-styleCOMMENT ON TABLE mule_jobs IS '<n>'ledger.mule/tasks/gen_migrationreadssteps/steps_forto emit migration files for an external runner (cigogne / plain SQL), which then owns its own tracking.
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
-
MigrationFailed(pog.TransactionError(pog.QueryError))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_jobsexists but was not created by these migrations: itsstatecolumn is nottext(an enum-typed column reports asUSER-DEFINED). Refused rather than managed — see the module docs for the dedicated-prefix escape hatch. -
NotMigratedverify: the
mule_jobstable does not exist — migrations never ran. -
MissingLedgerverify:
mule_jobsexists (state istext) but carries no version comment — a schema applied fromgen_migrationfiles, whose external runner owns sequencing. Runningmigrateonce 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;migratereclaims 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 byrollback_with(its target isto:) and byverify_migrated_with(which always checks the library target, like Elixir verifying againstcurrent_version). -
Prefix(prefix: String)The Postgres schema (“prefix” in Ecto terms) the tables live in. Defaults to
"public". Pair with the engine’snew_with_prefixand, in a cluster, the notifier’s channel prefix. -
CreateSchema(create_schema: Bool)Whether
migratefirst runscreate schema if not exists. Defaults toTrueexactly when the prefix is not"public"(Elixir’screate_schemadefault); passCreateSchema(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).