Skip to content

Commit d513e32

Browse files
committed
fix: drop type_key column from text_fields and datetime_fields tables
1 parent 3846b65 commit d513e32

6 files changed

Lines changed: 79 additions & 14 deletions

File tree

docs/library-changes.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,9 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
148148

149149
#### Version 200
150150

151-
| Used From | Format | Location |
152-
| --------- | ------ | ----------------------------------------------- |
153-
| TBD | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
151+
| Used From | Format | Location |
152+
| ---------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
153+
| [c15e2b5](https://github.com/TagStudioDev/TagStudio/commit/c15e2b56eedd0a3c13391fa43571b8f8f7c7a91f) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
154154

155155
- Adds `text_field_templates` and `date_field_templates` tables.
156156
- Drops `boolean_fields` and `value_type` tables.
@@ -162,3 +162,12 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
162162
- Values are set to `TRUE` if the field row was previously a "TEXT_BOX" type.
163163
- Repairs existing "Description" fields inside the `text_fields` table to have their `is_multiline` column set to `TRUE` _(Previously done in [Version 7](#version-7))_.
164164
- Repairs existing "Comments" fields inside the `text_fields` table to have their `is_multiline` column set to `TRUE`.
165+
166+
#### Version 201
167+
168+
| Used From | Format | Location |
169+
| --------- | ------ | ----------------------------------------------- |
170+
| TBD | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
171+
172+
- Drops `type_key` columns from `text_fields` and `datetime_fields` tables.
173+
- Enforces column positions for `text_fields` and `datetime_fields` tables.

src/tagstudio/core/library/alchemy/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
DB_VERSION_CURRENT_KEY: str = "CURRENT"
1111
DB_VERSION_INITIAL_KEY: str = "INITIAL"
12-
DB_VERSION: int = 200
12+
DB_VERSION: int = 201
1313

1414
TAG_CHILDREN_QUERY = text("""
1515
WITH RECURSIVE ChildTags AS (

src/tagstudio/core/library/alchemy/fields.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ class BaseField(Base):
2020

2121
@declared_attr
2222
def id(self) -> Mapped[int]:
23-
return mapped_column(primary_key=True, autoincrement=True)
23+
return mapped_column(primary_key=True, autoincrement=True, sort_order=1)
2424

2525
@declared_attr
2626
def name(self) -> Mapped[str]:
27-
return mapped_column(nullable=False, default="")
27+
return mapped_column(nullable=False, default="", sort_order=2)
2828

2929
@declared_attr
3030
def entry_id(self) -> Mapped[int]:
31-
return mapped_column(ForeignKey("entries.id"))
31+
return mapped_column(ForeignKey("entries.id"), sort_order=3)
3232

3333
@declared_attr
3434
def entry(self) -> Mapped[Entry]:
@@ -47,7 +47,7 @@ def clone_with_entry_id(self, entry_id: int) -> BaseField: # pyright: ignore
4747
class TextField(BaseField):
4848
__tablename__ = "text_fields"
4949

50-
value: Mapped[str | None]
50+
value: Mapped[str | None] = mapped_column(sort_order=4)
5151
is_multiline: Mapped[bool] = mapped_column(nullable=False, default=False)
5252

5353
@override
@@ -75,7 +75,7 @@ def clone_with_entry_id(self, entry_id: int) -> TextField:
7575
class DatetimeField(BaseField):
7676
__tablename__ = "datetime_fields"
7777

78-
value: Mapped[str | None]
78+
value: Mapped[str | None] = mapped_column(sort_order=4)
7979

8080
@override
8181
def __eq__(self, other: object) -> bool:

src/tagstudio/core/library/alchemy/library.py

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ def open_sqlite_library(
419419
# Under -> sqlite-the-sqlite-dialect-now-uses-nullpool-for-file-based-databases
420420
poolclass = None if storage_path == ":memory:" else NullPool
421421
loaded_db_version: int = 0
422+
initial_db_version: int = DB_VERSION
422423

423424
logger.info(
424425
"[Library] Opening SQLite Library",
@@ -430,6 +431,7 @@ def open_sqlite_library(
430431
# Don't check DB version when creating new library
431432
if not is_new:
432433
loaded_db_version = self.get_version(DB_VERSION_CURRENT_KEY)
434+
initial_db_version = self.get_version(DB_VERSION_INITIAL_KEY)
433435

434436
# ======================== Library Database Version Checking =======================
435437
# DB_VERSION 6 is the first supported SQLite DB version.
@@ -452,7 +454,7 @@ def open_sqlite_library(
452454
),
453455
)
454456

455-
logger.info(f"[Library] DB_VERSION: {loaded_db_version}")
457+
logger.info(f"[Library] Library DB version: {loaded_db_version}")
456458
make_tables(self.engine)
457459

458460
if is_new:
@@ -571,6 +573,9 @@ def open_sqlite_library(
571573
self.__apply_db104_migrations(session, library_dir)
572574
if loaded_db_version < 200:
573575
self.__apply_db200_migrations(session)
576+
# changes: field tables
577+
if initial_db_version < 200 and loaded_db_version < 201:
578+
self.__apply_db201_migrations(session)
574579

575580
session.execute(
576581
text("CREATE INDEX IF NOT EXISTS idx_tags_name_shorthand ON tags (name, shorthand)")
@@ -588,6 +593,7 @@ def open_sqlite_library(
588593

589594
# Update DB_VERSION
590595
if loaded_db_version < DB_VERSION:
596+
logger.info(f"[Library] Library migrated to DB version {DB_VERSION}")
591597
self.set_version(DB_VERSION_CURRENT_KEY, DB_VERSION)
592598

593599
# everything is fine, set the library path
@@ -808,10 +814,6 @@ def __apply_db200_migrations(self, session: Session):
808814
session.execute(text("UPDATE datetime_fields SET name = type_key"))
809815
session.flush()
810816

811-
# TODO: Remove `type_key` columns from text_fields and datetime_fields tables.
812-
# See issue with dropping columns foreign keys in SQLite:
813-
# https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes
814-
815817
# Change `name` values to title case
816818
logger.info("[Library][Migration][200] Normalizing TextField names...")
817819
for text_field in session.execute(select(TextField)).scalars():
@@ -863,6 +865,59 @@ def __apply_db200_migrations(self, session: Session):
863865

864866
session.commit()
865867

868+
def __apply_db201_migrations(self, session: Session):
869+
"""Migrate DB to DB_VERSION 201."""
870+
with session:
871+
create_text_fields_table = text("""
872+
CREATE TABLE text_fields (
873+
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
874+
name VARCHAR NOT NULL,
875+
entry_id INTEGER NOT NULL,
876+
value VARCHAR,
877+
is_multiline BOOLEAN NOT NULL,
878+
FOREIGN KEY(entry_id) REFERENCES entries (id)
879+
)
880+
""")
881+
create_datetime_fields_table = text("""
882+
CREATE TABLE datetime_fields (
883+
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
884+
name VARCHAR NOT NULL,
885+
entry_id INTEGER NOT NULL,
886+
value VARCHAR,
887+
FOREIGN KEY(entry_id) REFERENCES entries (id)
888+
)
889+
""")
890+
891+
logger.info("[Library][Migration][201] Dropping type_key from text_fields table...")
892+
session.execute(text("ALTER TABLE text_fields RENAME TO text_fields_old"))
893+
session.flush()
894+
session.execute(create_text_fields_table)
895+
session.flush()
896+
session.execute(
897+
text("""
898+
INSERT INTO text_fields (id, name, entry_id, value, is_multiline)
899+
SELECT id, name, entry_id, value, is_multiline
900+
FROM text_fields_old
901+
""")
902+
)
903+
session.execute(text("DROP TABLE text_fields_old"))
904+
905+
logger.info("[Library][Migration][201] Dropping type_key from datetime_fields table...")
906+
session.execute(text("ALTER TABLE datetime_fields RENAME TO datetime_fields_old"))
907+
session.flush()
908+
session.execute(create_datetime_fields_table)
909+
session.flush()
910+
session.execute(
911+
text("""
912+
INSERT INTO datetime_fields (id, name, entry_id, value)
913+
SELECT id, name, entry_id, value
914+
FROM datetime_fields_old
915+
""")
916+
)
917+
session.execute(text("DROP TABLE datetime_fields_old"))
918+
919+
session.commit()
920+
866921
@property
867922
def field_templates(self) -> Sequence[BaseFieldTemplate]:
868923
with Session(self.engine) as session:
0 Bytes
Binary file not shown.

tests/test_db_migrations.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
# str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_102")),
3131
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_103")),
3232
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_200")),
33+
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_201")),
3334
],
3435
)
3536
def test_library_migrations(path: str):

0 commit comments

Comments
 (0)