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:
For example:
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()