diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-08-12 07:42:38 +0200 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-08-12 08:16:03 +0200 |
| commit | 2b5625d1c4a2bfad9baad07348e83ebad7b8330c (patch) | |
| tree | dcd82765abd728a4581a0f0585b16f612118b238 /src/Jellyfin.Database/Jellyfin.Database.Implementations/DescendantQueryHelper.cs | |
| parent | 9ae6ffe441586764554ea0e347b540365f069a00 (diff) | |
Resolve link owners from LinkedChildren
Diffstat (limited to 'src/Jellyfin.Database/Jellyfin.Database.Implementations/DescendantQueryHelper.cs')
| -rw-r--r-- | src/Jellyfin.Database/Jellyfin.Database.Implementations/DescendantQueryHelper.cs | 60 |
1 files changed, 25 insertions, 35 deletions
diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/DescendantQueryHelper.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/DescendantQueryHelper.cs index 9a42c86f7d..7425ebde83 100644 --- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/DescendantQueryHelper.cs +++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/DescendantQueryHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -85,7 +85,6 @@ public static class DescendantQueryHelper .Distinct() .ToHashSet(); - // The callers want only descendants, and an item is never its own descendant. descendants.ExceptWith(parentIds); return descendants; @@ -122,16 +121,18 @@ public static class DescendantQueryHelper var linkParents = ResolveLinkParents(context, matchingItemIds, hierarchyAncestors); - // The link parents are resolved ids, so they are read back as a sub-select to keep the result - // composable. An id without a BaseItem row could never match a caller's row anyway. - var linkedParents = context.BaseItems - .WhereOneOrMany(linkParents, e => e.Id) - .Select(e => e.Id); + // Read back as a sub-select so the result stays composable. LinkedChildren is the cheapest + // source: owning a link is what put an id in the set, and ParentId is its leading key. + var linkedParents = context.LinkedChildren + .WhereOneOrMany(linkParents, e => e.ParentId) + .Select(e => e.ParentId); var linkedParentAncestors = context.AncestorIds .WhereOneOrMany(linkParents, e => e.ItemId) .Select(e => e.ParentItemId); + // The chain an item carries stops at its collection folders, so this hop crosses that seam to + // the UserRootFolder above them. One statement for both sides beats a sub-select per side. var seamAncestors = context.AncestorIds .Where(e => hierarchyAncestors.Contains(e.ItemId) || linkedParentAncestors.Contains(e.ItemId)) .Select(e => e.ParentItemId); @@ -173,17 +174,11 @@ public static class DescendantQueryHelper return direct.Concat(indirect); } - /// <summary> - /// Resolves every folder that reaches one of the matching items through a linked edge. - /// </summary> - /// <returns>The ids of the folders whose linked children lead, at any depth, to a matching item.</returns> + // Resolves the folders whose linked children lead, at any depth, to a matching item. private static List<Guid> ResolveLinkParents(JellyfinDbContext context, IQueryable<Guid> matchingItemIds, IQueryable<Guid> ancestorsOfMatches) { - // A link sits above the closure as well as above another link: a BoxSet holds a Series whose - // episode matches, and another BoxSet holds that BoxSet. So the hop repeats until it stops - // finding anything new, and each hop takes the links landing on the set itself or on a folder - // that contains it. Only folders owning linked children are ever collected, which bounds this - // by the number of BoxSets and Playlists rather than by the item count. + // A link sits above the closure and above another link alike, so the hop repeats until nothing + // new turns up. Only link owners are collected, which bounds it by BoxSets and Playlists. var resolved = context.LinkedChildren .Where(e => matchingItemIds.Contains(e.ChildId) || ancestorsOfMatches.Contains(e.ChildId)) .Select(e => e.ParentId) @@ -214,7 +209,7 @@ public static class DescendantQueryHelper frontier = []; foreach (var id in next) { - // Cyclic links (a BoxSet holding itself, directly or not) terminate on the resolved set. + // Cyclic links terminate on the resolved set. if (resolved.Add(id)) { frontier.Add(id); @@ -225,16 +220,10 @@ public static class DescendantQueryHelper return [.. resolved]; } - /// <summary> - /// Resolves the roots the descendant sub-selects have to be anchored on. - /// </summary> - /// <returns> - /// The roots whose AncestorIds closure belongs to the result, and the roots whose LinkedChildren - /// belong to the result. - /// </returns> + // Resolves the roots the descendant sub-selects are anchored on: those contributing their closure, + // and those contributing their linked children. private static (List<Guid> ClosureRoots, List<Guid> LinkRoots) ResolveLinkedRoots(JellyfinDbContext context, Guid parentId) { - // A folder found through the closure needs no closure hop of its own. var closureRoots = new List<Guid> { parentId }; var linkRoots = new List<Guid> { parentId }; var visited = new HashSet<Guid> { parentId }; @@ -248,19 +237,21 @@ public static class DescendantQueryHelper .WhereOneOrMany(frontier, e => e.ParentId) .Select(e => e.ChildId); - // Folders that own linked children, i.e. the only items whose links are worth following. - var linkOwners = context.BaseItems - .Where(e => e.IsFolder - && (closureIds.Contains(e.Id) || linkedIds.Contains(e.Id)) - && context.LinkedChildren.Any(l => l.ParentId.Equals(e.Id))) - .Select(e => e.Id) - .ToArray(); - var linkedFolders = context.BaseItems .Where(e => e.IsFolder && linkedIds.Contains(e.Id)) .Select(e => e.Id) .ToHashSet(); + // Folders whose own links have to be followed. Driven off LinkedChildren because owning a + // link is the rare property, so the folder check only reaches rows that can qualify. That + // check stays: a non-folder owns links too (a movie and its alternate versions). + var linkOwners = context.LinkedChildren + .Where(e => (closureIds.Contains(e.ParentId) || linkedIds.Contains(e.ParentId)) + && e.Parent!.IsFolder) + .Select(e => e.ParentId) + .Distinct() + .ToArray(); + frontier = []; foreach (var id in linkOwners.Concat(linkedFolders)) { @@ -272,8 +263,7 @@ public static class DescendantQueryHelper frontier.Add(id); linkRoots.Add(id); - // Only a folder reached through a link contributes a closure that is not covered by - // the roots already collected. + // Only a folder reached through a link adds a closure the roots so far do not cover. if (linkedFolders.Contains(id)) { closureRoots.Add(id); |
