From 277095ee012d0512d37e907f9ff9b01614b9d6f7 Mon Sep 17 00:00:00 2001 From: RustyNova Date: Fri, 5 Sep 2025 07:37:18 +0000 Subject: [PATCH] feat: added trigger to prevent a tag to be its own parent --- .../core/library/alchemy/constants.py | 2 +- src/tagstudio/core/library/alchemy/library.py | 51 ++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/tagstudio/core/library/alchemy/constants.py b/src/tagstudio/core/library/alchemy/constants.py index 26ce1c832..046f2f4dc 100644 --- a/src/tagstudio/core/library/alchemy/constants.py +++ b/src/tagstudio/core/library/alchemy/constants.py @@ -11,7 +11,7 @@ DB_VERSION_LEGACY_KEY: str = "DB_VERSION" DB_VERSION_CURRENT_KEY: str = "CURRENT" DB_VERSION_INITIAL_KEY: str = "INITIAL" -DB_VERSION: int = 101 +DB_VERSION: int = 102 TAG_CHILDREN_QUERY = text(""" WITH RECURSIVE ChildTags AS ( diff --git a/src/tagstudio/core/library/alchemy/library.py b/src/tagstudio/core/library/alchemy/library.py index fc56f9194..2244feb2f 100644 --- a/src/tagstudio/core/library/alchemy/library.py +++ b/src/tagstudio/core/library/alchemy/library.py @@ -542,7 +542,11 @@ def open_sqlite_library(self, library_dir: Path, is_new: bool) -> LibraryStatus: if loaded_db_version < 9: self.__apply_db9_filename_population(session) if loaded_db_version < 100: - self.__apply_db100_parent_repairs(session) + self.__apply_db100_parent_repairs(session) + + # This change might break stuff if done before the datachanges. + if loaded_db_version < 102: + self.__apply_db102_schema_changes(session) # Convert file extension list to ts_ignore file, if a .ts_ignore file does not exist self.migrate_sql_to_ts_ignore(library_dir) @@ -681,6 +685,51 @@ def __apply_db100_parent_repairs(self, session: Session): session.commit() logger.info("[Library][Migration] Refactored TagParent column") + def __apply_db102_schema_changes(self, session: Session): + """Add a trigger to prevent having a tag being its own parent""" + add_trigger = text( + """ + CREATE TRIGGER `trigger_before_insert_tag_parents` BEFORE INSERT ON `tag_parents` BEGIN + WITH RECURSIVE + ChildTags AS ( + SELECT + NEW.child_id AS tag_id + UNION + SELECT + tp.child_id AS tag_id + FROM + tag_parents tp + INNER JOIN ChildTags c ON tp.parent_id = c.tag_id + ) + SELECT + RAISE ( + ABORT, + 'Cannot insert a tag as a child when it is already a parent' + ) + WHERE + EXISTS ( + SELECT + 1 + FROM + ChildTags + WHERE + ChildTags.`tag_id` = NEW.`parent_id` + ); + + END; + """ + ) + try: + session.execute(add_trigger) + session.commit() + logger.info("[Library][Migration] Added trigger to prevent having a tag being its own parent") + except Exception as e: + logger.error( + "[Library][Migration] Could not create trigger to prevent having a tag being its own parent!", + error=e, + ) + session.rollback() + def migrate_sql_to_ts_ignore(self, library_dir: Path): # Do not continue if existing '.ts_ignore' file is found if Path(library_dir / TS_FOLDER_NAME / IGNORE_NAME).exists():