Migrations¶
Database migrations track and apply schema changes to your database.
Prerequisites¶
Advanced Alchemy installed with
cliextraConfiguration file created (see CLI)
Understanding Migrations¶
Migrations provide:
Version control for database schema
Incremental schema changes
Rollback capabilities
Team coordination for schema updates
Database-agnostic schema definitions
Advanced Alchemy uses Alembic for migration management.
Migration Workflow¶
Follow this workflow for schema changes:
Step 1: Modify Models¶
Make changes to your SQLAlchemy models:
from advanced_alchemy.base import UUIDAuditBase
from sqlalchemy.orm import Mapped, mapped_column
class User(UUIDAuditBase):
username: Mapped[str] = mapped_column(unique=True)
email: Mapped[str] = mapped_column(unique=True) # Added field
Step 2: Generate Migration¶
Create migration file with autogenerate:
alchemy make-migrations -m "add user email field" --config path.to.alchemy-config.config
Output:
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
INFO [alembic.autogenerate.compare] Detected added column 'user.email'
Generating /path/to/migrations/versions/abc123_add_user_email_field.py ... done
Alembic autogenerate detects changes automatically.
Step 3: Review Generated Migration¶
Open the migration file and verify changes:
"""add user email field
Revision ID: abc123
Revises: def456
Create Date: 2025-10-18 12:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers
revision = 'abc123'
down_revision = 'def456'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user', sa.Column('email', sa.String(), nullable=False))
op.create_unique_constraint(None, 'user', ['email'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'user', type_='unique')
op.drop_column('user', 'email')
# ### end Alembic commands ###
Review checklist:
Column additions/removals correct
Constraints properly defined
Indexes created where needed
downgrade()reversesupgrade()
Step 4: Apply Migration¶
Apply migration to database:
alchemy upgrade --config path.to.alchemy-config.config
Output:
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
INFO [alembic.runtime.migration] Running upgrade def456 -> abc123, add user email field
Migration applied successfully.
Step 5: Verify Schema¶
Check current database revision:
alchemy show-current-revision --config path.to.alchemy-config.config
Output:
abc123 (head)
Verify schema in database:
psql -d mydb -c "\d+ user"
mysql -e "DESCRIBE user;"
sqlite3 mydb.db ".schema user"
Autogenerate Capabilities¶
Alembic autogenerate detects:
- Table Changes
Table additions
Table removals
Table renames (detected as drop + add)
- Column Changes
Column additions
Column removals
Column type modifications
Nullable/not-null changes
- Constraint Changes
Unique constraints
Foreign key constraints
Check constraints
Primary key changes
- Index Changes
Index additions
Index removals
Index modifications
Autogenerate Limitations¶
Manual migration required for:
- Renamed Columns
Detected as drop + add, not rename:
# Manual migration needed op.alter_column('user', 'old_name', new_column_name='new_name')
- Renamed Tables
Detected as drop + add:
# Manual migration needed op.rename_table('old_table', 'new_table')
- Server Default Changes
Not detected by autogenerate:
# Manual migration needed op.alter_column('user', 'created_at', server_default=func.now())
- Data Migrations
Requires manual implementation:
# Manual data transformation connection = op.get_bind() connection.execute( sa.text("UPDATE user SET email = username || '@example.com' WHERE email IS NULL") )
- Column Comment Changes
Not tracked:
# Manual migration needed op.alter_column('user', 'username', comment='User login name')
Rollback¶
Downgrade to previous revision:
alchemy downgrade --config path.to.alchemy-config.config
Downgrade specific steps:
# Downgrade 1 revision
alchemy downgrade -1 --config path.to.alchemy-config.config
# Downgrade to specific revision
alchemy downgrade abc123 --config path.to.alchemy-config.config
# Downgrade to base (all migrations removed)
alchemy downgrade base --config path.to.alchemy-config.config
Rollback limitations:
Data loss possible if
downgrade()drops columnsIrreversible operations (truncate, drop table)
Custom data migrations may not be reversible
Always backup before downgrading
Technical Constraints¶
Migration Detection¶
Alembic compares metadata to database state:
# ✅ Correct - model registered with metadata
from advanced_alchemy.base import UUIDAuditBase
class User(UUIDAuditBase): # Registered via orm_registry
username: Mapped[str]
# ❌ Incorrect - model not in metadata
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
class User(Base): # Different metadata, won't be detected
__tablename__ = "user"
username: Mapped[str]
Use Advanced Alchemy base classes for proper metadata registration.
Relationship Detection¶
Viewonly relationships not in migrations:
# Migration includes posts relationship (no viewonly)
class Post(BigIntAuditBase):
tags: Mapped[List["Tag"]] = relationship(
secondary=post_tag,
back_populates="posts"
)
# Migration ignores posts relationship (viewonly=True)
class Tag(BigIntAuditBase):
posts: Mapped[List[Post]] = relationship(
secondary=post_tag,
back_populates="tags",
viewonly=True # Not tracked in migrations
)
Viewonly relationships don’t affect database schema.
Migration File Conflicts¶
Multiple developers may create conflicting migrations:
migrations/versions/
├── abc123_feature_a.py # down_revision: xyz789
└── def456_feature_b.py # down_revision: xyz789 (conflict!)
Resolve using merge command:
alchemy merge heads -m "merge feature branches" --config path.to.alchemy-config.config
Common Issues¶
Migration Not Detected¶
Problem: Model changes not detected by autogenerate
Causes:
Model not imported in Alembic
env.pyModel uses different metadata registry
Using
viewonly=TruerelationshipsChanging
server_defaultvalues
Solution:
# Ensure models imported
# Use correct metadata
target_metadata = orm_registry.metadata # Advanced Alchemy registry
Migration Fails to Apply¶
Problem: alchemy upgrade fails with error
Common causes:
Existing Data Violates Constraints
ERROR: column "email" contains null values
Solution: Add data migration before constraint:
def upgrade():
# Add column as nullable first
op.add_column('user', sa.Column('email', sa.String(), nullable=True))
# Populate data
connection = op.get_bind()
connection.execute(
sa.text("UPDATE user SET email = username || '@example.com'")
)
# Add constraint
op.alter_column('user', 'email', nullable=False)
Column Already Exists
ERROR: column "email" of relation "user" already exists
Solution: Migration already applied or partially applied. Check revision:
alchemy show-current-revision --config path.to.alchemy-config.config
Database Dialect Incompatibility
ERROR: syntax error at or near "IF EXISTS"
Solution: Use database-agnostic Alembic operations, not raw SQL.
Multiple Heads (Branch Conflict)¶
Problem: alchemy upgrade reports multiple heads
FAILED: Multiple head revisions are present; please merge
Solution: Merge branches:
# Check heads
alchemy heads --config path.to.alchemy-config.config
# Merge all heads
alchemy merge heads -m "merge branches" --config path.to.alchemy-config.config
# Apply merged migration
alchemy upgrade --config path.to.alchemy-config.config
Database-Specific Considerations¶
PostgreSQL¶
Supports transactional DDL (rollback schema changes)
CONCURRENTLYfor indexes (requires manual migration)ENUMtype changes require manual handling
MySQL¶
Non-transactional DDL (cannot rollback schema changes)
Foreign keys require InnoDB engine
ALTER TABLElocks tables during migration
SQLite¶
Limited
ALTER TABLEsupportCannot drop columns (requires table recreation)
Foreign key constraints disabled by default
Oracle¶
Sequence management for primary keys
NUMBERtype for integersRequires manual
IDENTITYhandling
Next Steps¶
See Commands for complete command reference.