From a1bed02a58b7a7bd7c72920b1584baf0d41e08e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sun, 8 Feb 2026 07:09:01 +0000 Subject: [PATCH] Add support for migrations --- beets/dbcore/db.py | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/beets/dbcore/db.py b/beets/dbcore/db.py index 8640a5678..d13f923f6 100755 --- a/beets/dbcore/db.py +++ b/beets/dbcore/db.py @@ -26,14 +26,7 @@ import threading import time from abc import ABC from collections import defaultdict -from collections.abc import ( - Callable, - Generator, - Iterable, - Iterator, - Mapping, - Sequence, -) +from collections.abc import Mapping from functools import cached_property from sqlite3 import Connection, sqlite_version_info from typing import TYPE_CHECKING, Any, AnyStr, ClassVar, Generic, NamedTuple @@ -1088,11 +1081,14 @@ class Database: self._db_lock = threading.Lock() # Set up database schema. + self._ensure_migration_state_table() for model_cls in self._models: self._make_table(model_cls._table, model_cls._fields) self._make_attribute_table(model_cls._flex_table) self._create_indices(model_cls._table, model_cls._indices) + self._migrate() + # Primitive access control: connections and transactions. def _connection(self) -> Connection: @@ -1292,6 +1288,38 @@ class Database: f"ON {table} ({', '.join(index.columns)});" ) + # Generic migration state handling. + + def _ensure_migration_state_table(self) -> None: + with self.transaction() as tx: + tx.script(""" + CREATE TABLE IF NOT EXISTS migration_state ( + name TEXT PRIMARY KEY, + done INTEGER NOT NULL DEFAULT 0 + ); + """) + + def _migrate(self) -> None: + """Perform any necessary migration for the database.""" + + def get_migration_state(self, name: str) -> bool: + """Return whether a named migration has been marked complete.""" + with self.transaction() as tx: + rows = tx.query( + "SELECT done FROM migration_state WHERE name = ?", + (name,), + ) + return bool(rows and rows[0]["done"]) + + def set_migration_state(self, name: str, done: bool) -> None: + """Set completion state for a named migration.""" + with self.transaction() as tx: + tx.mutate( + "INSERT INTO migration_state(name, done) VALUES (?, ?) " + "ON CONFLICT(name) DO UPDATE SET done = excluded.done", + (name, int(done)), + ) + # Querying. def _fetch(