Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Plan and execute database schema migrations safely with rollback strategies and zero-downtime deployment patterns.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
SKILL.md
1---2name: database-migration3description: Execute database migrations across ORMs and platforms with zero-downtime strategies, data transformation, and rollback procedures. Use when migrating databases, changing schemas, performing data transformations, or implementing zero-downtime deployment strategies.4---56# Database Migration78Master database schema and data migrations across ORMs (Sequelize, TypeORM, Prisma), including rollback strategies and zero-downtime deployments.910## When to Use This Skill1112- Migrating between different ORMs13- Performing schema transformations14- Moving data between databases15- Implementing rollback procedures16- Zero-downtime deployments17- Database version upgrades18- Data model refactoring1920## ORM Migrations2122### Sequelize Migrations2324```javascript25// migrations/20231201-create-users.js26module.exports = {27up: async (queryInterface, Sequelize) => {28await queryInterface.createTable("users", {29id: {30type: Sequelize.INTEGER,31primaryKey: true,32autoIncrement: true,33},34email: {35type: Sequelize.STRING,36unique: true,37allowNull: false,38},39createdAt: Sequelize.DATE,40updatedAt: Sequelize.DATE,41});42},4344down: async (queryInterface, Sequelize) => {45await queryInterface.dropTable("users");46},47};4849// Run: npx sequelize-cli db:migrate50// Rollback: npx sequelize-cli db:migrate:undo51```5253### TypeORM Migrations5455```typescript56// migrations/1701234567-CreateUsers.ts57import { MigrationInterface, QueryRunner, Table } from "typeorm";5859export class CreateUsers1701234567 implements MigrationInterface {60public async up(queryRunner: QueryRunner): Promise<void> {61await queryRunner.createTable(62new Table({63name: "users",64columns: [65{66name: "id",67type: "int",68isPrimary: true,69isGenerated: true,70generationStrategy: "increment",71},72{73name: "email",74type: "varchar",75isUnique: true,76},77{78name: "created_at",79type: "timestamp",80default: "CURRENT_TIMESTAMP",81},82],83}),84);85}8687public async down(queryRunner: QueryRunner): Promise<void> {88await queryRunner.dropTable("users");89}90}9192// Run: npm run typeorm migration:run93// Rollback: npm run typeorm migration:revert94```9596### Prisma Migrations9798```prisma99// schema.prisma100model User {101id Int @id @default(autoincrement())102email String @unique103createdAt DateTime @default(now())104}105106// Generate migration: npx prisma migrate dev --name create_users107// Apply: npx prisma migrate deploy108```109110## Schema Transformations111112### Adding Columns with Defaults113114```javascript115// Safe migration: add column with default116module.exports = {117up: async (queryInterface, Sequelize) => {118await queryInterface.addColumn("users", "status", {119type: Sequelize.STRING,120defaultValue: "active",121allowNull: false,122});123},124125down: async (queryInterface) => {126await queryInterface.removeColumn("users", "status");127},128};129```130131### Renaming Columns (Zero Downtime)132133```javascript134// Step 1: Add new column135module.exports = {136up: async (queryInterface, Sequelize) => {137await queryInterface.addColumn("users", "full_name", {138type: Sequelize.STRING,139});140141// Copy data from old column142await queryInterface.sequelize.query("UPDATE users SET full_name = name");143},144145down: async (queryInterface) => {146await queryInterface.removeColumn("users", "full_name");147},148};149150// Step 2: Update application to use new column151152// Step 3: Remove old column153module.exports = {154up: async (queryInterface) => {155await queryInterface.removeColumn("users", "name");156},157158down: async (queryInterface, Sequelize) => {159await queryInterface.addColumn("users", "name", {160type: Sequelize.STRING,161});162},163};164```165166### Changing Column Types167168```javascript169module.exports = {170up: async (queryInterface, Sequelize) => {171// For large tables, use multi-step approach172173// 1. Add new column174await queryInterface.addColumn("users", "age_new", {175type: Sequelize.INTEGER,176});177178// 2. Copy and transform data179await queryInterface.sequelize.query(`180UPDATE users181SET age_new = CAST(age AS INTEGER)182WHERE age IS NOT NULL183`);184185// 3. Drop old column186await queryInterface.removeColumn("users", "age");187188// 4. Rename new column189await queryInterface.renameColumn("users", "age_new", "age");190},191192down: async (queryInterface, Sequelize) => {193await queryInterface.changeColumn("users", "age", {194type: Sequelize.STRING,195});196},197};198```199200## Data Transformations201202### Complex Data Migration203204```javascript205module.exports = {206up: async (queryInterface, Sequelize) => {207// Get all records208const [users] = await queryInterface.sequelize.query(209"SELECT id, address_string FROM users",210);211212// Transform each record213for (const user of users) {214const addressParts = user.address_string.split(",");215216await queryInterface.sequelize.query(217`UPDATE users218SET street = :street,219city = :city,220state = :state221WHERE id = :id`,222{223replacements: {224id: user.id,225street: addressParts[0]?.trim(),226city: addressParts[1]?.trim(),227state: addressParts[2]?.trim(),228},229},230);231}232233// Drop old column234await queryInterface.removeColumn("users", "address_string");235},236237down: async (queryInterface, Sequelize) => {238// Reconstruct original column239await queryInterface.addColumn("users", "address_string", {240type: Sequelize.STRING,241});242243await queryInterface.sequelize.query(`244UPDATE users245SET address_string = CONCAT(street, ', ', city, ', ', state)246`);247248await queryInterface.removeColumn("users", "street");249await queryInterface.removeColumn("users", "city");250await queryInterface.removeColumn("users", "state");251},252};253```254255## Rollback Strategies256257### Transaction-Based Migrations258259```javascript260module.exports = {261up: async (queryInterface, Sequelize) => {262const transaction = await queryInterface.sequelize.transaction();263264try {265await queryInterface.addColumn(266"users",267"verified",268{ type: Sequelize.BOOLEAN, defaultValue: false },269{ transaction },270);271272await queryInterface.sequelize.query(273"UPDATE users SET verified = true WHERE email_verified_at IS NOT NULL",274{ transaction },275);276277await transaction.commit();278} catch (error) {279await transaction.rollback();280throw error;281}282},283284down: async (queryInterface) => {285await queryInterface.removeColumn("users", "verified");286},287};288```289290### Checkpoint-Based Rollback291292```javascript293module.exports = {294up: async (queryInterface, Sequelize) => {295// Create backup table296await queryInterface.sequelize.query(297"CREATE TABLE users_backup AS SELECT * FROM users",298);299300try {301// Perform migration302await queryInterface.addColumn("users", "new_field", {303type: Sequelize.STRING,304});305306// Verify migration307const [result] = await queryInterface.sequelize.query(308"SELECT COUNT(*) as count FROM users WHERE new_field IS NULL",309);310311if (result[0].count > 0) {312throw new Error("Migration verification failed");313}314315// Drop backup316await queryInterface.dropTable("users_backup");317} catch (error) {318// Restore from backup319await queryInterface.sequelize.query("DROP TABLE users");320await queryInterface.sequelize.query(321"CREATE TABLE users AS SELECT * FROM users_backup",322);323await queryInterface.dropTable("users_backup");324throw error;325}326},327};328```329330## Zero-Downtime Migrations331332### Blue-Green Deployment Strategy333334```javascript335// Phase 1: Make changes backward compatible336module.exports = {337up: async (queryInterface, Sequelize) => {338// Add new column (both old and new code can work)339await queryInterface.addColumn("users", "email_new", {340type: Sequelize.STRING,341});342},343};344345// Phase 2: Deploy code that writes to both columns346347// Phase 3: Backfill data348module.exports = {349up: async (queryInterface) => {350await queryInterface.sequelize.query(`351UPDATE users352SET email_new = email353WHERE email_new IS NULL354`);355},356};357358// Phase 4: Deploy code that reads from new column359360// Phase 5: Remove old column361module.exports = {362up: async (queryInterface) => {363await queryInterface.removeColumn("users", "email");364},365};366```367368## Cross-Database Migrations369370### PostgreSQL to MySQL371372```javascript373// Handle differences374module.exports = {375up: async (queryInterface, Sequelize) => {376const dialectName = queryInterface.sequelize.getDialect();377378if (dialectName === "mysql") {379await queryInterface.createTable("users", {380id: {381type: Sequelize.INTEGER,382primaryKey: true,383autoIncrement: true,384},385data: {386type: Sequelize.JSON, // MySQL JSON type387},388});389} else if (dialectName === "postgres") {390await queryInterface.createTable("users", {391id: {392type: Sequelize.INTEGER,393primaryKey: true,394autoIncrement: true,395},396data: {397type: Sequelize.JSONB, // PostgreSQL JSONB type398},399});400}401},402};403```404