From 045f83a2c29b50b6e845e4f3349c6e7b687fe8f0 Mon Sep 17 00:00:00 2001
From: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Date: Sun, 27 Sep 2026 23:58:55 -0700
Subject: [PATCH] fix: add 'IF NOT EXISTS' to migration 200 template tables
---
.../core/library/alchemy/migrations.py | 6 ++--
tests/core/library/test_migrations.py | 34 ++++++++++++++++++
.../issue-1500/.TagStudio/ts_library.sqlite | Bin 0 -> 122880 bytes
3 files changed, 38 insertions(+), 2 deletions(-)
create mode 100644 tests/fixtures/issue-1500/.TagStudio/ts_library.sqlite
diff --git a/src/tagstudio/core/library/alchemy/migrations.py b/src/tagstudio/core/library/alchemy/migrations.py
index 69561a38e..54cdf6c1a 100644
--- a/src/tagstudio/core/library/alchemy/migrations.py
+++ b/src/tagstudio/core/library/alchemy/migrations.py
@@ -445,15 +445,17 @@ def run(cls, conn: Connection, library_dir: Path, fmt_log: LoggingMethod):
""")
# Add field templates tables
+ # NOTE: v9.6.0-9.6.2 created these empty tables before migrating, so backup library files
+ # or libraries that failed mid-migration may already have them.
conn.execute("""
- CREATE TABLE text_field_templates (
+ CREATE TABLE IF NOT EXISTS text_field_templates (
id INTEGER NOT NULL PRIMARY KEY,
is_multiline BOOLEAN NOT NULL,
name VARCHAR NOT NULL
)
""")
conn.execute("""
- CREATE TABLE datetime_field_templates (
+ CREATE TABLE IF NOT EXISTS datetime_field_templates (
id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR NOT NULL
)
diff --git a/tests/core/library/test_migrations.py b/tests/core/library/test_migrations.py
index 4f6c77b9e..256806842 100644
--- a/tests/core/library/test_migrations.py
+++ b/tests/core/library/test_migrations.py
@@ -56,3 +56,37 @@ def test_library_migrations(path: str):
raise (e)
finally:
shutil.rmtree(temp_path)
+
+
+def test_migration_with_existing_field_template_tables(tmp_path: Path):
+ """DB_VERSION 104 library that already has the (empty) field template tables.
+
+ v9.6.0-9.6.2 created these empty tables before migrating, so backup library files
+ or libraries that failed mid-migration may already have them.
+
+ This tests for a specific scenario where a backup library file from a
+ v9.5.6 (DB102) -> v9.6.2 (DB300) migration is opened in a newer version.
+ Historically this would cause breakage in v9.6.3 (see issue #1500).
+ """
+ fixture = CWD.parents[2] / FIXTURES / "issue-1500" / TS_FOLDER_NAME / SQL_FILENAME
+ (tmp_path / TS_FOLDER_NAME).mkdir()
+ shutil.copy(fixture, tmp_path / TS_FOLDER_NAME / SQL_FILENAME)
+
+ library = Library()
+ try:
+ assert library.open_library(library_dir=tmp_path).success
+ expected = ["Title", "Author", "Artist", "URL", "Description", "Notes", "Comments", "Date"]
+ assert [t.name for t in library.field_templates] == expected
+
+ assert library.entries_count == 2
+ entry = library.get_entry_full(entry_id=2)
+ assert entry
+ assert {f.name: f.value for f in entry.text_fields} == {
+ "Title": "Mario",
+ "Description": "This is a cat.",
+ }
+ assert {f.name: f.value for f in entry.datetime_fields} == {
+ "Date": None,
+ }
+ finally:
+ library.close()
diff --git a/tests/fixtures/issue-1500/.TagStudio/ts_library.sqlite b/tests/fixtures/issue-1500/.TagStudio/ts_library.sqlite
new file mode 100644
index 0000000000000000000000000000000000000000..74c1a3396438e25432b2c05af70420362f39c196
GIT binary patch
literal 122880
zcmeI54Qw0NeZcQ{Cmz2~woKEqOzR{@u^5RKKg18k^)iY&Doi9%p{O+O(h(=}Bypxl
zg&&TUVe8~L1&U$qkagH63baMHqFaXzSOauJk)rFmb{mGG9oiHFI&4F;rC5ugA83(w
z>)yNXqmGKvdU0a^Y012M|NsBJ|NFhK|GlGl8;`BjyCS#UYVQeME
oKRhO9O?21|Fo95re0HuYY&FELuu~D6
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
literal 0
HcmV?d00001