# `PhoenixKit.Migrations.Repair.Executor`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.13.7/lib/phoenix_kit/migrations/repair/executor.ex#L1)

Turns one `PhoenixKit.Migrations.Repair.Scope.resolved()` into the SQL to
run, and (in real repair mode) runs it — additive-only, statement-at-a-time,
autocommit, spec §6.2/§6.3.

## Two halves, deliberately split

`create_action/2` is **pure** — no `repo` argument, cannot touch a
database — and is the whole "what SQL would this be" answer:
`PhoenixKit.Migrations.Repair.verify/1` (and a dry-run `.repair/1`) call
only this half, so `--dry-run` genuinely cannot write anything by
construction, not by discipline. `test/phoenix_kit/migrations/repair/executor_test.exs`
asserts exact statement text against it — the "SQL-generation assertions
without executing" the plan calls for. `create/3` is the executing half;
it has no unit test (no database in this suite).

## Additive-only rules this module enforces (spec §6.2)

Never `DROP`, `ALTER TYPE`, `SET`/`DROP NOT NULL` on a pre-existing column,
`UPDATE`/`DELETE` user data, rename, or touch anything outside
`phoenix_kit*` — none of those verbs appear anywhere below. The one
`UPDATE` this module ever issues (`backfill/4`) only ever targets `WHERE
"<col>" IS NULL` on a column this same call **just added** in the
immediately preceding statement (spec D7) — never a pre-existing column,
never rows that already have a value.

## FK `NOT VALID` + `VALIDATE` (spec §6.3)

For every foreign key (`shape.type == "f"`), `create_action/2` always adds
` NOT VALID` to the `ADD CONSTRAINT` — regardless of whether the manifest's
own precomputed `object.create` would have (it never does; V113 is the
in-chain precedent for the two-step). `create/3` follows a successful
`ADD` with a separate `VALIDATE CONSTRAINT` statement; a validation
failure leaves the constraint `NOT VALID` (per spec — never dropped, never
retried) and reports an orphan-row count via
`PhoenixKit.Migrations.Repair.Probe.orphan_count/6`.

# `outcome`

```elixir
@type outcome() ::
  :created
  | :already_present
  | :best_effort_skipped
  | {:create_failed, String.t()}
  | {:fk_validation_failed, String.t()}
```

What happened (or, from `create_action/2` alone, would be attempted).

# `create`

```elixir
@spec create(
  Ecto.Repo.t(),
  PhoenixKit.Migrations.Repair.Scope.resolved(),
  prefix :: String.t(),
  skip_validate? :: boolean()
) :: outcome()
```

Runs `create_action/2` for real: executes a SQL string immediately
(`repo.query!/3`, autocommit), invokes a `{:helper, mfa}`, or reports
`:already_present` for `nil`. Column creates backfill their own default on
success (spec D7); FK constraint creates follow with `VALIDATE CONSTRAINT`
on success — unless `skip_validate?` is `true` (§6.3's `--unsafe-pooled`:
the constraint is added `NOT VALID` and left there, deliberately, without
ever attempting the VALIDATE statement against a connection that cannot
safely hold the advisory lock either).

# `create_action`

```elixir
@spec create_action(
  PhoenixKit.Migrations.Repair.Scope.resolved(),
  prefix :: String.t()
) ::
  nil | String.t() | {:helper, {module(), atom(), [term()]}}
```

The create action for one resolved object — `nil` (nothing to create;
`:legacy_optional`), a SQL string, or `{:helper, {mod, fun, args}}`. Pure.

Dispatches on class exactly as `PhoenixKit.Migrations.Repair.ShapeSql`'s
moduledoc describes: `:column`/`:index`/`:constraint` are always rebuilt
from `resolved.shape` (never `resolved.object.create`, which is the
newest-revision precomputed field and NOT VALID-less for every FK); every
other class uses the object's own precomputed `create` (revision-blind by
design, matching the generator).

    iex> resolved = %{
    ...>   object: %{class: :column, check: {:catalog, %{kind: :column, table: "widgets", column: "name"}}},
    ...>   shape: %{type: "text", not_null: false, default: nil}
    ...> }
    iex> Executor.create_action(resolved, "public")
    ~s(ALTER TABLE public.widgets ADD COLUMN IF NOT EXISTS "name" text)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
