Get the FREE Ultimate OpenClaw Setup Guide →

migrations

npx machina-cli add skill mrsknetwork/supernova/migrations --openclaw
Files (1)
SKILL.md
5.1 KB

Migrations Engineering (Alembic)

Purpose

Database migrations are irreversible in production. A dropped column is gone. A bad NOT NULL constraint on an existing table fails at runtime when the migration runs. This skill enforces a review-before-apply workflow that catches these problems before they cost you data or a production incident.

SOP: Alembic Workflow

Step 1 - Alembic Setup (First Time Only)

alembic init alembic

alembic/env.py (configure for async + your models):

from logging.config import fileConfig
from sqlalchemy import pool
from sqlalchemy.ext.asyncio import async_engine_from_config
from alembic import context
from src.database import Base
from src.models import *  # noqa - import all models so Alembic sees them

config = context.config
config.set_main_option("sqlalchemy.url", settings.DATABASE_URL)
target_metadata = Base.metadata

async def run_migrations_online():
    connectable = async_engine_from_config(
        config.get_section(config.config_ini_section),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )
    async with connectable.connect() as connection:
        await connection.run_sync(do_run_migrations)

def do_run_migrations(connection):
    context.configure(connection=connection, target_metadata=target_metadata)
    with context.begin_transaction():
        context.run_migrations()

Step 2 - Generate a Migration

# After modifying a SQLAlchemy model:
alembic revision --autogenerate -m "add_user_bio_column"

Immediately review the generated file before applying it. Autogenerate is good but not perfect. Look for:

Red FlagWhat to Do
op.drop_table(...) or op.drop_column(...)Confirm intentional. Add a DB backup step in your deploy SOP before this migration.
op.add_column(..., nullable=False) on a non-empty tableThis will FAIL. Use nullable=True first, backfill, then alter_column to NOT NULL.
Empty upgrade() bodyAlembic missed your change. Check that the model is imported in env.py.
Renamed column detected as drop+addSet compare_type=True in context.configure() and check if it's a true rename or drop.

Step 3 - Safe NOT NULL Migration (3-Step Pattern)

Never add a NOT NULL column to an existing table in a single migration. This is the #1 migration foot-gun:

# BAD - will fail if table has existing rows:
op.add_column("users", sa.Column("bio", sa.String(500), nullable=False))

# GOOD - split into 3 migrations:

# Migration 1: Add as nullable
op.add_column("users", sa.Column("bio", sa.String(500), nullable=True))

# Migration 2 (separate): Backfill existing rows
op.execute("UPDATE users SET bio = '' WHERE bio IS NULL")

# Migration 3 (separate): Now safe to enforce NOT NULL
op.alter_column("users", "bio", nullable=False)

Step 4 - Apply Migrations

# Apply all pending migrations (local dev)
alembic upgrade head

# Apply one at a time (careful production deploy)
alembic upgrade +1

# Check current state
alembic current

# See pending
alembic history --indicate-current

Step 5 - Rollback

alembic downgrade -1    # roll back one migration
alembic downgrade <revision_id>  # roll back to a specific revision

Every migration's downgrade() function must be implemented. An empty downgrade() means you cannot recover from a bad migration. Alembic autogenerate provides a starting point — always check it is correct.

Step 6 - Production Migration SOP

Run this checklist before applying migrations to a production database:

[ ] Run migrations on staging first with a copy of production data
[ ] Verify staging app works correctly after migration
[ ] Take a database backup before running on production (RDS: create snapshot)
[ ] Put the app in maintenance mode if the migration holds exclusive table locks
[ ] Run: alembic upgrade head
[ ] Verify: alembic current (should show head)
[ ] Smoke test: 3 key user flows that touch the migrated tables
[ ] If anything is wrong: alembic downgrade -1 (then investigate)

Step 7 - Adding an Index (Non-Blocking)

Adding an index on a large table takes a lock that blocks reads and writes. In PostgreSQL, use CONCURRENTLY:

# In the migration:
def upgrade():
    op.execute("CREATE INDEX CONCURRENTLY ix_orders_status ON orders (status)")

def downgrade():
    op.execute("DROP INDEX CONCURRENTLY ix_orders_status")

Do not use op.create_index() for large production tables - it acquires a full table lock.

Source

git clone https://github.com/mrsknetwork/supernova/blob/main/skills/migrations/SKILL.mdView on GitHub

Overview

Manages schema changes with Alembic, generating migrations, reviewing them for safety, running them in the correct order, and handling rollbacks. Use it when adding tables, modifying columns, adding indexes, or renaming database objects, while avoiding common data-loss mistakes.

How This Skill Works

Set up Alembic once and wire env.py to your async engine and models. After modifying a SQLAlchemy model, generate a migration with alembic revision --autogenerate -m "your_message" and review the file for red flags. Apply migrations with alembic upgrade head, and rollback with alembic downgrade as needed, including the safe NOT NULL migration pattern described in Step 3.

When to Use It

  • Adding a new table
  • Modifying an existing column (including nullable/not-null transitions)
  • Adding an index or unique constraint
  • Renaming a table or column
  • Upgrading or downgrading the database schema in a controlled deployment

Quick Start

  1. Step 1: alembic init alembic (first-time setup) and configure env.py
  2. Step 2: Modify models, run alembic revision --autogenerate -m "your_message" and review the file
  3. Step 3: Apply and verify: alembic upgrade head; check current; if needed, use alembic downgrade <revision_id>

Best Practices

  • Always review the autogenerated migration before applying, watching for dropped tables/columns
  • Back up the database before running migrations in production
  • Avoid adding NOT NULL columns to non-empty tables; apply via the 3-step pattern (add nullable, backfill, then set NOT NULL)
  • Confirm renames use type-preserving checks (compare_type) and proper env.py configuration
  • Ensure every migration has a downgrade() to enable safe rollbacks

Example Use Cases

  • Add a new users table to support authentication
  • Change a user's bio column from nullable to non-null with a backfill
  • Create an index on emails for faster lookups
  • Rename a table from user_accounts to users and adjust metadata
  • Rollback a deployment by downgrading to the previous revision

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers