# Migrations

Entities describe the schema you want. A migration records each step from
one version of the database to the next. `jadpo migrate new add_priority`
compares the entities with the previous migration's snapshot and writes
the next folder:

```text
database/migrations/20261020T1400_add_priority/
  snapshot.json        generated: the whole schema at this point
  postgres.sql         generated: the difference from the previous snapshot
  changes.jadpo        only when a decision is needed
```

When a change needs a decision, `jadpo migrate new` writes `changes.jadpo`
with a line to fill in for each one, such as `Todo.priority: {}`. `jadpo
check` names what is missing until it is written, and running `jadpo
migrate new` again finishes the migration, writing the decisions into its
SQL. Here a required `priority` is added, so existing rows need a value,
and the legacy flag is retired:

database/migrations/20261020T1401_add_priority/changes.jadpo:

```jadpo
// changes.jadpo
/// Adds a priority to every todo and retires the legacy flag.
migration {
    Todo.priority: {
        backfill: Priority.normal  // existing rows need a value
    }
    Todo.legacy_flag: FieldChange.retired
}
```

- **Forgetting is caught at once.** `jadpo check` fails while the entities
  and the latest snapshot disagree, naming what changed and the command to
  run, so the editor, the build and CI all stop. Loose files, with no
  `app.jadpo`, have no migrations, so the check is a project's.
- **Decisions only where needed:** a value for existing rows of a
  required field, or retiring a field.
- **Removing a field takes two migrations.** `FieldChange.retired` comes
  first: code stops using the field, and the column stays, now nullable,
  for servers still running the old build. Later, `FieldChange.dropped`
  removes it, and needs an explicit acceptance, a person's recorded
  approval of a breaking change
  ([Acceptances](acceptances.md)).
- **Renames keep their data.** The identity registry, a file Jadpo keeps
  under `database/`, records what each renamed thing was born as
  ([Records and manifest](records-and-manifest.md)). Tables and columns
  keep the names they were born with, so renaming a field keeps its
  column. A field renamed by hand, without the registry, reads as one
  removed and one added, and the removed one's decision says so.
- **Backfills can be worked out** from each row's own fields, for a new
  field that reshapes data the entity already holds
  ([Computed backfills](computed-backfills.md)).
- **`jadpo migrate apply`** runs pending migrations as a deploy step, each
  in a transaction, one per store, and records each in the store's
  `jadpo_migrations` table. It refuses a database that applied a
  migration the project doesn't have, or one that changed since. `jadpo
  migrate status` lists the migrations and which a database has applied.
- **A native deployable checks its database.** A native deployable built
  by `jadpo build` refuses to start against a database that hasn't applied
  every migration it was built with, or that applied one it doesn't have
  or that changed since, naming the store and the migrations
  ([A native deployable](a-native-deployable.md)).
- **The test database.** `jadpo test` gives each action test its own copy
  of a template built from the migrations, on the server
  `JADPO_TEST_DATABASE_URL` names or a disposable one, with copies
  prepared in the background. `--keep-failed` keeps a failed test's copy.

A migration's `postgres.sql` holds a section per store, each opened by a
`-- store: <name>` line. It names every table, column, constraint and
index as the snapshot does: a table after its entity's born name in
`lower_snake_case` (`Todo` is `todo`), a column after its field, and keys
and indexes `<table>_<name>_key`, `_fkey` and `_idx`, cut short with a
digest past PostgreSQL's 63 bytes. Each store that holds an entity also
gets the runtime's [system tables](system-tables.md), prefixed `jadpo_`.

Not built yet: `jadpo rename`, which writes the identity registry
([Tooling](tooling.md)); `FieldChange.dropped` and its acceptance;
removing an entity, moving one between stores, and changing a column's
type other than widening it. `jadpo migrate new` says so for each.
