Skip to content

fix: always catch db mismatch #738

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions tagstudio/resources/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@
"status.library_closing": "Closing Library...",
"status.library_save_success": "Library Saved and Closed!",
"status.library_search_query": "Searching Library...",
"status.library_version_expected": "Expected:",
"status.library_version_found": "Found:",
"status.library_version_mismatch": "Library Version Mismatch!",
"status.results_found": "{count} Results Found ({time_span})",
"status.results": "Results",
"tag_manager.title": "Library Tags",
Expand Down
22 changes: 14 additions & 8 deletions tagstudio/src/core/library/alchemy/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import structlog
from sqlalchemy import Dialect, Engine, String, TypeDecorator, create_engine, text
from sqlalchemy.exc import OperationalError
from sqlalchemy.orm import DeclarativeBase
from src.core.constants import RESERVED_TAG_END

Expand Down Expand Up @@ -36,7 +37,7 @@ def make_engine(connection_string: str) -> Engine:


def make_tables(engine: Engine) -> None:
logger.info("creating db tables")
logger.info("[Library] Creating DB tables...")
Base.metadata.create_all(engine)

# tag IDs < 1000 are reserved
Expand All @@ -47,14 +48,19 @@ def make_tables(engine: Engine) -> None:
result = conn.execute(text("SELECT SEQ FROM sqlite_sequence WHERE name='tags'"))
autoincrement_val = result.scalar()
if not autoincrement_val or autoincrement_val <= RESERVED_TAG_END:
conn.execute(
text(
"INSERT INTO tags (id, name, color_namespace, color_slug, is_category) VALUES "
f"({RESERVED_TAG_END}, 'temp', NULL, NULL, false)"
try:
conn.execute(
text(
"INSERT INTO tags "
"(id, name, color_namespace, color_slug, is_category) VALUES "
f"({RESERVED_TAG_END}, 'temp', NULL, NULL, false)"
)
)
)
conn.execute(text(f"DELETE FROM tags WHERE id = {RESERVED_TAG_END}"))
conn.commit()
conn.execute(text(f"DELETE FROM tags WHERE id = {RESERVED_TAG_END}"))
conn.commit()
except OperationalError as e:
logger.error("Could not initialize built-in tags", error=e)
conn.rollback()


def drop_tables(engine: Engine) -> None:
Expand Down
61 changes: 27 additions & 34 deletions tagstudio/src/core/library/alchemy/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
selectinload,
)
from src.core.library.json.library import Library as JsonLibrary # type: ignore
from src.qt.translations import Translations

from ...constants import (
BACKUP_FOLDER_NAME,
Expand Down Expand Up @@ -295,10 +296,35 @@ def open_sqlite_library(self, library_dir: Path, is_new: bool) -> LibraryStatus:
poolclass = None if self.storage_path == ":memory:" else NullPool

logger.info(
"Opening SQLite Library", library_dir=library_dir, connection_string=connection_string
"[Library] Opening SQLite Library",
library_dir=library_dir,
connection_string=connection_string,
)
self.engine = create_engine(connection_string, poolclass=poolclass)
with Session(self.engine) as session:
# dont check db version when creating new library
if not is_new:
db_version = session.scalar(
select(Preferences).where(Preferences.key == LibraryPrefs.DB_VERSION.name)
)

if not db_version or db_version.value != LibraryPrefs.DB_VERSION.default:
mismatch_text = Translations.translate_formatted(
"status.library_version_mismatch"
)
found_text = Translations.translate_formatted("status.library_version_found")
expected_text = Translations.translate_formatted(
"status.library_version_expected"
)
return LibraryStatus(
success=False,
message=(
f"{mismatch_text}\n"
f"{found_text} v{0 if not db_version else db_version.value}, "
f"{expected_text} v{LibraryPrefs.DB_VERSION.default}"
),
)

make_tables(self.engine)

# TODO: Determine a good way of updating built-in data after updates.
Expand Down Expand Up @@ -337,21 +363,6 @@ def open_sqlite_library(self, library_dir: Path, is_new: bool) -> LibraryStatus:
except IntegrityError:
session.rollback()

# dont check db version when creating new library
if not is_new:
db_version = session.scalar(
select(Preferences).where(Preferences.key == LibraryPrefs.DB_VERSION.name)
)

if not db_version:
return LibraryStatus(
success=False,
message=(
"Library version mismatch.\n"
f"Found: v0, expected: v{LibraryPrefs.DB_VERSION.default}"
),
)

for pref in LibraryPrefs:
with catch_warnings(record=True):
try:
Expand All @@ -377,24 +388,6 @@ def open_sqlite_library(self, library_dir: Path, is_new: bool) -> LibraryStatus:
logger.debug("ValueType already exists", field=field)
session.rollback()

db_version = session.scalar(
select(Preferences).where(Preferences.key == LibraryPrefs.DB_VERSION.name)
)
# if the db version is different, we cant proceed
if db_version.value != LibraryPrefs.DB_VERSION.default:
logger.error(
"DB version mismatch",
db_version=db_version.value,
expected=LibraryPrefs.DB_VERSION.default,
)
return LibraryStatus(
success=False,
message=(
"Library version mismatch.\n"
f"Found: v{db_version.value}, expected: v{LibraryPrefs.DB_VERSION.default}"
),
)

# check if folder matching current path exists already
self.folder = session.scalar(select(Folder).where(Folder.path == library_dir))
if not self.folder:
Expand Down
Loading