SIMPLE_SQL_MIGRATION_RUNNER
Version-controlled database migrations using SQLite's user_version PRAGMA. Apply schema changes in order and track migration state.
How It Works
Migrations are numbered starting from 1. The current version is stored in SQLite's user_version PRAGMA. Only migrations newer than the current version are applied.
Creation
make (a_db: SIMPLE_SQL_DATABASE)
Create migration runner attached to database.
Adding Migrations
add_migration (a_version: INTEGER; a_sql: READABLE_STRING_8)
Add SQL migration at version number.
add_migration_with_name (a_version: INTEGER; a_name: READABLE_STRING_8; a_sql: READABLE_STRING_8)
Add named migration for better logging.
Running Migrations
migrate
Run all pending migrations in order.
migrate_to (a_version: INTEGER)
Run migrations up to specific version.
Version Queries
current_version: INTEGER
Get current schema version.
latest_version: INTEGER
Get highest registered migration version.
pending_count: INTEGER
Number of migrations not yet applied.
Examples
Define and Run Migrations
local
runner: SIMPLE_SQL_MIGRATION_RUNNER
do
create runner.make (db)
-- Version 1: Create users table
runner.add_migration_with_name (1, "create_users",
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE)")
-- Version 2: Add created_at column
runner.add_migration_with_name (2, "add_created_at",
"ALTER TABLE users ADD COLUMN created_at TEXT DEFAULT CURRENT_TIMESTAMP")
-- Version 3: Create sessions table
runner.add_migration_with_name (3, "create_sessions",
"CREATE TABLE sessions (id INTEGER PRIMARY KEY, user_id INTEGER REFERENCES users(id), token TEXT NOT NULL, expires_at TEXT)")
-- Run all pending migrations
print ("Current version: " + runner.current_version.out + "%N")
print ("Pending migrations: " + runner.pending_count.out + "%N")
runner.migrate
print ("Migrated to version: " + runner.current_version.out + "%N")
end
Multi-Statement Migration
-- Migrations can contain multiple SQL statements
runner.add_migration (4,
"CREATE INDEX idx_sessions_user ON sessions(user_id); CREATE INDEX idx_sessions_token ON sessions(token)")
Check Version Before Operations
local
runner: SIMPLE_SQL_MIGRATION_RUNNER
do
create runner.make (db)
-- Ensure database is at expected version
if runner.current_version < 3 then
print ("Database needs migration. Run migrate command first.%N")
else
-- Safe to proceed with operations that require v3 schema
run_application
end
end
Incremental Migration
-- Only migrate to version 2 (skip later migrations)
runner.migrate_to (2)
-- Check what's pending
print ("Still pending: " + runner.pending_count.out + "%N")
Migration Best Practices
- Never modify an existing migration after it's been deployed
- Each migration should be self-contained and idempotent where possible
- Use IF NOT EXISTS / IF EXISTS clauses for safety
- Back up database before running migrations in production
SQLite Limitations
SQLite has limited ALTER TABLE support. For complex changes (rename column, change type), create a new table and migrate data:
CREATE TABLE users_new (...);
INSERT INTO users_new SELECT ... FROM users;
DROP TABLE users;
ALTER TABLE users_new RENAME TO users;