mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-08-30 07:41:30 +02:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 27bd1622b7 | |||
| e2b54ef3ff | |||
| 70f1a3d975 |
@@ -504,10 +504,9 @@ class Library:
|
||||
) -> LibraryStatus:
|
||||
logger.info("[Library] Opening SQLite Library", library_dir=library_dir)
|
||||
|
||||
self.engine = self.__get_engine(library_dir, in_memory, sql_filename)
|
||||
|
||||
# migrate if necessary
|
||||
try:
|
||||
migrations = DBMigrations(library_dir, self.engine)
|
||||
migrations = DBMigrations(library_dir, sql_filename)
|
||||
|
||||
# save backup if patches will be applied
|
||||
if migrations.required:
|
||||
@@ -517,7 +516,8 @@ class Library:
|
||||
except MigrationError as e:
|
||||
return LibraryStatus(success=False, message=e.args[0])
|
||||
|
||||
# everything is fine, set the library path
|
||||
# open up-to-date library
|
||||
self.engine = self.__get_engine(library_dir, in_memory, sql_filename)
|
||||
self.library_dir = library_dir
|
||||
return LibraryStatus(success=True, library_path=library_dir)
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
import sqlite3
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import override
|
||||
|
||||
import sqlalchemy
|
||||
import structlog
|
||||
import ujson
|
||||
from sqlalchemy import Engine, and_, delete, select, text, update
|
||||
from sqlalchemy import and_, delete, select, text, update
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from tagstudio.core.constants import IGNORE_NAME, TAG_ARCHIVED, TS_FOLDER_NAME
|
||||
@@ -23,6 +23,7 @@ from tagstudio.core.library.alchemy.constants import (
|
||||
from tagstudio.core.library.alchemy.fields import LEGACY_FIELD_MAP, DatetimeField, TextField
|
||||
from tagstudio.core.library.alchemy.joins import TagParent
|
||||
from tagstudio.core.library.alchemy.models import Entry, Tag, TagColorGroup, Version
|
||||
from tagstudio.core.library.alchemy.utils import list_tables
|
||||
from tagstudio.core.library.ignore import migrate_ext_list
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.i18n.translations import Translations
|
||||
@@ -46,9 +47,11 @@ class DBMigration:
|
||||
|
||||
|
||||
class DBMigrations:
|
||||
def __init__(self, library_dir: Path, engine: Engine) -> None:
|
||||
def __init__(self, library_dir: Path, sql_filename: str) -> None:
|
||||
self.library_dir = library_dir
|
||||
self.engine = engine
|
||||
self._connection = sqlite3.connect(
|
||||
str(library_dir / TS_FOLDER_NAME / sql_filename), autocommit=False
|
||||
)
|
||||
|
||||
# Don't check DB version when creating new library
|
||||
self.loaded_db_version = self._get_version(DB_VERSION_CURRENT_KEY)
|
||||
@@ -102,70 +105,63 @@ class DBMigrations:
|
||||
MigrationTo300, # changes: deletes folders
|
||||
MigrationTo400, # changes: add category_exclusions
|
||||
]
|
||||
with Session(self.engine) as session:
|
||||
for migration in migrations:
|
||||
if self.loaded_db_version < migration.version and (
|
||||
migration.initial_version is None
|
||||
or self.initial_db_version < migration.initial_version
|
||||
):
|
||||
logger.info(f"[Library][Migration][{migration.version}] Starting DB Migration")
|
||||
# any error causes transaction to rollback
|
||||
migration.run(
|
||||
session,
|
||||
self.library_dir,
|
||||
lambda msg, v=migration.version: f"[Library][Migration][{v}] {msg}",
|
||||
for migration in migrations:
|
||||
if self.loaded_db_version < migration.version and (
|
||||
migration.initial_version is None
|
||||
or self.initial_db_version < migration.initial_version
|
||||
):
|
||||
logger.info(f"[Library][Migration][{migration.version}] Starting DB Migration")
|
||||
# any error causes transaction to rollback
|
||||
migration.run(
|
||||
None, # TODO: remove session param once all Migrations have been updated
|
||||
self.library_dir,
|
||||
lambda msg, v=migration.version: f"[Library][Migration][{v}] {msg}",
|
||||
)
|
||||
self.loaded_db_version = migration.version
|
||||
try:
|
||||
self._set_version(DB_VERSION_CURRENT_KEY, migration.version)
|
||||
logger.info(f"[Library][Migration][{migration.version}] Completed DB Migration")
|
||||
except Exception as e:
|
||||
logger.info(
|
||||
f"[Library][Migration][{migration.version}] "
|
||||
"Couldn't update version, continuing without commit",
|
||||
error=e,
|
||||
)
|
||||
self.loaded_db_version = migration.version
|
||||
try:
|
||||
self._set_version(session, DB_VERSION_CURRENT_KEY, migration.version)
|
||||
logger.info(
|
||||
f"[Library][Migration][{migration.version}] Completed DB Migration"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.info(
|
||||
f"[Library][Migration][{migration.version}] "
|
||||
"Couldn't update version, continuing without commit",
|
||||
error=e,
|
||||
)
|
||||
session.flush()
|
||||
else:
|
||||
session.commit()
|
||||
else:
|
||||
self._connection.commit()
|
||||
|
||||
assert self.loaded_db_version >= DB_VERSION, (
|
||||
"Ran all migrations, but the DB is still not on the newest version"
|
||||
)
|
||||
|
||||
def _get_version(self, key: str) -> int:
|
||||
with Session(self.engine) as session:
|
||||
inspector = sqlalchemy.inspect(self.engine)
|
||||
try:
|
||||
# "Version" table added in DB_VERSION 101
|
||||
if inspector and inspector.has_table("versions"):
|
||||
version = session.scalar(select(Version).where(Version.key == key))
|
||||
assert version
|
||||
return version.value
|
||||
# "Preferences" table deprecated in TagStudio 9.5.4
|
||||
else:
|
||||
return int(
|
||||
unwrap(
|
||||
session.scalar(
|
||||
text("SELECT value FROM preferences WHERE key == 'DB_VERSION'")
|
||||
)
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
return 0
|
||||
"""Get a version value from the DB.
|
||||
|
||||
def _set_version(self, session: Session, key: str, value: int) -> None:
|
||||
Args:
|
||||
key(str): The name of the version type to retrieve.
|
||||
"""
|
||||
# "Version" table added in DB_VERSION 101
|
||||
if "versions" in list_tables(self._connection):
|
||||
query = ("SELECT value FROM versions WHERE key == ?", [key])
|
||||
# "Preferences" table deprecated in TagStudio 9.5.4
|
||||
else:
|
||||
query = ("SELECT value FROM preferences WHERE key == 'DB_VERSION'", [])
|
||||
|
||||
return int(unwrap(self._connection.execute(*query).fetchone())[0])
|
||||
|
||||
def _set_version(self, key: str, value: int) -> None:
|
||||
"""Set a version value to the DB.
|
||||
|
||||
Args:
|
||||
session(Session): The SQLAlchemy DB Session to use.
|
||||
key(str): The key for the name of the version type to set.
|
||||
key(str): The the name of the version type to set.
|
||||
value(int): The version value to set.
|
||||
"""
|
||||
# Insert if key has no value yet, otherwise update the value
|
||||
session.merge(Version(key=key, value=value))
|
||||
self._connection.execute(
|
||||
"INSERT INTO versions (key, value) VALUES (?, ?)"
|
||||
"ON CONFLICT(key) DO UPDATE SET value=excluded.value",
|
||||
[key, value],
|
||||
)
|
||||
|
||||
|
||||
class MigrationTo7(DBMigration):
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
from sqlite3 import Connection
|
||||
|
||||
|
||||
def list_tables(con: Connection) -> list[str]:
|
||||
return [
|
||||
row[0]
|
||||
for row in con.execute("SELECT name FROM sqlite_master WHERE type == 'table'").fetchall()
|
||||
]
|
||||
Reference in New Issue
Block a user