# PostgreSQL model migrations

Migrations record every schema change as reviewable SQL. Use them whenever you add
or change a model: run `makemigrations`, review the SQL, then run `migrate`.

GORM runs your queries at runtime. It never replaces recorded history with
`AutoMigrate`.

## Ask your agent

```text
Read https://cicadel.fauzaanu.com/en/0.2.7/topics/migrations/index.md.
Add [model or schema change] through the application's registered Models and
recorded PostgreSQL history. Generate and review SQL, test against a disposable
database, and report the plan before touching an existing production database.
Do not use AutoMigrate or rewrite applied migration files.
```

## How it works

![Models flow through makemigrations into a reviewable SQL file, then migrate applies it to PostgreSQL and records the history](https://github.com/fauzaanu/cicadel/blob/2678611d308376ca8045c8741ee9d4d7814a3fec/docs/diagrams/migrations-flow.svg)

1. You register models in your app's `Models()`.
2. `makemigrations` compares them with the recorded history and writes a new SQL file.
3. You review the SQL, commit it and rebuild the application.
4. `migrate` applies pending files to PostgreSQL and records what it applied.

The first-party `contrib/migrations` module does the work: generation, historical
state, version dependencies, execution, verification and recovery. The standard
management commands use it.

## Add a model

`startproject` already includes accounts, formsets, the migration commands and
typed query generation. `startapp blog` creates an app with:

- a `Models()` function,
- a `MigrationDirectory`,
- an embedded migration filesystem.

Register `blog.New()` in `project/apps.go`. Keep the existing first-party
registrations.

Then add and register a model in `apps/blog/models.go`:

<!-- example: migrations/apps/blog/models.go -->
```go
package blog

import "github.com/fauzaanu/cicadel"

type Note struct {
    ID int64 `gorm:"primaryKey"`
    Title string `gorm:"not null"`
}

func (Note) TableName() string { return "blog_notes" }

func Models() []cicadel.Model {
    return []cicadel.Model{cicadel.ModelOf[Note]("note", "blog_notes")}
}
```

## Generate and apply

From the project root:

```sh
go run manage.go makemigrations blog --name initial
go run manage.go makemigrations blog --check
go run manage.go generatequeries
DATABASE_URL='postgres://.../app' go run manage.go migrate plan
DATABASE_URL='postgres://.../app' go run manage.go migrate
DATABASE_URL='postgres://.../app' go run manage.go migrate verify
DATABASE_URL='postgres://.../app' go run manage.go showmigrations
```

| Command | What it does | Needs PostgreSQL |
| --- | --- | --- |
| `makemigrations APP --name NAME` | Writes the next SQL file into the app's migration directory. | No |
| `makemigrations APP --check` | Fails when model changes need a migration. Writes no files. | No |
| `generatequeries` | Generates typed query files. | No |
| `migrate plan` | Lists the steps `migrate` would run. | Yes |
| `migrate` | Applies pending migrations. | Yes |
| `migrate verify` | Checks that the database schema matches the applied history. | Yes |
| `showmigrations` | Lists each migration as applied or pending. | Yes |

Generation compares registered models with the recorded historical state. It never
reads a developer's database. It runs offline, even when `DATABASE_URL` points to
an unavailable server. So app factories used by `InstalledApps(nil)` must not do
database work.

After generating:

1. Review the generated SQL and query files.
2. Commit them.
3. Build the application again, so its embedded history is current.

## Model registration

Migrations and typed queries share one public `ModelOf` registry. It holds model
names, types and table ownership. Each declared table must match GORM's table name.

Configure these in `project/migrations.go` through
`management.ModelMigrationOptions`:

- pinned cross-app historical references,
- naming strategies,
- custom type mappings.

Renames, removals, conversions and backfills must be declared explicitly. Use
`makemigrations APP --changes changes.json`. See the
[operation contract](https://cicadel.fauzaanu.com/en/0.2.7/ref/migrations/index.md).

## Core module composition

The migration engine is a core default shipped as an installable Go module.
Standard projects already include its management commands.

- Register models and migration sources through the app contract.
- Generate package-owned migrations in the package's source checkout.
- Application builds load recorded history from the embedded filesystem.

Use the same framework version for the CLI and the application. Recorded
migrations and history from any release keep working with later releases. See the
[data guarantee](https://cicadel.fauzaanu.com/en/0.2.7/releases/releases/index.md#stability-guarantee).

## Recovery

Management commands load every registered migration source into one dependency
catalog. They use the runner's shared PostgreSQL lock. Register every app whose
applied history you manage.

| Task | Command |
| --- | --- |
| Review a target version | `migrate plan --target APP=VERSION` |
| Roll back deliberately, after review | `migrate apply --target APP=VERSION --allow-data-loss` |
| Verify an eligible pre-existing initial schema | `migrate baseline --reason TEXT` |
| Recover a nontransactional index migration | `migrate recover --migration APP:VERSION --recovery retry\|complete` |

> [!WARNING]
> Migration history is not a data backup.

Unsupported changes fail explicitly. See
[the supported PostgreSQL operations](https://cicadel.fauzaanu.com/en/0.2.7/ref/migrations/index.md) and the
[acceptance evidence](https://cicadel.fauzaanu.com/en/0.2.7/sources/contrib/migrations/ACCEPTANCE.md).
