Skip to content

Fix: Swapped IDs in parent_tags DB table #998

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

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
2 changes: 1 addition & 1 deletion src/tagstudio/core/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,4 @@ class LibraryPrefs(DefaultEnum):

IS_EXCLUDE_LIST = True
EXTENSION_LIST = [".json", ".xmp", ".aae"]
DB_VERSION = 9
DB_VERSION = 10
30 changes: 23 additions & 7 deletions src/tagstudio/core/library/alchemy/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,8 @@ def migrate_json_to_sqlite(self, json_lib: JsonLibrary):

# Parent Tags (Previously known as "Subtags" in JSON)
for tag in json_lib.tags:
for child_id in tag.subtag_ids:
self.add_parent_tag(parent_id=tag.id, child_id=child_id)
for parent_id in tag.subtag_ids:
self.add_parent_tag(parent_id=parent_id, child_id=tag.id)

# Entries
self.add_entries(
Expand Down Expand Up @@ -491,6 +491,8 @@ def open_sqlite_library(self, library_dir: Path, is_new: bool) -> LibraryStatus:
self.apply_db8_default_data(session)
if db_version < 9:
self.apply_db9_filename_population(session)
if db_version < 10:
self.apply_db10_parent_repairs(session)

# Update DB_VERSION
if LibraryPrefs.DB_VERSION.default > db_version:
Expand Down Expand Up @@ -616,6 +618,20 @@ def apply_db9_filename_population(self, session: Session):
session.commit()
logger.info("[Library][Migration] Populated filename column in entries table")

def apply_db10_parent_repairs(self, session: Session):
"""Apply database repairs introduced in DB_VERSION 10."""
logger.info("[Library][Migration] Applying patches to DB_VERSION: 10 library...")
with session:
# Repair parent-child tag relationships that are the wrong way around.
stmt = update(TagParent).values(
parent_id=TagParent.child_id,
child_id=TagParent.parent_id,
)
session.execute(stmt)
session.flush()

session.commit()

@property
def default_fields(self) -> list[BaseField]:
with Session(self.engine) as session:
Expand Down Expand Up @@ -1588,22 +1604,22 @@ def update_parent_tags(self, tag: Tag, parent_ids: list[int] | set[int], session

# load all tag's parent tags to know which to remove
prev_parent_tags = session.scalars(
select(TagParent).where(TagParent.parent_id == tag.id)
select(TagParent).where(TagParent.child_id == tag.id)
).all()

for parent_tag in prev_parent_tags:
if parent_tag.child_id not in parent_ids:
if parent_tag.parent_id not in parent_ids:
session.delete(parent_tag)
else:
# no change, remove from list
parent_ids.remove(parent_tag.child_id)
parent_ids.remove(parent_tag.parent_id)

# create remaining items
for parent_id in parent_ids:
# add new parent tag
parent_tag = TagParent(
parent_id=tag.id,
child_id=parent_id,
parent_id=parent_id,
child_id=tag.id,
)
session.add(parent_tag)

Expand Down
4 changes: 2 additions & 2 deletions src/tagstudio/core/library/alchemy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ class Tag(Base):
aliases: Mapped[set[TagAlias]] = relationship(back_populates="tag")
parent_tags: Mapped[set["Tag"]] = relationship(
secondary=TagParent.__tablename__,
primaryjoin="Tag.id == TagParent.parent_id",
secondaryjoin="Tag.id == TagParent.child_id",
primaryjoin="Tag.id == TagParent.child_id",
secondaryjoin="Tag.id == TagParent.parent_id",
back_populates="parent_tags",
)
disambiguation_id: Mapped[int | None]
Expand Down
12 changes: 5 additions & 7 deletions src/tagstudio/core/library/alchemy/visitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,15 @@

logger = structlog.get_logger(__name__)

# TODO: Reevaluate after subtags -> parent tags name change
TAG_CHILDREN_ID_QUERY = text("""
-- Note for this entire query that tag_parents.child_id is the parent id and tag_parents.parent_id is the child id due to bad naming
WITH RECURSIVE ChildTags AS (
SELECT :tag_id AS child_id
SELECT :tag_id AS tag_id
UNION
SELECT tp.parent_id AS child_id
FROM tag_parents tp
INNER JOIN ChildTags c ON tp.child_id = c.child_id
SELECT tp.child_id AS tag_id
FROM tag_parents tp
INNER JOIN ChildTags c ON tp.parent_id = c.tag_id
)
SELECT child_id FROM ChildTags;
SELECT tag_id FROM ChildTags;
""") # noqa: E501


Expand Down
2 changes: 1 addition & 1 deletion src/tagstudio/qt/widgets/migration_modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,7 @@ def check_subtag_parity(self) -> bool:
for tag in self.sql_lib.tags:
tag_id = tag.id # Tag IDs start at 0
sql_parent_tags = set(
session.scalars(select(TagParent.child_id).where(TagParent.parent_id == tag.id))
session.scalars(select(TagParent.parent_id).where(TagParent.child_id == tag.id))
)

# JSON tags allowed self-parenting; SQL tags no longer allow this.
Expand Down
2 changes: 1 addition & 1 deletion tests/qt/test_build_tag_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def test_build_tag_panel_set_parent_tags(library, generate_tag):
assert parent
assert child

library.add_parent_tag(child.id, parent.id)
library.add_parent_tag(parent.id, child.id)

child = library.get_tag(child.id)

Expand Down