Migrations are numbered sequentially starting from 0000. Each file contains:
SQL statements to apply schema changes
Rollback instructions (if reversible)
Comments explaining the purpose
Example migration structure:
Copy
Ask AI
-- Migration: 0004_quiz_answers-- Purpose: Add individual quiz answer tracking for analytics-- Date: 2025-01-15CREATE TABLE quiz_answers ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), learner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, lesson_id TEXT NOT NULL REFERENCES lessons(id) ON DELETE CASCADE, question_index INTEGER NOT NULL, question_text TEXT NOT NULL, question_hash VARCHAR(64) NOT NULL, user_answer INTEGER NOT NULL, correct_answer INTEGER NOT NULL, is_correct BOOLEAN NOT NULL, concept_tags TEXT[], answered_at TIMESTAMP DEFAULT NOW());CREATE INDEX idx_learner_answers ON quiz_answers(learner_id, answered_at);CREATE INDEX idx_question_hash ON quiz_answers(question_hash);
# Populate database with test data (development only)npm run db:seed# Creates:# - Sample users (admin, parent, learners)# - Test lessons# - Mock achievements
CREATE TABLE learning_goals ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), learner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, title TEXT NOT NULL, target_type TEXT NOT NULL, target_value INTEGER NOT NULL, current_value INTEGER DEFAULT 0, is_completed BOOLEAN DEFAULT false, created_at TIMESTAMP DEFAULT NOW());
-- 0012_add_grade_level_column.sqlALTER TABLE learner_profiles ADD COLUMN grade_level INTEGER;-- 0013_populate_grade_levels.sqlUPDATE learner_profiles SET grade_level = 5WHERE grade_level IS NULL;ALTER TABLE learner_profiles ALTER COLUMN grade_level SET NOT NULL;
-- Simple indexCREATE INDEX idx_lessons_learner ON lessons(learner_id);-- Composite indexCREATE INDEX idx_quiz_answers_learner_dateON quiz_answers(learner_id, answered_at DESC);-- Partial indexCREATE UNIQUE INDEX idx_one_active_per_learnerON lessons(learner_id) WHERE status = 'ACTIVE';-- Concurrent index (non-blocking)CREATE INDEX CONCURRENTLY idx_lessons_subject ON lessons(subject);
-- Change type (safe if compatible)ALTER TABLE lessons ALTER COLUMN score TYPE BIGINT;-- Add constraintALTER TABLE lessons ADD CONSTRAINT score_range CHECK (score >= 0 AND score <= 100);-- Make nullableALTER TABLE users ALTER COLUMN email DROP NOT NULL;-- Change defaultALTER TABLE users ALTER COLUMN role SET DEFAULT 'LEARNER';
-- Rename columnALTER TABLE users RENAME COLUMN name TO display_name;-- Rename tableALTER TABLE old_table RENAME TO new_table;-- Rename indexALTER INDEX old_idx RENAME TO new_idx;
Cause: Large table, slow operation (e.g., adding index).Fix:
Copy
Ask AI
-- Use CONCURRENTLY for indexesCREATE INDEX CONCURRENTLY idx_name ON table(column);-- Break into smaller batches for data migrationsUPDATE table SET new_col = old_col WHERE id < 1000;UPDATE table SET new_col = old_col WHERE id >= 1000 AND id < 2000;
Constraint violation
Cause: Existing data doesn’t meet new constraint.Fix:
Copy
Ask AI
-- Check violating rowsSELECT * FROM lessons WHERE score < 0 OR score > 100;-- Fix dataUPDATE lessons SET score = 0 WHERE score < 0;UPDATE lessons SET score = 100 WHERE score > 100;-- Re-run migrationnpm run migrate
Migration already applied
Cause: Migration ran but wasn’t recorded in tracking table.Fix:
Copy
Ask AI
-- Check table exists\dt+ achievements-- Manually mark as appliedINSERT INTO drizzle_migrations (hash, created_at) VALUES ('0014_achievements', NOW());
Schema mismatch
Cause: Database schema doesn’t match shared/schema.ts.Fix:
Copy
Ask AI
# Option 1: Push current schema (development only)npm run db:push# Option 2: Generate and apply migrationnpx drizzle-kit generate:pgnpm run migrate
# Create test databasecreatdb sunschool_test# Run migrations against test DBDATABASE_URL="postgresql://localhost/sunschool_test" npm run migrate# Verify schemapsql sunschool_test -c "\dt+"# Test rollbackpsql sunschool_test < drizzle/migrations/0014_rollback.sql
Always test migrations in staging before production.
Copy
Ask AI
# Railway staging environmentrailway environment stagingrailway run npm run migrate# Verifyrailway run psql $DATABASE_URL -c "SELECT * FROM drizzle_migrations;"