#pragma warning disable RS0030 // Do not use banned APIs using System; using System.Linq; using System.Linq.Expressions; using Jellyfin.Database.Implementations; using Jellyfin.Database.Implementations.Entities; namespace Jellyfin.Server.Implementations.Item; /// /// Extension methods for applying folder-aware filters that check items and their descendants. /// internal static class FolderAwareFilterExtensions { /// /// Filters items where either the item matches the condition (for non-folders) /// or any descendant matches (for folders). Uses reverse traversal through AncestorIds. /// /// The query to filter. /// The database context. /// The condition to check on BaseItemEntity. /// Filtered query. public static IQueryable WhereItemOrDescendantMatches( this IQueryable query, JellyfinDbContext context, Expression> condition) { // Use correlated Any() subqueries instead of UNION + Contains for better index utilization var matchingIds = context.BaseItems.Where(condition).Select(b => b.Id); return query.Where(e => matchingIds.Contains(e.Id) || context.AncestorIds.Any(a => a.ParentItemId == e.Id && matchingIds.Contains(a.ItemId)) || context.LinkedChildren.Any(lc => lc.ParentId == e.Id && matchingIds.Contains(lc.ChildId))); } /// /// Filters items where neither the item matches the condition (for non-folders) /// nor any descendant matches (for folders). Uses reverse traversal for infinite depth. /// /// The query to filter. /// The database context. /// The condition that should NOT match. /// Filtered query. public static IQueryable WhereNeitherItemNorDescendantMatches( this IQueryable query, JellyfinDbContext context, Expression> condition) { var matchingIds = context.BaseItems.Where(condition).Select(b => b.Id); return query.Where(e => !matchingIds.Contains(e.Id) && !context.AncestorIds.Any(a => a.ParentItemId == e.Id && matchingIds.Contains(a.ItemId)) && !context.LinkedChildren.Any(lc => lc.ParentId == e.Id && matchingIds.Contains(lc.ChildId))); } }