Music Assistant Music Controller Database Migrations: Schema Evolution and Version Management
Music Assistant automatically migrates its SQLite music library database on startup using a versioned system defined in migrations.py, incrementally upgrading from the stored schema version to the current DB_SCHEMA_VERSION (43) through idempotent ALTER TABLE operations and data transformations.
The Music Assistant server (music-assistant/server) maintains your media library in a local SQLite database that evolves with each release. Understanding how music controller database migrations work is essential for developers contributing schema changes or administrators troubleshooting upgrade failures, as the system handles upgrades automatically without requiring manual SQL execution.
How Database Migrations Are Triggered
Version Tracking and Constants
The migration system centers on a single integer constant defined in music_assistant/controllers/music/constants.py. The constant DB_SCHEMA_VERSION represents the current schema revision and is set to 43 in the latest codebase. This version is stored persistently in the settings table of the SQLite database, allowing the application to detect when an upgrade is required.
The Entry Point
When the Music Assistant core starts, the MusicDatabaseSetupMixin._setup_database method in music_assistant/controllers/music/database.py orchestrates the initialization process. It reads the stored version from the database and compares it against DB_SCHEMA_VERSION. If the stored version differs from the current constant, the mixin invokes the migrate_database coroutine from music_assistant/controllers/music/migrations.py, passing the previous version number and a callback to recreate tables if necessary.
# From MusicDatabaseSetupMixin._setup_database()
await self.__create_database_tables()
if prev_version not in (0, DB_SCHEMA_VERSION):
await migrate_database(
self.mass,
self.database,
self.logger,
prev_version,
self.__create_database_tables,
)
await self._database.insert_or_replace(
DB_TABLE_SETTINGS,
{"key": "version", "value": str(DB_SCHEMA_VERSION), "type": "str"},
)
Migration Workflow and Schema Changes
The migrate_database coroutine in music_assistant/controllers/music/migrations.py executes a series of conditional blocks that follow the pattern if prev_version <= X:, allowing the system to apply changes incrementally from any prior version.
Key Schema Evolution Steps
The migration logic handles complex data transformations across 43 iterations, including:
- Version 15 – Adds
search_nameandsearch_sort_namecolumns to all primary media tables (tracks, albums, artists, radios, playlists, audiobooks, podcasts) and populates them usingcreate_safe_stringfor case- and diacritic-insensitive searching. - Version 28 – Executes the genre migration, creating the
genreandgenre_aliastables, seeding default genres, extracting unique genre names from every media table, and populating the many-to-manygenre_media_item_mappingtable using a CTE-based bulk insert. - Version 32 – Recreates
genre_media_item_mappingwith nullablealiasand a newis_derivedflag. - Version 36 – Permanently drops the legacy
smart_fades_analysistable. - Version 38 – Migrates loudness data into the unified
audio_analysistable and re-applies the smart-fades table drop for users upgrading from stable 2.8.9 to 2.9.0. - Version 42 – Adds
translation_keyandtranslation_paramscolumns to theplayliststable to preserve localized playlist names across sync cycles.
After processing all applicable migration blocks, the function commits the transaction and clears the global cache using await mass.cache.clear() to prevent stale lookups against the old schema.
Idempotent ALTER TABLE Operations
Each migration step is designed to be idempotent. The code catches SQLite exceptions such as "duplicate column" or "no such column" errors, allowing the migration to be safely re-run if interrupted. For example, adding a new column follows this defensive pattern:
if prev_version <= 44:
await database.execute(
f"ALTER TABLE {DB_TABLE_TRACKS} ADD COLUMN sample_rate INTEGER NOT NULL DEFAULT 44100"
)
Table Creation and Index Management
Core Table Definitions
The __create_database_tables method in database.py defines every table schema, including columns introduced by recent migrations. This method is invoked both for fresh installations and during migrations that require table recreation (such as version 21, which drops the smart_fades_analysis table and rebuilds core tables).
Indexes and Triggers
Two additional private methods maintain database performance and integrity:
__create_database_indexes– Builds indices on frequently queried columns includingfavorite,name,search_name,search_sort_name,external_ids, timestamps, and play counts, as well as foreign-key tables likeprovider_mappings.__create_database_triggers– Installs SQLite triggers that automatically update thetimestamp_modifiedcolumn on UPDATE operations for core media tables. These are recreated after migrations that may have removed them (such as version 17).
Robustness and Error Handling
The migration system implements multiple safeguards to prevent data loss:
- Automatic Backup – Before any migration begins, the existing database file is copied to
library.db.backup. If any step throws an exception, the system logs the error, deletes the corrupted database, restores from the backup, and falls back to a fresh database with a full library rescan. - Cache Invalidation – Upon successful completion, the global cache is cleared to ensure the application uses the migrated schema immediately.
- Transaction Safety – All schema changes are committed atomically after the migration logic completes, preventing partial schema updates.
Extending the Migration System
To add a new schema change to the Music Assistant codebase:
- Increment
DB_SCHEMA_VERSIONinmusic_assistant/controllers/music/constants.py. - Add a new conditional block
if prev_version <= <new_version>:in themigrate_databasecoroutine inmigrations.py. - Perform the required
ALTER TABLE,CREATE TABLE, or data-migration steps using thedatabase.execute()method. - Update
__create_database_tablesindatabase.pyif the new columns need to be present on fresh installs.
Because the migration logic is isolated in migrations.py, developers can safely test new steps by manually setting prev_version to a lower number in a development database.
Debugging and Manual Execution
For testing or debugging purposes, you can invoke the migration manually:
from music_assistant import MusicAssistant
from music_assistant.helpers.database import DatabaseConnection
import logging
async def run_migration():
mass = MusicAssistant()
db = DatabaseConnection("/tmp/library.db")
await db.setup()
logger = logging.getLogger("migration")
await migrate_database(
mass,
db,
logger,
prev_version=30, # Start from older version
create_tables=lambda: None, # Stub for table recreation
)
To inspect the current schema version programmatically:
async def get_schema_version():
db = DatabaseConnection("/tmp/library.db")
await db.setup()
row = await db.get_row("settings", {"key": "version"})
print("Current DB schema version:", row["value"] if row else "unknown")
Summary
- Music Assistant uses an incremental migration system in
music_assistant/controllers/music/migrations.pythat automatically upgrades the SQLite database from the stored version toDB_SCHEMA_VERSION(43) on startup. - The
MusicDatabaseSetupMixin._setup_databasemethod indatabase.pydetects version mismatches and triggers the migration process, while__create_database_tableshandles fresh installations. - Idempotent conditional blocks ensure migrations can be re-run safely, with specific steps handling complex transformations like the version 28 genre migration and version 38 loudness data consolidation.
- Robustness features include automatic backup to
library.db.backup, transaction-based commits, and automatic cache invalidation viamass.cache.clear().
Frequently Asked Questions
What happens if a database migration fails during startup?
If any migration step throws an exception, Music Assistant logs the error, deletes the corrupted database, restores the backup from library.db.backup, and initiates a full library rescan. This ensures the application remains usable even if a migration encounters an unexpected edge case.
How can I check my current database schema version?
Query the settings table for the key "version" using the database helper. The version is stored as a string representation of the integer schema version (e.g., "43"). You can inspect this via the Python API or by executing SELECT value FROM settings WHERE key='version' directly on the SQLite file.
Can I run migrations manually without starting the full server?
Yes, you can import the migrate_database coroutine from music_assistant/controllers/music/migrations.py and invoke it with a DatabaseConnection, a MusicAssistant instance, and the target prev_version. This is useful for development and testing new schema changes.
Where is the database backup stored before migration?
Before attempting any migration, Music Assistant copies the existing library.db file to library.db.backup in the same directory. This backup is used for automatic rollback if the migration fails, but developers can also manually restore it to revert to a pre-migration state.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →