| name | migration-specialist |
| version | 2.0 |
| trigger | database migration, schema change, zero-downtime, drizzle migrate |
| description | Database migrations, schema evolution, data transformation, and zero-downtime deployments. Use when creating migrations, evolving database schema, transforming data, or planning deployment strategies. |
Migration Specialist
Migration Patterns
Safe Schema Changes
export async function up(db: Kysely<any>) {
await db.schema
.alterTable('users')
.addColumn('tier', 'integer')
.execute();
await db.updateTable('users')
.set({ tier: 1 })
.where('tier', 'is', null)
.execute();
await db.schema
.alterTable('users')
.alterColumn('tier', col => col.setNotNull())
.execute();
await db.schema
.createIndex('user_tier_idx')
.on('users')
.column('tier')
.execute();
}
export async function down(db: Kysely<any>) {
await db.schema
.alterTable('users')
.dropColumn('tier')
.execute();
}
Zero-Downtime Migration
export async function up(db: Kysely<any>) {
await db.schema
.alterTable('users')
.addColumn('email_address', 'varchar(255)')
.execute();
await db.execute(sql`
UPDATE users
SET email_address = email
WHERE email_address IS NULL
`);
await db.execute(sql`
CREATE OR REPLACE FUNCTION sync_email()
RETURNS TRIGGER AS $$
BEGIN
NEW.email_address = NEW.email;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
`);
}
export async function up_final(db: Kysely<any>) {
await db.schema
.alterTable('users')
.dropColumn('email')
.execute();
await db.execute(sql`DROP FUNCTION sync_email`);
}
Data Transformation
Batch Processing
export async function transformData() {
const batchSize = 1000;
let offset = 0;
let hasMore = true;
while (hasMore) {
const rows = await db
.selectFrom('legacy_data')
.selectAll()
.limit(batchSize)
.offset(offset)
.execute();
if (rows.length === 0) {
hasMore = false;
continue;
}
const transformed = rows.map(transformRow);
await db.transaction().execute(async trx => {
for (const row of transformed) {
await trx
.insertInto('new_data')
.values(row)
.execute();
}
});
offset += batchSize;
console.log(`Processed ${offset} rows`);
}
}
Migration Testing
describe('Migration 0001', () => {
beforeAll(async () => {
await migrate.downTo('0000');
});
it('adds tier column', async () => {
await migrate.upTo('0001');
const result = await db
.insertInto('users')
.values({ email: 'test@test.com', tier: 2 })
.returningAll()
.executeTakeFirst();
expect(result.tier).toBe(2);
});
it('is reversible', async () => {
await migrate.upTo('0001');
await migrate.downTo('0000');
const columns = await db.execute(sql`
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'users' AND column_name = 'tier'
`);
expect(columns.rows).toHaveLength(0);
});
});
Deployment Strategies
Blue-Green Deployment
export class BlueGreenDeployer {
async deploy(newVersion: string) {
await this.deployTo('green', newVersion);
await this.runMigrations('green');
const healthy = await this.healthCheck('green');
if (!healthy) {
throw new Error('Green deployment unhealthy');
}
await this.switchTraffic('green');
await this.waitAndCleanup('blue', '30m');
}
}
Feature Flags for Schema
export async function migrateWithFeatureFlag() {
await db.schema
.alterTable('users')
.addColumn('new_feature_data', 'jsonb')
.execute();
if (await featureFlags.isEnabled('new-feature')) {
}
}
Protocol
- Assess — Understand the specific requirement and context
- Plan — Determine the approach based on the guidance above
- Execute — Apply the migration specialist methodology
- Verify — Validate the output against expected standards
- Report — Document results and any issues encountered