feat(#248): add merge/delete/count native queries to TagRepository
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
package org.raddatz.familienarchiv.repository;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.raddatz.familienarchiv.model.Tag;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
@@ -51,4 +53,67 @@ public interface TagRepository extends JpaRepository<Tag, UUID> {
|
||||
SELECT id FROM descendants
|
||||
""", nativeQuery = true)
|
||||
List<UUID> findDescendantIdsByName(@Param("name") String name);
|
||||
|
||||
/**
|
||||
* Returns the IDs of the tag with the given ID AND all of its descendants
|
||||
* via a recursive CTE. Used for merge validation and subtree delete.
|
||||
* Includes a depth guard of 50 levels to prevent runaway queries.
|
||||
*/
|
||||
@Query(value = """
|
||||
WITH RECURSIVE descendants AS (
|
||||
SELECT id, 0 AS depth FROM tag WHERE id = :tagId
|
||||
UNION ALL
|
||||
SELECT t.id, d.depth + 1 FROM tag t
|
||||
JOIN descendants d ON t.parent_id = d.id
|
||||
WHERE d.depth < 50
|
||||
)
|
||||
SELECT id FROM descendants
|
||||
""", nativeQuery = true)
|
||||
List<UUID> findDescendantIds(@Param("tagId") UUID tagId);
|
||||
|
||||
/**
|
||||
* Reassigns document_tags rows from source to target, skipping rows where
|
||||
* the target tag is already present (to avoid PK conflicts).
|
||||
*/
|
||||
@Modifying(clearAutomatically = true)
|
||||
@Query(value = """
|
||||
UPDATE document_tags
|
||||
SET tag_id = :targetId
|
||||
WHERE tag_id = :sourceId
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM document_tags d2
|
||||
WHERE d2.document_id = document_tags.document_id
|
||||
AND d2.tag_id = :targetId
|
||||
)
|
||||
""", nativeQuery = true)
|
||||
void reassignDocumentTags(@Param("sourceId") UUID sourceId, @Param("targetId") UUID targetId);
|
||||
|
||||
/**
|
||||
* Removes all document_tags rows for the given tag.
|
||||
*/
|
||||
@Modifying(clearAutomatically = true)
|
||||
@Query(value = "DELETE FROM document_tags WHERE tag_id = :tagId", nativeQuery = true)
|
||||
void deleteDocumentTagsByTagId(@Param("tagId") UUID tagId);
|
||||
|
||||
/**
|
||||
* Removes all document_tags rows for the given collection of tag IDs.
|
||||
* Caller must guard against an empty collection — PostgreSQL rejects IN ().
|
||||
*/
|
||||
@Modifying(clearAutomatically = true)
|
||||
@Query(value = "DELETE FROM document_tags WHERE tag_id IN :ids", nativeQuery = true)
|
||||
void deleteDocumentTagsByTagIds(@Param("ids") Collection<UUID> ids);
|
||||
|
||||
/**
|
||||
* Re-parents all direct children of sourceId to targetId.
|
||||
*/
|
||||
@Modifying(clearAutomatically = true)
|
||||
@Query(value = "UPDATE tag SET parent_id = :targetId WHERE parent_id = :sourceId", nativeQuery = true)
|
||||
void reparentChildren(@Param("sourceId") UUID sourceId, @Param("targetId") UUID targetId);
|
||||
|
||||
/**
|
||||
* Returns (tag_id, count) pairs for all tags that appear in document_tags.
|
||||
* Used to populate documentCount in the tag tree without N+1 queries.
|
||||
*/
|
||||
@Query(value = "SELECT tag_id, COUNT(*) FROM document_tags GROUP BY tag_id", nativeQuery = true)
|
||||
List<Object[]> findDocumentCountsPerTag();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user