ADR-0009: Migrations Are Hand-Written, One Statement per Call — No ORM Autogenerate
Status
Accepted
Context
Alembic supports --autogenerate, which diffs an ORM's declared models
against the live database schema to produce a migration automatically.
Azan360's data layer is raw asyncpg with no ORM — there are no models to
diff against.
Decision
Migrations are written by hand with op.execute(...), never with
--autogenerate. Each migration uses exactly one SQL statement per
op.execute() call, because SQLAlchemy's asyncpg dialect prepares each
statement it's given, and a prepared statement cannot hold more than one
command.
Alternatives Considered
Using --autogenerate against an ORM layer — not viable without first
introducing an ORM, which the project does not have and which is treated
as out of scope by this decision (implicitly: the raw-asyncpg data-layer
choice is prior to and outside this ADR's scope).
Consequences
- Without an ORM to diff against,
--autogeneratewould produce a confident, empty diff — actively worse than no tooling, since it looks like verification without providing any. Hand-writing migrations is the correct choice given the existing data-layer architecture, not a shortcut. - Every migration author must know the exact SQL being applied — there is no generated starting point to review and adjust.
- The one-statement-per-call constraint is not a style preference; batching
multiple statements in one
op.execute()will fail at the driver level because of how the asyncpg dialect prepares statements. - Destructive statements can still be reviewed before touching production
via
alembic upgrade head --sql, which emits SQL instead of running it.