Implements Alembic for managing token storage database schema versions. Migrations run automatically on startup with full backward compatibility. **Changes:** - Add Alembic dependency (1.14.0+) and SQLAlchemy (auto-installed) - Create migration infrastructure in alembic/ directory - Add initial migration (001) capturing current schema - Modify RefreshTokenStorage.initialize() to run migrations via anyio - Add CLI commands: db upgrade, current, history, downgrade, migrate - Add comprehensive migration documentation **Backward Compatibility:** - Pre-Alembic databases automatically stamped with revision 001 - No schema changes for existing databases - Automatic upgrade on first startup after update **Migration Strategy:** Three scenarios handled: 1. New database → Run migrations from scratch 2. Pre-Alembic database → Stamp with 001 (no changes) 3. Alembic-managed → Upgrade to latest **Architecture:** - Uses anyio.to_thread.run_sync() for structured concurrency - Alembic env.py runs with anyio.run() in worker thread - SQLite-friendly migration patterns documented - No ThreadPoolExecutor needed (anyio handles it) **CLI Usage:** ```bash nextcloud-mcp-server db upgrade # Upgrade to latest nextcloud-mcp-server db current # Show version nextcloud-mcp-server db history # View changelog nextcloud-mcp-server db downgrade # Rollback (with confirmation) nextcloud-mcp-server db migrate "description" # Create migration ``` **Testing:** - All 13 webhook storage tests pass - New/pre-Alembic database scenarios validated - anyio integration tested 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
8.1 KiB
Database Migrations
This document describes the database migration system for nextcloud-mcp-server's token storage database.
Overview
The token storage database uses Alembic for schema versioning and migrations. Alembic provides:
- Version Control: Track schema changes in Git
- Rollback Support: Safely downgrade schema if needed
- Audit Trail: Migration files serve as schema changelog
- Automated Upgrades: Database schema updates automatically on startup
Architecture
Migration Strategy
The system handles three scenarios:
- New Database: Runs migrations from scratch to create all tables
- Pre-Alembic Database: Stamps existing database with initial revision (no changes)
- Alembic-Managed Database: Upgrades to latest version automatically
Directory Structure
nextcloud-mcp-server/
├── alembic/ # Alembic migrations
│ ├── versions/ # Migration scripts
│ │ └── 20251217_2200_001_initial_schema.py
│ ├── env.py # Alembic environment
│ ├── script.py.mako # Migration template
│ └── README # Migration usage guide
├── alembic.ini # Alembic configuration
└── nextcloud_mcp_server/
├── auth/storage.py # Uses migrations on init
└── migrations.py # Migration utilities
Usage
Automatic Migration on Startup
Migrations run automatically when the server starts:
uv run nextcloud-mcp-server
The RefreshTokenStorage.initialize() method:
- Checks if database is Alembic-managed
- Stamps pre-Alembic databases with initial revision
- Upgrades to latest version
Manual Migration Commands
# Show current database version
uv run nextcloud-mcp-server db current
# Upgrade database to latest version
uv run nextcloud-mcp-server db upgrade
# Show migration history
uv run nextcloud-mcp-server db history
# Downgrade by one version (emergency use only)
uv run nextcloud-mcp-server db downgrade
# Specify custom database path
uv run nextcloud-mcp-server db current -d /path/to/tokens.db
Environment Variables
TOKEN_STORAGE_DB: Path to database file (default:/app/data/tokens.db)
Creating Migrations (Developers)
Step 1: Create Migration File
uv run nextcloud-mcp-server db migrate "add user preferences table"
This creates a new migration file in alembic/versions/ with empty upgrade() and downgrade() functions.
Step 2: Write Migration SQL
Since we don't use SQLAlchemy models, write raw SQL:
def upgrade() -> None:
"""Add user preferences table."""
op.execute("""
CREATE TABLE user_preferences (
user_id TEXT PRIMARY KEY,
theme TEXT DEFAULT 'light',
language TEXT DEFAULT 'en',
created_at INTEGER NOT NULL
)
""")
op.execute("""
CREATE INDEX idx_user_preferences_user_id
ON user_preferences(user_id)
""")
def downgrade() -> None:
"""Remove user preferences table."""
op.execute("DROP INDEX IF EXISTS idx_user_preferences_user_id")
op.execute("DROP TABLE IF EXISTS user_preferences")
Step 3: Test Migration
# Test upgrade
uv run nextcloud-mcp-server db upgrade -d /tmp/test.db
# Verify schema
sqlite3 /tmp/test.db ".schema"
# Test downgrade
uv run nextcloud-mcp-server db downgrade -d /tmp/test.db
# Verify removal
sqlite3 /tmp/test.db ".schema"
Step 4: Commit Migration
git add alembic/versions/YYYYMMDD_HHMM_XXX_description.py
git commit -m "feat: add user preferences table migration"
SQLite Limitations
SQLite has limited ALTER TABLE support:
Supported Operations
- ✅ Add columns:
ALTER TABLE table ADD COLUMN ... - ✅ Rename table:
ALTER TABLE old RENAME TO new - ✅ Rename column:
ALTER TABLE table RENAME COLUMN old TO new(SQLite 3.25+)
Unsupported Operations (Requires Table Recreation)
- ❌ Drop column
- ❌ Change column type
- ❌ Add constraints to existing columns
Table Recreation Pattern
For complex schema changes:
def upgrade() -> None:
# Create new table with desired schema
op.execute("""
CREATE TABLE refresh_tokens_new (
user_id TEXT PRIMARY KEY,
encrypted_token BLOB NOT NULL,
new_field TEXT, -- New column
expires_at INTEGER,
created_at INTEGER NOT NULL
)
""")
# Copy data from old table
op.execute("""
INSERT INTO refresh_tokens_new
(user_id, encrypted_token, expires_at, created_at)
SELECT user_id, encrypted_token, expires_at, created_at
FROM refresh_tokens
""")
# Drop old table and rename new table
op.execute("DROP TABLE refresh_tokens")
op.execute("ALTER TABLE refresh_tokens_new RENAME TO refresh_tokens")
# Recreate indexes
op.execute("CREATE INDEX idx_user_id ON refresh_tokens(user_id)")
Best Practices
Naming Conventions
- Migrations:
YYYYMMDD_HHMM_XXX_description.py - Revision IDs: Sequential numbers (
001,002,003) - Descriptions: Imperative mood ("add table", "remove column")
Migration Guidelines
- Test Thoroughly: Test both upgrade and downgrade paths
- Preserve Data: Ensure data migration logic is correct
- Document Changes: Add comments explaining complex operations
- Small Changes: One logical change per migration
- No Breaking Changes: Maintain backward compatibility when possible
Downgrade Considerations
- Data Loss: Downgrade may lose data (dropped columns, tables)
- Confirmation: Downgrade command requires explicit confirmation
- Testing: Always test downgrade path before deploying
- Emergency Only: Use downgrades only for critical rollbacks
Backward Compatibility
Pre-Alembic Databases
Existing databases created before Alembic integration are automatically detected and stamped with revision 001:
- Server detects no
alembic_versiontable - Checks if
refresh_tokenstable exists - If yes, stamps database with
001(no schema changes) - Future updates use normal migration path
Migration Path
Pre-Alembic DB → Stamp(001) → Upgrade(002) → Upgrade(003) → ...
New DB → Migrate(001) → Upgrade(002) → Upgrade(003) → ...
Troubleshooting
Migration Fails
# Check current state
uv run nextcloud-mcp-server db current -d /path/to/tokens.db
# View migration history
uv run nextcloud-mcp-server db history -d /path/to/tokens.db
# Manually inspect database
sqlite3 /path/to/tokens.db ".schema"
Reset to Initial State
WARNING: This destroys all data!
# Downgrade to base (empty database)
uv run nextcloud-mcp-server db downgrade -d /path/to/tokens.db --revision base
# Upgrade to latest
uv run nextcloud-mcp-server db upgrade -d /path/to/tokens.db
Corrupted Migration State
If alembic_version table is corrupted:
# Manually fix via SQL
sqlite3 /path/to/tokens.db
> DELETE FROM alembic_version;
> INSERT INTO alembic_version (version_num) VALUES ('001');
> .quit
# Verify and upgrade
uv run nextcloud-mcp-server db current -d /path/to/tokens.db
uv run nextcloud-mcp-server db upgrade -d /path/to/tokens.db
CI/CD Integration
Pre-Deployment
# Run migrations in test environment
export TOKEN_STORAGE_DB=/app/data/tokens.db
uv run nextcloud-mcp-server db upgrade
# Verify current version
uv run nextcloud-mcp-server db current
Docker Deployment
Migrations run automatically on container startup via RefreshTokenStorage.initialize().
Rollback Plan
- Stop application
- Backup database:
cp tokens.db tokens.db.backup - Downgrade:
uv run nextcloud-mcp-server db downgrade --revision XXX - Deploy previous application version
- Restart application