Overview
TrailBase uses a migration system based on SQL files to manage database schema changes. Migrations are automatically discovered and applied on server startup, ensuring your database schema stays in sync with your application.Migration System
Migration Types
TrailBase supports two types of migrations, indicated by filename prefix:Versioned migrations that run exactly once. Cannot be modified after being applied.Format:
V<version>__<name>.sqlUndo-safe migrations that use timestamps as versions. Can be created dynamically.Format:
U<timestamp>__<name>.sqlTrailBase’s CLI generates
U (undo-safe) migrations by default using Unix timestamps, which ensures unique ordering and prevents version conflicts.Migration Locations
Migrations are discovered from multiple locations:- Embedded migrations - Built into the TrailBase binary
- User migrations - Located in
<data-dir>/migrations/<database>/
Place database-specific migrations in
migrations/<database>/ subdirectories. The legacy approach of placing files directly in migrations/ still works but is deprecated.Creating Migrations
Create a New Migration
Use thetrail migration command to create a new migration file:
Optional descriptive suffix for the migration filename. Defaults to “update” if not provided.
Target database name. Defaults to “main” if not provided.
Migration File Format
Generated migration files contain a comment header:Applying Migrations
Migrations are automatically discovered and applied when TrailBase starts:Migration Order
Migrations are applied in this order:- Embedded base migrations - Core system tables
- Embedded main migrations - Built-in functionality
- User migrations - Your custom migrations from
migrations/<db>/ - Legacy migrations - Direct SQL files in
migrations/(deprecated)
Migration State
TrailBase tracks applied migrations in the_schema_history table:
Migration Workflows
Adding a New Table
Step-by-step: Add a new table
Step-by-step: Add a new table
Modifying Existing Schema
Step-by-step: Add a column
Step-by-step: Add a column
SQLite has limited ALTER TABLE support. To add a column:
Step-by-step: Complex schema changes
Step-by-step: Complex schema changes
For complex changes, use the recreate pattern:
Working with Multiple Databases
Step-by-step: Set up a separate analytics database
Step-by-step: Set up a separate analytics database
Data Migrations
Step-by-step: Migrate existing data
Step-by-step: Migrate existing data
Migration Best Practices
Naming Conventions
Use descriptive suffixes that explain what the migration does:Writing Safe Migrations
DO:
- Test migrations on a copy of production data
- Use transactions (implicit in SQLite)
- Add indexes for foreign key columns
- Include
IF NOT EXISTSfor idempotency when possible - Use
COALESCE()for default values when migrating data
Idempotent Migrations
When possible, make migrations safe to run multiple times:Backward Compatibility
When adding constraints, consider existing data:Troubleshooting
Migration Failed to Apply
Symptoms: Server fails to start with migration error- Fix the SQL syntax error in the migration file
- Restart the server
Divergent Migrations
Symptoms: Warning about migrations with same version but different names- Multiple developers create migrations with the same version
- Migration files are renamed after being applied
- Ensuring all migration files are in version control
- Using
Uprefix migrations (timestamp-based) to avoid conflicts
Migration Skipped
Symptoms: Migration exists but isn’t applied Causes:- Invalid filename format - Must match
[U|V]<version>__<name>.sql - Lower version than applied - Check
_schema_historyfor highest version - File not in correct directory - Place in
migrations/<db>/
Can’t Modify Applied Migration
Problem: You found a bug in an applied migration Solution: Create a new migration to fix it:Database Locked
Symptoms: Migration fails with “database is locked” Causes:- Another process has the database open
- Previous migration didn’t complete cleanly
- Stop all TrailBase processes
- Check for stale lock files:
<data-dir>/data/*.db-wal - Restart TrailBase
Migration Internals
Schema History Table
TrailBase tracks migrations in_schema_history:
Migration Runner
The migration runner:- Discovers migrations from all sources
- Sorts by version (numeric or timestamp)
- Checks
_schema_historyfor applied migrations - Applies pending migrations in transaction
- Records each migration in
_schema_history
Timestamp Generation
Timestamps are Unix timestamps (seconds since epoch):Advanced Topics
Embedded Migrations
TrailBase includes embedded migrations for system tables:- Base migrations - Core tables (
_user,_session) - Main migrations - Additional functionality
- Logs migrations - Logging tables
Multi-Database Migrations
Create separate migration directories for each database:config.textproto:
Testing Migrations
Test migrations on a copy of production data:Rollback Strategies
SQLite doesn’t support rollback migrations directly. Strategies:-
Backup before migration:
-
Create compensating migration:
-
Restore from backup: