# `StatifierPersistence.Ecto.Migrations.V04`
[🔗](https://github.com/riddler/statifier_persistence/blob/v0.18.0/lib/statifier_persistence/ecto/migrations/v04.ex#L2)

V04 of the package DDL: rebuilds V03's `metadata` GIN index with
`CREATE INDEX CONCURRENTLY`.

V03 creates that index with a plain `CREATE INDEX`, which takes a
`SHARE` lock on the executions table for the whole build and blocks
every `INSERT`, `UPDATE` and `DELETE` against it until the build
finishes. For a host stepping executions durably that is every step of
every execution. On a small or idle table the build is imperceptible;
on a large one it is an outage, and the README's answer used to be a hand-written
migration the host maintained itself (sp-ajz).

This version ships that answer instead. It drops the index V03 built
and creates the same index again - same name, same expression, same
`jsonb_path_ops` opclass - with `concurrently: true` on both
statements, so neither blocks writes.

## The host's migration module is what disables the transaction

`CREATE INDEX CONCURRENTLY` cannot run inside a transaction block,
and `@disable_ddl_transaction` / `@disable_migration_lock` are read
by `Ecto.Migrator` from the module it is *running* - the host's own
migration - not from this module. A package migration module cannot
turn its caller's transaction off. So a host that wants the
concurrent build writes:

    defmodule MyApp.Repo.Migrations.RebuildStatifierPersistenceMetadataIndex do
      use Ecto.Migration

      @disable_ddl_transaction true
      @disable_migration_lock true

      def up, do: StatifierPersistence.Ecto.Migrations.up(for: MyApp.Persistence, from: 4)
      def down, do: StatifierPersistence.Ecto.Migrations.down(for: MyApp.Persistence, version: 4)
    end

and `up/1` **skips** when it finds itself inside a transaction after
all. Skipping rather than raising is deliberate: the end state of a
skipped V04 is the index V03 already built, which is correct and
complete, differing only in how it was built. Raising would break the
one-call recipe (`up(for: MyApp.Persistence)`) that every fresh
database and every test harness uses, to no benefit - on an empty
executions table a plain build costs nothing.

The skip warns through `Logger.warning/1` **only when the executions
table already holds rows**, which is exactly the case where the plain build
blocked writes and the host has something to do about it. A fresh
database migrates in silence.

## Rolling back

`down/1` does nothing, on every adapter, and that is the whole
rollback. What V04 leaves behind is not a new object: it is V03's
index, under V03's name, with V03's definition, and V03's `down/1`
is what drops it. Rebuilding it plainly on the way down would take
exactly the `SHARE` lock this version exists to avoid, in order to
reach a state no reader can tell apart from the one it started in.

## Failure part-way through

A non-transactional migration has no rollback, so an interrupted
`up/1` can leave the executions table with no `metadata` index or with
an invalid one. Re-running the migration is the repair: the drop is
`drop_if_exists`, so it clears either leftover, and the create then
builds the index once more. Nothing in the package's DDL depends on
the index existing; what depends on it is the cost of the two
containment queries V03's moduledoc names.

## Adapters other than Postgres

`up/1` and `down/1` are both no-ops off `Ecto.Adapters.Postgres`,
under the same exact-match adapter check V03 uses and for the same
reason: `GIN` and `jsonb_path_ops` are Postgres spellings, so on any
other adapter there is no index to rebuild - V03 skipped creating one
(sp-11w), and `docs/non-postgres-backends.md` describes what that
costs such a host. The check runs before the transaction check, so a
SQLite host needs neither attribute.

# `down`

```elixir
@spec down(StatifierPersistence.Ecto.Config.t()) :: :ok
```

Leaves V03's index in place - see the moduledoc's rollback section.

# `up`

```elixir
@spec up(StatifierPersistence.Ecto.Config.t()) :: :ok
```

Rebuilds the `metadata` GIN index concurrently per `config`.

A no-op off Postgres, and a no-op with a warning when the host's
migration module has not disabled the DDL transaction - see the
moduledoc.

---

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