Migrations - Manticore CockroachDB Client
Description: A Python client for CockroachDB with sync and async support

Skip to content

# Database Migrations

This guide covers how to use the migration tools to manage database schema changes with the Manticore CockroachDB client.

## Introduction to Migrations

Database migrations are a way to manage changes to your database schema over time. They allow you to:

- Track changes to your database schema
- Apply changes in a consistent way across different environments
- Roll back changes if needed
- Keep a history of all schema changes

The Manticore CockroachDB client provides both synchronous and asynchronous migration tools.

## Synchronous Migrations

### Setting Up Migrations

    from manticore_cockroachdb import Database, Migration

    # Connect to database
    db = Database(database="example_db")

    # Create migration instance
    migration = Migration(db, migrations_dir="./migrations")

### Creating Migrations

    # Create a migration to create a users table
    migration.create_migration(
        "create users table",  # Description of the migration
        """
        CREATE TABLE users (
            id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
            name TEXT NOT NULL,
            email TEXT UNIQUE NOT NULL,
            created_at TIMESTAMPTZ DEFAULT now()
        );
        """,  # Forward SQL (applied during migration)
        """
        DROP TABLE users;
        """  # Undo SQL (applied during rollback)
    )

    # Create a migration to add a column
    migration.create_migration(
        "add age column",
        "ALTER TABLE users ADD COLUMN age INTEGER;",
        "ALTER TABLE users DROP COLUMN age;"
    )

### Loading Migrations

    # Load all migrations from the migrations directory
    migrations = migration.load_migrations()

    # Print loaded migrations
    for m in migrations:
        print(f"Version {m.version}: {m.description}")

### Applying Migrations

    # Apply all pending migrations
    applied_count = migration.migrate()
    print(f"Applied {applied_count} migrations")

    # Apply migrations up to a specific version
    applied_count = migration.migrate(target_version="20230101120000")
    print(f"Applied {applied_count} migrations")

### Rolling Back Migrations

    # Roll back the last migration
    rollback_count = migration.rollback()
    print(f"Rolled back {rollback_count} migrations")

    # Roll back multiple migrations
    rollback_count = migration.rollback(count=3)
    print(f"Rolled back {rollback_count} migrations")

    # Roll back to a specific version
    rollback_count = migration.rollback(target_version="20230101120000")
    print(f"Rolled back {rollback_count} migrations")

### Checking Migration Status

    # Get the current migration version
    current_version = migration.get_current_version()
    print(f"Current version: {current_version}")

    # Check if a specific migration has been applied
    is_applied = migration.is_applied("20230101120000")
    print(f"Migration 20230101120000 applied: {is_applied}")

    # Get all applied migrations
    applied_migrations = migration.get_applied_migrations()
    for m in applied_migrations:
        print(f"Version {m['version']}: {m['description']} (applied at {m['applied_at']})")

## Asynchronous Migrations

### Setting Up Migrations

    import asyncio
    from manticore_cockroachdb import AsyncDatabase, AsyncMigration

    async def main():
        # Connect to database
        db = AsyncDatabase(database="example_db")
        await db.connect()

        try:
            # Create migration instance
            migration = AsyncMigration(db, migrations_dir="./async_migrations")

            # Rest of the migration code...
        finally:
            await db.close()

    # Run the async function
    asyncio.run(main())

### Creating Migrations

    # Create a migration to create a users table
    await migration.create_migration(
        "create async users table",
        """
        CREATE TABLE async_users (
            id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
            name TEXT NOT NULL,
            email TEXT UNIQUE NOT NULL,
            created_at TIMESTAMPTZ DEFAULT now()
        );
        """,
        """
        DROP TABLE async_users;
        """
    )

    # Create a migration to add a column
    await migration.create_migration(
        "add email verification",
        "ALTER TABLE async_users ADD COLUMN email_verified BOOLEAN DEFAULT FALSE;",
        "ALTER TABLE async_users DROP COLUMN email_verified;"
    )

### Loading Migrations

    # Load all migrations from the migrations directory
    migrations = await migration.load_migrations()

    # Print loaded migrations
    for m in migrations:
        print(f"Version {m.version}: {m.description}")

### Applying Migrations

    # Apply all pending migrations
    applied_count = await migration.migrate()
    print(f"Applied {applied_count} migrations")

    # Apply migrations up to a specific version
    applied_count = await migration.migrate(target_version="20230101120000")
    print(f"Applied {applied_count} migrations")

### Rolling Back Migrations

    # Roll back the last migration
    rollback_count = await migration.rollback()
    print(f"Rolled back {rollback_count} migrations")

    # Roll back multiple migrations
    rollback_count = await migration.rollback(count=3)
    print(f"Rolled back {rollback_count} migrations")

    # Roll back to a specific version
    rollback_count = await migration.rollback(target_version="20230101120000")
    print(f"Rolled back {rollback_count} migrations")

### Checking Migration Status

    # Get the current migration version
    current_version = await migration.get_current_version()
    print(f"Current version: {current_version}")

    # Check if a specific migration has been applied
    is_applied = await migration.is_applied("20230101120000")
    print(f"Migration 20230101120000 applied: {is_applied}")

    # Get all applied migrations
    applied_migrations = await migration.get_applied_migrations()
    for m in applied_migrations:
        print(f"Version {m['version']}: {m['description']} (applied at {m['applied_at']})")

## Migration File Format

Migration files are stored in the migrations directory with the following naming convention:

    <version>_<description>.sql

For example:

    20230101120000_create_users_table.sql
    20230102130000_add_age_column.sql

Each migration file contains both the forward and undo SQL, separated by a special marker:

    -- Forward migration
    CREATE TABLE users (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        name TEXT NOT NULL,
        email TEXT UNIQUE NOT NULL,
        created_at TIMESTAMPTZ DEFAULT now()
    );

    -- @UNDO

    -- Undo migration
    DROP TABLE users;

## Complete Example

    from manticore_cockroachdb import Database, Migration

    # Connect to database
    db = Database(database="example_db")

    try:
        # Create migration instance
        migration = Migration(db, migrations_dir="./migrations")

        # Create migrations
        migration.create_migration(
            "create users table",
            """
            CREATE TABLE users (
                id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
                name TEXT NOT NULL,
                email TEXT UNIQUE NOT NULL,
                created_at TIMESTAMPTZ DEFAULT now()
            );
            """,
            """
            DROP TABLE users;
            """
        )

        migration.create_migration(
            "add age column",
            "ALTER TABLE users ADD COLUMN age INTEGER;",
            "ALTER TABLE users DROP COLUMN age;"
        )

        # Load migrations
        migrations = migration.load_migrations()
        print(f"Loaded {len(migrations)} migrations")

        for m in migrations:
            print(f"Version {m.version}: {m.description}")

        # Apply migrations
        applied = migration.migrate()
        print(f"Applied {applied} migrations")

        # Insert test data
        db.insert("users", {
            "name": "Migration Test User",
            "email": "migrate@example.com",
            "age": 30
        })

        # Show user data
        users = db.select("users")
        print(f"Users in database: {len(users)}")

        # Rollback last migration
        rollback_count = migration.rollback(count=1)
        print(f"Rolled back {rollback_count} migrations")

        # Apply all migrations again
        applied = migration.migrate()
        print(f"Applied {applied} migrations")

    finally:
        # Clean up
        db.drop_table("users", if_exists=True)
        db.drop_table("_migrations", if_exists=True)

        # Close database connection
        db.close()