PhoenixKit.Migrations.Repair (phoenix_kit v2.6.0)

Copy Markdown View Source

Runtime, additive-only verify-and-repair for the PhoenixKit migration chain (spec §6, D6). Two public entry points:

Both return {:ok, PhoenixKit.Migrations.Repair.Report.t()} on a completed run, or {:error, reason} for a condition the pipeline refuses to proceed past (manifest not generated, below-floor, comment ahead of code, a pooled connection without --unsafe-pooled, or a detected concurrent migration). reason is always either :not_generated or a {tag, ...} tuple; error_message/1 renders any of them to the same text mix phoenix_kit.repair/mix phoenix_kit.doctor print.

Options

  • :prefix — schema prefix, already resolved (default "public"). Callers resolving it from config/--prefix (PhoenixKit.Install.PrefixConfig.resolve_prefix/1) do so before calling here — this module does not read application config itself, the same way PhoenixKit.Migrations.Postgres.up/1 does not.
  • :repo — defaults to PhoenixKit.RepoHelper.repo/0.
  • :adopt — R4 (§6.4). Only consulted when the raw comment is nil.
  • :heal_comment — R2's stale-low heal. Only consulted for an in-range comment whose marker cross-check found {:stale_low, _}.
  • :unsafe_pooled — skip the advisory lock and FK VALIDATE (§6.3). Required (else {:error, {:pooled_connection, message}}) whenever PhoenixKit.Migrations.Repair.Environment.pooled?/2 says yes AND this run would actually write (repair/1 with dry_run: false, the default). Never consulted for a read-only pass — verify/1, or repair/1 with dry_run: true — which takes neither the lock nor the FK VALIDATE path regardless of the pooled verdict, so a pooled connection can never block one. This is what lets mix phoenix_kit.doctor's manifest-repair check call verify/1 plainly, with no flag, and still get real information on a PgBouncer-fronted deployment (a real, documented topology for this codebase's own runtime).
  • :dry_runrepair/1 only; verify/1 always runs as if this were true regardless of what is passed.

Repair vs. a concurrently running migration

Postgres.up/1 and .down/1 take the same advisory lock key this module takes (PhoenixKit.Migrations.Repair.Environment.with_lock/2) — spec §6.1 — but the exclusion that buys is one-directional: a chain run started while a repair holds the lock waits for it, whereas a repair started while a chain run is mid-DDL is not blocked, because the generated wrappers disable their DDL transaction and the migration side's transaction-scoped lock is therefore released after its own statement (see Postgres.acquire_chain_lock!/0 for the full reasoning). For that direction — and on a pooled connection, where advisory locking cannot be trusted at all — the before/after raw-comment re-read (PhoenixKit.Migrations.Repair.CommentPolicy.concurrent_migration?/2, S18) remains the mechanism, and it detects rather than prevents.

Summary

Types

Every {:error, _} shape this module returns; see moduledoc.

Functions

Renders any error_reason() this module returns to operator-facing text — the wording mix phoenix_kit.repair/mix phoenix_kit.doctor both print verbatim.

Applies missing objects and comment-policy writes unless dry_run: true. See moduledoc.

Read-only. See moduledoc.

Types

error_reason()

@type error_reason() ::
  :not_generated
  | {:not_installed, prefix :: String.t()}
  | {:below_floor, comment :: pos_integer(), floor :: pos_integer()}
  | {:comment_unreadable, term()}
  | {:above_current, comment :: pos_integer(), current :: pos_integer()}
  | {:pooled_connection, String.t()}
  | {:concurrent_migration, before :: term(), after_ :: term()}

Every {:error, _} shape this module returns; see moduledoc.

Functions

error_message(arg1)

@spec error_message(error_reason()) :: String.t()

Renders any error_reason() this module returns to operator-facing text — the wording mix phoenix_kit.repair/mix phoenix_kit.doctor both print verbatim.

repair(opts \\ [])

@spec repair(keyword()) ::
  {:ok, PhoenixKit.Migrations.Repair.Report.t()} | {:error, error_reason()}

Applies missing objects and comment-policy writes unless dry_run: true. See moduledoc.

verify(opts \\ [])

@spec verify(keyword()) ::
  {:ok, PhoenixKit.Migrations.Repair.Report.t()} | {:error, error_reason()}

Read-only. See moduledoc.