skills/sql-migrations/SKILL.md
Database migration mastery with Prisma, Drizzle, Knex, TypeORM, Alembic, Flyway, and golang-migrate. Use when user asks to "create a migration", "update database schema", "add a column", "remove a column", "rename a table", "set up Prisma", "rollback migration", "write SQL migration", "set up Drizzle", "zero-downtime migration", "backfill data", "squash migrations", "seed database", "migrate production", or any database schema change tasks.
npx skillsauth add 1mangesh1/dev-skills-collection sql-migrationsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A migration is a versioned, incremental change to a database schema. Migrations run in order and track which have been applied via a metadata table (e.g., schema_migrations, _prisma_migrations).
Up/Down: The up migration applies the change. The down migration reverses it. Not all changes are reversible (dropping a column with data destroys it).
Versioned vs Repeatable: Versioned migrations run once in order (001, 002, ...). Repeatable migrations (Flyway R__ prefix) re-run whenever their checksum changes -- useful for views, functions, and stored procedures.
Idempotent migrations: Use IF NOT EXISTS / IF EXISTS guards so a migration can be re-run safely without erroring on already-applied state:
CREATE TABLE IF NOT EXISTS users (...);
ALTER TABLE users ADD COLUMN IF NOT EXISTS role TEXT;
DROP INDEX IF EXISTS idx_users_email;
npm install prisma @prisma/client
npx prisma init
Commands:
npx prisma migrate dev --name add_users_table # create + apply migration (dev)
npx prisma migrate deploy # apply pending migrations (production)
npx prisma migrate reset # drop + recreate + seed (destructive)
npx prisma migrate status # check migration status
npx prisma generate # regenerate client
npx prisma db push # push schema without migration file (prototyping)
npx prisma db seed # run seed script
npx prisma studio # open database GUI
npm install drizzle-orm drizzle-kit
Commands:
npx drizzle-kit generate # generate migration from schema diff
npx drizzle-kit migrate # apply migrations
npx drizzle-kit push # push schema directly (prototyping)
npx drizzle-kit studio # open Drizzle Studio
npx drizzle-kit drop # drop a migration
npm install knex pg
npx knex init # create knexfile.js
npx knex migrate:make add_users_table # create migration
npx knex migrate:latest # run pending
npx knex migrate:rollback # undo last batch
npx knex migrate:rollback --all # undo everything
npx knex seed:make seed_users # create seed file
npx knex seed:run # run seeds
npx typeorm migration:create src/migrations/AddUsersTable
npx typeorm migration:generate -d src/data-source.ts src/migrations/AddUsersTable
npx typeorm migration:run -d src/data-source.ts
npx typeorm migration:revert -d src/data-source.ts
alembic init alembic # initialize
alembic revision --autogenerate -m "add users table"
alembic upgrade head # apply all
alembic downgrade -1 # undo last
alembic history # list migrations
alembic current # show current revision
flyway migrate # apply pending
flyway info # show status
flyway validate # verify applied match local
flyway repair # fix metadata table
flyway clean # drop all objects (destructive)
# Naming: V1__Create_users.sql, V2__Add_email_index.sql
# Repeatable: R__Create_views.sql (re-runs when checksum changes)
migrate create -ext sql -dir db/migrations -seq add_users_table
migrate -path db/migrations -database "$DB_URL" up
migrate -path db/migrations -database "$DB_URL" down 1
migrate -path db/migrations -database "$DB_URL" force 3 # fix dirty state
Never add a NOT NULL column without a default to a table that has existing rows. The safe sequence:
ALTER TABLE users ADD COLUMN role TEXT;UPDATE users SET role = 'user' WHERE role IS NULL;ALTER TABLE users ALTER COLUMN role SET NOT NULL;Never drop a column that code still reads. The safe sequence:
ALTER TABLE users DROP COLUMN legacy_field;Renaming breaks existing queries instantly. The safe sequence:
For tables, the same expand-migrate-contract pattern applies. Alternatively, create a view with the old name during transition.
On Postgres, CREATE INDEX locks the table for writes. Use CONCURRENTLY:
-- Safe: does not block writes (Postgres only, cannot run inside a transaction)
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
-- Drop safely too
DROP INDEX CONCURRENTLY IF EXISTS idx_users_email;
In migration tools that wrap each file in a transaction, you must disable the transaction for that specific migration or run the index creation separately.
-- Add foreign key without locking (Postgres)
ALTER TABLE posts ADD CONSTRAINT fk_posts_author
FOREIGN KEY (author_id) REFERENCES users(id) NOT VALID;
ALTER TABLE posts VALIDATE CONSTRAINT fk_posts_author;
-- Add check constraint without locking (Postgres 12+)
ALTER TABLE users ADD CONSTRAINT chk_role
CHECK (role IN ('admin', 'user', 'moderator')) NOT VALID;
ALTER TABLE users VALIDATE CONSTRAINT chk_role;
Keep them separate. Schema migrations change structure (DDL). Data migrations change content (DML). Mixing them causes problems:
-- Schema migration: 005_add_status_column.sql
ALTER TABLE orders ADD COLUMN status TEXT;
-- Data migration: 006_backfill_status.sql (separate file)
UPDATE orders SET status = 'completed' WHERE completed_at IS NOT NULL;
UPDATE orders SET status = 'pending' WHERE completed_at IS NULL;
For tables with millions of rows, a single ALTER TABLE or UPDATE can lock the table or run for hours.
Batched updates: Process rows in chunks to avoid long locks and transaction log bloat:
-- Backfill in batches of 10,000
DO $$
DECLARE batch_size INT := 10000;
BEGIN
LOOP
UPDATE orders SET status = 'pending'
WHERE id IN (SELECT id FROM orders WHERE status IS NULL LIMIT batch_size);
EXIT WHEN NOT FOUND;
COMMIT;
END LOOP;
END $$;
Online schema change tools (MySQL): pt-online-schema-change (Percona) and gh-ost (GitHub) create a shadow table, copy data, replay binlog changes, then swap. Use these for any DDL on large MySQL tables in production.
# pt-online-schema-change
pt-online-schema-change --alter "ADD COLUMN status VARCHAR(50)" \
--execute D=mydb,t=orders
# gh-ost
gh-ost --alter "ADD COLUMN status VARCHAR(50)" \
--database=mydb --table=orders --execute
When migration count grows unwieldy (100+ files), squash them:
pg_dump --schema-only > baseline.sqlPrisma: npx prisma migrate diff can generate a diff between two states. Drizzle and Knex do not have built-in squash -- do it manually.
Always write down migrations but understand their limits:
-- Down migration: 003_add_role.down.sql
ALTER TABLE users DROP COLUMN IF EXISTS role;
Most migration tools use advisory locks to prevent two processes from running migrations simultaneously. If your deployment runs multiple instances:
migrate force (golang-migrate) / flyway repair.Run migrations in a dedicated CI/CD step, not at application boot.
up then down then up again to verify reversibility.Seeds populate the database with initial or test data. Keep seeds idempotent.
-- Idempotent seed
INSERT INTO roles (name) VALUES ('admin'), ('user'), ('moderator')
ON CONFLICT (name) DO NOTHING;
// Prisma seed (prisma/seed.ts, configured in package.json "prisma.seed")
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
await prisma.role.upsert({
where: { name: "admin" },
update: {},
create: { name: "admin" },
});
}
main().finally(() => prisma.$disconnect());
migrate reset / db push freely. Speed matters more than safety.reset, push, or clean. Only migrate deploy / migrate up. Always back up before migrating.Use environment variables to control connection strings. Never hardcode credentials in migration files.
# GitHub Actions example
- name: Run migrations
run: npx prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
# Validate migrations in PR checks
- name: Check migration status
run: |
npx prisma migrate status
npx prisma migrate diff --from-migrations ./prisma/migrations --to-schema-datamodel ./prisma/schema.prisma --exit-code
CI pipeline checklist:
migrations/
├── 001_create_users.up.sql
├── 001_create_users.down.sql
├── 002_create_posts.up.sql
├── 002_create_posts.down.sql
└── 003_add_email_index.up.sql
-- Example up migration
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(255),
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Example down migration
DROP TABLE IF EXISTS users;
1. One change per migration -- easier to review, rollback, and debug
2. Always write down migrations for reversibility
3. Never edit an applied migration -- create a new one
4. Test migrations against production-like data
5. Wrap multi-statement migrations in transactions (except CONCURRENTLY)
6. Separate schema migrations from data migrations
7. Backfill data in a separate migration from the schema change
8. Run migrations in CI/CD, not at application boot
9. Back up the database before running production migrations
tools
Parallel execution with xargs, GNU parallel, and batch processing patterns. Use when user mentions "xargs", "parallel", "batch processing", "run in parallel", "parallel execution", "process list of files", "bulk operations", "concurrent commands", "map over files", or running commands on multiple inputs.
development
WebSocket implementation for real-time bidirectional communication. Use when user mentions "websocket", "ws://", "wss://", "real-time", "live updates", "chat application", "socket.io", "Server-Sent Events", "SSE", "push notifications", "live data", "streaming data", "bidirectional communication", "websocket server", "reconnection", or building real-time features.
tools
Frontend bundler configuration for Webpack and Vite. Use when user mentions "webpack", "vite", "bundler", "vite config", "webpack config", "code splitting", "tree shaking", "hot module replacement", "HMR", "build optimization", "bundle size", "chunk splitting", "loader", "plugin", "esbuild", "rollup", "dev server", or configuring JavaScript build tools.
tools
VS Code configuration, extensions, keybindings, and workspace optimization. Use when user mentions "vscode", "vs code", "vscode settings", "vscode extensions", "keybindings", "code editor", "workspace settings", "settings.json", "launch.json", "tasks.json", "vscode snippets", "devcontainer", "remote development", or customizing their VS Code setup.