Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Generate database migration scripts for Liquibase, Flyway, Alembic with rollback safety, data preservation, and zero-downtime patterns
.claude/skills/williamzujkowski-database-migration-script-generator/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 215% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 171% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 153% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 131% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 109% | 0% |
Trigger conditions:
Not for:
Time normalization:
NOW_ET using NIST/time.gov semantics (America/New_York, ISO-8601)NOW_ET for all citation access datesInput validation:
migration_tool must be one of: liquibase, flyway, alembicdatabase must be one of: postgresql, mysql, sqlserver, oraclemigration_type must be one of: schema, data, hybriddowntime_allowed must be boolean (true/false)migration_description must be non-empty and descriptiveSource freshness:
Fast path for simple DDL changes:
Liquibase (XML/YAML) accessed 2025-10-26T03:51:54-04:00 xml <changeSet id="add-email-column" author="migration-generator"> <addColumn tableName="users"> <column name="email" type="VARCHAR(255)"/> </addColumn> <rollback> <dropColumn tableName="users" columnName="email"/> </rollback> </changeSet>
Flyway (SQL) accessed 2025-10-26T03:51:54-04:00 sql -- V1__add_email_column.sql ALTER TABLE users ADD COLUMN email VARCHAR(255);
Alembic (Python) accessed 2025-10-26T03:51:54-04:00 python def upgrade(): op.add_column('users', sa.Column('email', sa.String(255)))
def downgrade(): op.drop_column('users', 'email')
Decision: If simple schema change without data → STOP at T1; otherwise proceed to T2.
Extended migrations with data transformations:
Backfill Existing Data python # Alembic: Backfill default values for new column def upgrade(): op.add_column('users', sa.Column('status', sa.String(20), nullable=True)) # Backfill existing rows op.execute("UPDATE users SET status = 'active' WHERE status IS NULL") # Make NOT NULL after backfill op.alter_column('users', 'status', nullable=False)
Data Transformation python # Alembic: Split full_name into first_name and last_name def upgrade(): op.add_column('users', sa.Column('first_name', sa.String(100))) op.add_column('users', sa.Column('last_name', sa.String(100))) # Transform data connection = op.get_bind() users = connection.execute("SELECT id, full_name FROM users").fetchall() for user_id, full_name in users: parts = full_name.split(' ', 1) first = parts[0] last = parts[1] if len(parts) > 1 else '' connection.execute( "UPDATE users SET first_name = %s, last_name = %s WHERE id = %s", (first, last, user_id) ) op.drop_column('users', 'full_name')
sql -- Post-migration validation SELECT COUNT(*) FROM users WHERE email IS NULL; -- Should be 0 SELECT COUNT(*) FROM users WHERE status NOT IN ('active', 'inactive'); -- Should be 0
PostgreSQL accessed 2025-10-26T03:51:54-04:00
ALTER TABLE ... SET NOT NULL with CHECK constraint firstCONCURRENTLY for index creation (zero-downtime)pg_stat_progress_create_index to monitor long operationsMySQL accessed 2025-10-26T03:51:54-04:00
ALGORITHM=INPLACE, LOCK=NONEALTER TABLE that requires table copy (pre-8.0)pt-online-schema-change for large tables (Percona Toolkit)SQL Server
WITH (ONLINE = ON) for index operationsSSMS execution plan analysisSCHEMA_ONLY copies for large data migrationsAdvanced patterns for production systems:
Phase 1: Expand (Add new schema) python # Migration 001: Add new column, keep old column def upgrade(): op.add_column('users', sa.Column('email_new', sa.String(255))) # Trigger to sync old → new during transition op.execute(""" CREATE TRIGGER sync_email_new BEFORE UPDATE ON users FOR EACH ROW BEGIN SET NEW.email_new = NEW.email; END; """)
Phase 2: Migrate Data python # Migration 002: Backfill new column def upgrade(): op.execute("UPDATE users SET email_new = email WHERE email_new IS NULL")
Phase 3: Contract (Remove old schema) python # Migration 003: Drop old column (after application updated) def upgrade(): op.execute("DROP TRIGGER IF EXISTS sync_email_new") op.drop_column('users', 'email') op.alter_column('users', 'email_new', new_column_name='email')
PostgreSQL CONCURRENTLY sql -- Flyway: V5__add_email_index.sql CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
-- Validation SELECT schemaname, tablename, indexname, indexdef FROM pg_indexes WHERE indexname = 'idx_users_email';
MySQL Online DDL sql -- Flyway: V6__add_composite_index.sql ALTER TABLE users ADD INDEX idx_email_status (email, status) ALGORITHM=INPLACE, LOCK=NONE;
python # Migration 010: Create shadow table with new schema def upgrade(): op.create_table( 'users_new', sa.Column('id', sa.Integer, primary_key=True), sa.Column('email', sa.String(255), nullable=False, index=True), sa.Column('status', sa.String(20), nullable=False) ) # Stream data from old → new table op.execute(""" INSERT INTO users_new (id, email, status) SELECT id, email, COALESCE(status, 'active') FROM users """) # Atomic rename (downtime: milliseconds) op.rename_table('users', 'users_old') op.rename_table('users_new', 'users')
markdown ## Deployment Steps
### Pre-Migration
### Migration Execution
SELECT * FROM pg_locks WHERE NOT grantedSELECT COUNT(*) FROM users### Post-Migration
### Rollback Procedure (if needed)
flyway undo or alembic downgrade -1SELECT * FROM users LIMIT 10Migration Tool Selection:
Migration Strategy by Downtime Allowance:
Database-Specific Patterns:
Abort Conditions:
Data Preservation Checks:
Schema (JSON):
json{ "migration_tool": "liquibase | flyway | alembic", "database": "postgresql | mysql | sqlserver | oracle", "migration_type": "schema | data | hybrid", "downtime_allowed": "boolean", "migration_script": { "filename": "string (e.g., V5__add_email_column.sql)", "content": "string (tool-specific migration code)" }, "rollback_script": { "filename": "string (e.g., U5__undo_email_column.sql)", "content": "string (inverse migration code)", "manual_steps": ["string (if auto-rollback unsafe)"] }, "validation_tests": [ { "description": "string", "query": "string (SQL validation query)", "expected_result": "string" } ], "deployment_guide": { "pre_migration_steps": ["string"], "execution_steps": ["string"], "post_migration_steps": ["string"], "rollback_procedure": ["string"], "estimated_duration": "string (e.g., '5 minutes', '2 hours')" }, "warnings": ["string (potential issues or breaking changes)"], "timestamp": "ISO-8601 string (NOW_ET)" }
Required Fields:
migration_tool, database, migration_type, downtime_allowed, migration_script, rollback_script, validation_tests, deployment_guide, timestampSafety Guarantees:
Example 1: Simple Column Addition (Alembic + PostgreSQL)
python"""Add email column to users table with NOT NULL constraint Revision ID: a1b2c3d4e5f6 Revises: previous_revision Create Date: 2025-10-26 03:51:54.000000 """ from alembic import op import sqlalchemy as sa def upgrade(): # Add column as nullable first op.add_column('users', sa.Column('email', sa.String(255), nullable=True)) # Backfill with placeholder (application will update) op.execute("UPDATE users SET email = CONCAT('user', id, '@example.com') WHERE email IS NULL") # Add NOT NULL constraint op.alter_column('users', 'email', nullable=False) # Add index for performance op.create_index('idx_users_email', 'users', ['email'], unique=True) def downgrade(): op.drop_index('idx_users_email', table_name='users') op.drop_column('users', 'email')
Token Budgets:
Safety:
Auditability:
Determinism:
Performance:
Official Documentation (accessed 2025-10-26T03:51:54-04:00):
Migration Patterns:
Best Practices:
Tool Comparisons:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | fail→pass | 13,572 | 11,707 | -14% | 1 | 1 | 0% | 2,183 | 6,874 | +215% | 0 | 0 | — |
case-05 | pass→pass | 11,932 | 7,874 | -34% | 1 | 1 | 0% | 1,729 | 6,128 | +254% | 0 | 0 | — |
case-01 | fail→pass | 15,108 | 13,383 | -11% | 1 | 1 | 0% | 2,784 | 7,548 | +171% | 0 | 0 | — |
case-02 | fail→pass | 15,435 | 14,454 | -6% | 1 | 1 | 0% | 3,130 | 7,925 | +153% | 0 | 0 | — |
case-03 | fail→pass | 19,422 | 20,819 | +7% | 1 | 1 | 0% | 3,832 | 8,858 | +131% | 0 | 0 | — |
case-06 | pass→pass | 20,895 | 17,216 | -18% | 1 | 1 | 0% | 1,999 | 7,766 | +288% | 0 | 0 | — |
case-07 | fail→pass | 19,892 | 13,328 | -33% | 1 | 1 | 0% | 3,540 | 7,416 | +109% | 0 | 0 | — |
case-08 | fail→pass | 8,840 | 9,617 | +9% | 1 | 1 | 0% | 1,676 | 6,721 | +301% | 0 | 0 | — |
case-09 | fail→pass | 11,147 | 12,050 | +8% | 1 | 1 | 0% | 2,088 | 7,244 | +247% | 0 | 0 | — |
case-10 | pass→pass | 4,556 | 12,545 | +175% | 1 | 1 | 0% | 772 | 7,306 | +846% | 0 | 0 | — |
case-11 | pass→pass | 6,644 | 10,265 | +55% | 1 | 1 | 0% | 1,206 | 6,910 | +473% | 0 | 0 | — |
case-12 | pass→pass | 23,625 | 14,505 | -39% | 1 | 1 | 0% | 3,166 | 7,669 | +142% | 0 | 0 | — |
case-13 | fail→fail | 6,706 | 9,598 | +43% | 1 | 1 | 0% | 1,112 | 6,692 | +502% | 0 | 0 | — |
case-22 | pass→pass | 17,437 | 14,212 | -18% | 1 | 1 | 0% | 3,020 | 7,506 | +149% | 0 | 0 | — |
case-14 | fail→fail | 8,165 | 15,823 | +94% | 1 | 1 | 0% | 1,623 | 6,622 | +308% | 0 | 0 | — |
case-15 | pass→pass | 12,062 | 15,819 | +31% | 1 | 1 | 0% | 2,127 | 7,867 | +270% | 0 | 0 | — |
case-16 | fail→pass | 6,503 | 9,455 | +45% | 1 | 1 | 0% | 1,395 | 6,795 | +387% | 0 | 0 | — |
case-17 | pass→pass | 5,583 | 9,846 | +76% | 1 | 1 | 0% | 1,089 | 6,827 | +527% | 0 | 0 | — |
case-18 | pass→pass | 15,519 | 12,023 | -23% | 1 | 1 | 0% | 2,813 | 7,322 | +160% | 0 | 0 | — |
case-19 | pass→fail | 16,155 | 17,178 | +6% | 1 | 1 | 0% | 3,022 | 8,391 | +178% | 0 | 0 | — |
case-20 | pass→pass | 5,186 | 7,781 | +50% | 1 | 1 | 0% | 1,022 | 6,385 | +525% | 0 | 0 | — |
case-21 | fail→pass | 6,985 | 10,272 | +47% | 1 | 1 | 0% | 1,299 | 7,023 | +441% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +36 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.